private async Task <IEnumerable <TemplateMappingItem> > GetTemplateMappingItemsInSpecification(SpecificationSummary specification, MigrationClients clients)
        {
            SemaphoreSlim throttle = new SemaphoreSlim(5, 5);
            ConcurrentBag <Task <TemplateMapping> > templateMappingQueryTasks = new ConcurrentBag <Task <TemplateMapping> >();

            string specificationId = specification.Id;

            foreach (Reference fundingStream in specification.FundingStreams)
            {
                templateMappingQueryTasks.Add(Task.Run(() =>
                {
                    try
                    {
                        throttle.Wait();

                        string fundingStreamId = fundingStream.Id;

                        ApiResponse <TemplateMapping> templateMappingResponse = clients.MakeCalculationsCall(_ => _.GetTemplateMapping(specificationId, fundingStreamId))
                                                                                .GetAwaiter()
                                                                                .GetResult();

                        CheckApiResponse(templateMappingResponse, $"Unable to fetch template mapping for specification id {specificationId} and funding stream id {fundingStreamId}");

                        return(templateMappingResponse.Content);
                    }
                    finally
                    {
                        throttle.Release();
                    }
                }));
            }

            await TaskHelper.WhenAllAndThrow(templateMappingQueryTasks.ToArray());

            return(templateMappingQueryTasks.SelectMany(_ => _.Result.TemplateMappingItems).ToArray());
        }
        private async Task <bool> TryMigrateCalculations(SpecificationSummary sourceSpecification,
                                                         SpecificationSummary destinationSpecification)
        {
            /*
             *  Create any missing additional calculations on destination specification
             *  Get the template calculation mapping for source and destination specifications for each funding streams
             *  Set the source code in the destination calculation for each of the calculations (based on TemplateCalculationId) where the source code is different
             */

            string sourceSpecificationId      = sourceSpecification.Id;
            string destinationSpecificationId = destinationSpecification.Id;

            Task <IEnumerable <TemplateMappingItem> >        sourceTemplateMappingsQueryTask      = GetTemplateMappingItemsInSpecification(sourceSpecification, _sourceClients);
            Task <IEnumerable <TemplateMappingItem> >        destinationTemplateMappingsQueryTask = GetTemplateMappingItemsInSpecification(destinationSpecification, _destinationClients);
            Task <ApiResponse <IEnumerable <Calculation> > > sourceCalculationsQueryTask          = _sourceClients.MakeCalculationsCall(
                _ => _.GetCalculationsForSpecification(sourceSpecificationId));
            Task <ApiResponse <IEnumerable <Calculation> > > destinationCalculationsQueryTask = _destinationClients.MakeCalculationsCall(
                _ => _.GetCalculationsForSpecification(destinationSpecificationId));

            WriteLine("Fetching template mappings and current calculation versions from source and destination specifications");

            await TaskHelper.WhenAllAndThrow(sourceCalculationsQueryTask, destinationCalculationsQueryTask, sourceTemplateMappingsQueryTask, destinationTemplateMappingsQueryTask);

            ApiResponse <IEnumerable <Calculation> > sourceCalculationsResponse      = sourceCalculationsQueryTask.Result;
            ApiResponse <IEnumerable <Calculation> > destinationCalculationsResponse = destinationCalculationsQueryTask.Result;

            CheckApiResponse(sourceCalculationsResponse, $"Unable to query current calculations for source specification {sourceSpecificationId}");
            CheckApiResponse(destinationCalculationsResponse, $"Unable to query current calculations for destination specification {destinationSpecificationId}");

            CalculationType additional = CalculationType.Additional;


            Calculation[] sourceAdditionalCalculations = sourceCalculationsResponse.Content.Where(
                _ => _.CalculationType == additional).ToArray();
            Calculation[] destinationAdditionalCalculations = destinationCalculationsResponse.Content.Where(
                _ => _.CalculationType == additional).ToArray();

            var template = CalculationType.Template;

            Dictionary <string, Calculation> sourceTemplateCalculations = sourceCalculationsResponse.Content.Where(
                _ => _.CalculationType == template).ToDictionary(_ => _.Id, _ => _);
            Dictionary <string, Calculation> destinationTemplateCalculations = destinationCalculationsResponse.Content.Where(
                _ => _.CalculationType == template).ToDictionary(_ => _.Id, _ => _);



            TemplateMappingItem[] sourceTemplateMappings = sourceTemplateMappingsQueryTask.Result.Where(
                _ => _.EntityType == TemplateMappingEntityType.Calculation)
                                                           .ToArray();
            Dictionary <uint, string> destinationTemplateIdToCalculationIdMappings = destinationTemplateMappingsQueryTask
                                                                                     .Result.Where(_ => _.EntityType == TemplateMappingEntityType.Calculation)
                                                                                     .ToDictionary(_ => _.TemplateId, _ => _.CalculationId);

            CheckForTemplateErrors(sourceTemplateMappings, destinationTemplateIdToCalculationIdMappings);

            if (MigrationHasErrors)
            {
                return(false);
            }

            WriteLine("Creating missing additional calculations from source specification in destination specification");

            await CreateMissingAdditionalCalculations(sourceAdditionalCalculations, destinationAdditionalCalculations, destinationSpecificationId);

            if (MigrationHasErrors)
            {
                return(false);
            }

            WriteLine("Updating source code for any template calculations in destination specification where this differs in source (using template id to match calculations)");

            await EnsureDestinationTemplateCalculationSourceCodeMatchesSource(sourceTemplateMappings,
                                                                              destinationTemplateIdToCalculationIdMappings,
                                                                              sourceTemplateCalculations,
                                                                              destinationTemplateCalculations,
                                                                              destinationSpecificationId);

            return(!MigrationHasErrors);
        }