public static async Task Run(
            string input,          // Work around https://github.com/Azure/azure-functions-vs-build-sdk/issues/168
            ILogger logger,
            [Inject] IConfigurationRoot configuration,
            [Inject] IVenueCollectionService venueCollectionService,
            [Inject] IProviderCollectionService providerCollectionService,
            [Inject] ICosmosDbHelper cosmosDbHelper,
            [Inject] IBlobStorageHelper blobhelper
            )
        {
            logger.LogDebug("VenueRestorer: Starting...");
            var connectionString   = configuration.GetConnectionString("TribalRestore");
            var venuesCollectionId = configuration["CosmosDbCollectionSettings:VenuesCollectionId"];
            var blobContainer      = configuration["BlobStorageSettings:Container"];
            var container          = configuration["BlobStorageSettings:Container"];
            var whiteListProviders = await GetProviderWhiteList();

            logger.LogDebug($"VenueRestorer: {whiteListProviders.Count} white-listed providers to process");
            var          coursesCollectionId        = "courses";
            var          apprenticeshipCollectionId = "apprenticeship";
            var          databaseId                  = configuration["CosmosDbSettings:DatabaseId"];
            var          documentClient              = cosmosDbHelper.GetClient();
            var          venueCollectionUri          = UriFactory.CreateDocumentCollectionUri(databaseId, venuesCollectionId);
            var          venueCollection_OldUri      = UriFactory.CreateDocumentCollectionUri(databaseId, "venues_old");
            var          coursesCollectionUri        = UriFactory.CreateDocumentCollectionUri(databaseId, coursesCollectionId);
            var          apprenticeshipCollectionUri = UriFactory.CreateDocumentCollectionUri(databaseId, apprenticeshipCollectionId);
            var          logFileName                 = $"VenueRestorer--{DateTime.Now.ToString("dd-MM-yy HHmm")}";
            var          allVenues       = GetAllOldVenues();
            var          result          = new List <ResultMessage>();
            const string WHITE_LIST_FILE = "ProviderWhiteList.txt";
            var          ukprnCache      = new List <int>();
            var          updatedBy       = "VenueRestorer";

            //grand totals
            var invalidCourseRunReferences      = 0;
            var totalInvalidCourseRunReferences = 0;
            var uniqueInvalidVenues             = new HashSet <string>();
            var replacedInvalidVenues           = new HashSet <Venue>();
            var references = new List <VenueRestorerReference>();
            var rereferencedApprenticeshipLocations = 0;

            //provider scoped totals
            var totalInvalidApprenticeshipLocationReferences = 0;
            var invalidApprenticeshipLocationReferences      = 0;

            int processedProviderCount = 0;

            using (var logStream = new MemoryStream())
                using (var logStreamWriter = new StreamWriter(logStream))
                    using (var logCsvWriter = new CsvWriter(logStreamWriter, CultureInfo.InvariantCulture))
                    {
                        foreach (var ukprn in whiteListProviders)
                        {
                            //reference for old venue so that courseruns & apprenticeship locations can be
                            //re-referenced
                            var venuesReplacedForProvider = new List <Tuple <Venue, Venue, Guid> >();
                            try
                            {
                                //reset counters
                                invalidCourseRunReferences = 0;
                                invalidApprenticeshipLocationReferences = 0;

                                //fetch data for ukprn
                                var allCoursesForProvider = await GetCourses(ukprn);

                                var allApprenticeshipsForProvider = await GetApprenticeships(ukprn);

                                var venues = await GetVenues(ukprn);

                                var old_venues = GetOldVenues(ukprn);

                                //courses
                                foreach (var course in allCoursesForProvider)
                                {
                                    //course runs
                                    foreach (var courserun in course.CourseRuns)
                                    {
                                        //only courses that references a venue (classroom based or both)
                                        if (courserun.VenueId != null && courserun.VenueId != Guid.Empty)
                                        {
                                            //current venue & old venue (pre migration)
                                            var invalidReferencedVenue = await GetVenueById(courserun.VenueId?.ToString());

                                            var restoredVenue = old_venues.FirstOrDefault(x => new Guid(x.ID) == courserun.VenueId);

                                            //if current venues provider is different to course then attempt to replace it with
                                            //old venue from cosmos backup.
                                            if (invalidReferencedVenue != null && invalidReferencedVenue.UKPRN != course.ProviderUKPRN)
                                            {
                                                //replace existing venue with old venue if a match is found
                                                if (restoredVenue != null && restoredVenue.UKPRN == course.ProviderUKPRN)
                                                {
                                                    await ReplaceVenue(invalidReferencedVenue.ID, restoredVenue, updatedBy);

                                                    //the venue that was referenced needs to be inserted again but with a new id.
                                                    var newId = Guid.NewGuid();
                                                    await ReplaceVenue(newId.ToString(), invalidReferencedVenue, updatedBy);

                                                    //reload venues as we have just replaced a venue
                                                    venues = await GetVenues(ukprn);

                                                    replacedInvalidVenues.Add(invalidReferencedVenue);

                                                    //store old venue so that apprenticeship locations can be re-referenced
                                                    venuesReplacedForProvider.Add(Tuple.Create(restoredVenue, invalidReferencedVenue, newId));

                                                    //log changes
                                                    references.Add(new VenueRestorerReference()
                                                    {
                                                        UKPRN              = course.ProviderUKPRN,
                                                        VenueId            = courserun.VenueId.ToString(),
                                                        CurrentVenueUKPRN  = invalidReferencedVenue.UKPRN,
                                                        CurrentAddress1    = invalidReferencedVenue.Address1,
                                                        CurrentPostcode    = invalidReferencedVenue.PostCode,
                                                        CurrentVenueName   = invalidReferencedVenue.VenueName,
                                                        RestoredAddress1   = restoredVenue.Address1,
                                                        RestoredVenueName  = restoredVenue.VenueName,
                                                        RestoredPostcode   = restoredVenue.PostCode,
                                                        RestoredVenueUKPRN = restoredVenue.UKPRN,
                                                        UKPRNMatched       = (course.ProviderUKPRN == invalidReferencedVenue.UKPRN),
                                                        Message            = "Replaced Venue",
                                                        Type        = "Course",
                                                        CourseId    = course.id,
                                                        CourseRunId = courserun.id,
                                                    });
                                                }
                                                else
                                                {
                                                    references.Add(new VenueRestorerReference()
                                                    {
                                                        UKPRN             = course.ProviderUKPRN,
                                                        VenueId           = courserun.VenueId.ToString(),
                                                        CurrentVenueUKPRN = invalidReferencedVenue.UKPRN,
                                                        CurrentAddress1   = invalidReferencedVenue.Address1,
                                                        CurrentPostcode   = invalidReferencedVenue.PostCode,
                                                        CurrentVenueName  = invalidReferencedVenue.VenueName,
                                                        UKPRNMatched      = (course.ProviderUKPRN == invalidReferencedVenue.UKPRN),
                                                        Message           = "Unable to replace Venue, as old venue was not found in backup",
                                                        Type        = "Course",
                                                        CourseId    = course.id,
                                                        CourseRunId = courserun.id,
                                                    });
                                                }

                                                //invalid references
                                                uniqueInvalidVenues.Add(invalidReferencedVenue.ID);
                                                invalidCourseRunReferences++;
                                            }
                                        }
                                    }

                                    //total for all providers
                                    totalInvalidCourseRunReferences += invalidCourseRunReferences;
                                }

                                //apprenticeships
                                foreach (var apprenticeship in allApprenticeshipsForProvider)
                                {
                                    //apprenticeship locations
                                    foreach (var location in apprenticeship.ApprenticeshipLocations)
                                    {
                                        //only apprenticeshiplocations that references a venue (classroom based or both)
                                        if (location.VenueId.HasValue && location.LocationGuidId.HasValue && location.LocationGuidId != Guid.Empty)
                                        {
                                            var invalidReferencedVenue = await GetVenueById(location.LocationGuidId?.ToString());

                                            if (invalidReferencedVenue != null && invalidReferencedVenue.UKPRN != apprenticeship.ProviderUKPRN)
                                            {
                                                var restoredVenue = old_venues.FirstOrDefault(x => new Guid(x.ID) == location.LocationGuidId);

                                                //replace existing venue with old venue if a match is found
                                                if (restoredVenue != null && restoredVenue.UKPRN == apprenticeship.ProviderUKPRN)
                                                {
                                                    logger.LogDebug($"VenueRestorer: Invalid Apprenticeship location, apprenticeship should reference {restoredVenue.VenueName}");

                                                    //old venue from json backup is the correct venue that should be referenced
                                                    //swap invalid venue, with restoredVenue
                                                    await ReplaceVenue(invalidReferencedVenue.ID, restoredVenue, updatedBy);

                                                    //the venue that was referenced needs to be inserted again but with a new id.
                                                    var newId = Guid.NewGuid();
                                                    await ReplaceVenue(newId.ToString(), invalidReferencedVenue, updatedBy);

                                                    //store old venue so that apprenticeship locations can be re-referenced
                                                    venuesReplacedForProvider.Add(Tuple.Create(restoredVenue, invalidReferencedVenue, newId));

                                                    //reload venues as we have just replaced a venue
                                                    venues = await GetVenues(ukprn);

                                                    //keep a track of the incorrect venue, these will be inserted with a new id.
                                                    replacedInvalidVenues.Add(invalidReferencedVenue);

                                                    references.Add(new VenueRestorerReference()
                                                    {
                                                        UKPRN = apprenticeship.ProviderUKPRN,
                                                        ApprenticeshipLocationUKPRN = location.ProviderUKPRN,
                                                        VenueId            = location.LocationGuidId.ToString(),
                                                        CurrentVenueUKPRN  = invalidReferencedVenue.UKPRN,
                                                        CurrentAddress1    = invalidReferencedVenue.Address1,
                                                        CurrentPostcode    = invalidReferencedVenue.PostCode,
                                                        CurrentVenueName   = invalidReferencedVenue.VenueName,
                                                        RestoredVenueUKPRN = restoredVenue.UKPRN,
                                                        RestoredAddress1   = restoredVenue?.Address1,
                                                        RestoredPostcode   = restoredVenue?.PostCode,
                                                        RestoredVenueName  = restoredVenue.VenueName,
                                                        UKPRNMatched       = (apprenticeship.ProviderUKPRN == invalidReferencedVenue.UKPRN),
                                                        Message            = "Replaced Venue",
                                                        Type             = "Apprenticeship",
                                                        ApprenticeshipId = apprenticeship.id
                                                    });
                                                }
                                                else
                                                {
                                                    references.Add(new VenueRestorerReference()
                                                    {
                                                        UKPRN = apprenticeship.ProviderUKPRN,
                                                        ApprenticeshipLocationUKPRN = location.ProviderUKPRN,
                                                        UKPRNMatched      = false,
                                                        CurrentVenueUKPRN = -1,
                                                        VenueId           = location.LocationGuidId.ToString(),
                                                        Type             = "Apprenticeship",
                                                        Message          = "Unable to replace Venue, as old venue was not found in backup",
                                                        ApprenticeshipId = apprenticeship.id
                                                    });
                                                }
                                            }
                                        }
                                    }
                                    totalInvalidApprenticeshipLocationReferences += invalidApprenticeshipLocationReferences;
                                }

                                //rereference apprenticeship locations
                                //if there is a venue that has been replaced but is referenced by an apprenticeshiplocation then the apprenticeship
                                //record needs to be updated to point the the new venue record to save data loss.
                                foreach (var apprenticeship in allApprenticeshipsForProvider)
                                {
                                    var updated = false;
                                    foreach (var location in apprenticeship.ApprenticeshipLocations)
                                    {
                                        var replacedVenue = venuesReplacedForProvider.FirstOrDefault(x => new Guid(x.Item2.ID) == location.LocationGuidId);
                                        if (replacedVenue != null)
                                        {
                                            updated = true;
                                            location.LocationGuidId = replacedVenue.Item3;
                                        }
                                    }

                                    if (updated)
                                    {
                                        var documentLink = UriFactory.CreateDocumentUri(databaseId, apprenticeshipCollectionId, apprenticeship.id.ToString());
                                        await documentClient.ReplaceDocumentAsync(documentLink, apprenticeship, new RequestOptions()
                                        {
                                            PartitionKey = new Microsoft.Azure.Documents.PartitionKey(ukprn)
                                        });
                                    }
                                }
                            }
                            catch (Exception e)
                            {
                                logger.LogError($"VenueRestorer: error for ukprn {ukprn}: {e.Message}", e);
                            }

                            processedProviderCount++;
                            if (processedProviderCount % 100 == 0)
                            {
                                logger.LogInformation($"VenueRestorer: {processedProviderCount}/{whiteListProviders.Count} providers processed.");
                            }
                            logger.LogDebug(
                                $"VenueRestorer: completed for UKPRN {ukprn}, {processedProviderCount}/{whiteListProviders.Count}. " +
                                $"{invalidCourseRunReferences} invalid venue references, " +
                                $"{invalidApprenticeshipLocationReferences} invalid apprenticeship references, " +
                                $"{rereferencedApprenticeshipLocations} apprenticeship locations were re-referenced.");
                        }

                        //write csv file
                        logCsvWriter.WriteHeader(typeof(VenueRestorerReference));
                        logCsvWriter.NextRecord();
                        foreach (var reference in references)
                        {
                            logCsvWriter.WriteRecord(reference);
                            logCsvWriter.NextRecord();
                        }

                        // Upload log CSV to blob storage
                        logStreamWriter.Flush();
                        logStream.Seek(0L, SeekOrigin.Begin);
                        var blob = blobhelper.GetBlobContainer(blobContainer).GetBlockBlobReference(logFileName);
                        await blob.UploadFromStreamAsync(logStream);

                        logger.LogInformation($"VenueRestorer: log uploaded as {logFileName}");
                    }

            logger.LogInformation(
                $"VenueRestorer: completed. " +
                $"{totalInvalidCourseRunReferences} invalid CourseRun references in total, " +
                $"{totalInvalidApprenticeshipLocationReferences} Apprenticeship location invalid references in total, " +
                $"{replacedInvalidVenues.Count()} Venues have been reverted back to old venues, " +
                $"{uniqueInvalidVenues.Count()} Venues were invalid");

            async Task <List <Course> > GetCourses(int ukprn)
            {
                var courses = new List <Course>();
                //Get all courses
                string continuation = null;

                do
                {
                    var feedOptions = new FeedOptions()
                    {
                        RequestContinuation = continuation,
                        PartitionKey        = new Microsoft.Azure.Documents.PartitionKey(ukprn)
                    };

                    var queryResponse = await documentClient.CreateDocumentQuery <Course>(coursesCollectionUri, feedOptions)
                                        .Where(p => p.ProviderUKPRN == ukprn && p.CourseStatus != CourseDirectory.Models.Enums.RecordStatus.Archived)
                                        .AsDocumentQuery()
                                        .ExecuteNextAsync <Course>();

                    courses.AddRange(queryResponse.ToList());
                    continuation = queryResponse.ResponseContinuation;
                }while (continuation != null);
                return(courses);
            }

            async Task <List <Venue> > GetVenues(int ukprn)
            {
                var    venues       = new List <Venue>();
                string continuation = null;

                do
                {
                    var feedOptions = new FeedOptions()
                    {
                        RequestContinuation = continuation
                    };

                    var queryResponse = await documentClient.CreateDocumentQuery <Venue>(venueCollectionUri, feedOptions)
                                        .Where(p => p.UKPRN == ukprn)
                                        .AsDocumentQuery()
                                        .ExecuteNextAsync <Venue>();

                    venues.AddRange(queryResponse.ToList());

                    continuation = queryResponse.ResponseContinuation;
                }while (continuation != null);
                return(venues);
            }

            List <Venue> GetOldVenues(int ukprn)
            {
                return(allVenues.Where(x => x.UKPRN == ukprn).ToList());
            }

            async Task <Venue> GetVenueById(string id)
            {
                var collectionLink = UriFactory.CreateDocumentCollectionUri(databaseId, venuesCollectionId);

                var query = documentClient
                            .CreateDocumentQuery <Venue>(collectionLink, new FeedOptions()
                {
                    EnableCrossPartitionQuery = true
                })
                            .Where(d => d.ID == id)
                            .AsDocumentQuery();

                return((await query.ExecuteNextAsync()).FirstOrDefault());
            }

            async Task <List <Apprenticeship> > GetApprenticeships(int ukprn)
            {
                var    apprenticeships = new List <Apprenticeship>();
                string continuation    = null;

                do
                {
                    var feedOptions = new FeedOptions()
                    {
                        RequestContinuation = continuation,
                        PartitionKey        = new Microsoft.Azure.Documents.PartitionKey(ukprn)
                    };

                    try
                    {
                        var queryResponse = await documentClient.CreateDocumentQuery <Apprenticeship>(apprenticeshipCollectionUri, feedOptions)
                                            .Where(p => p.ProviderUKPRN == ukprn && p.RecordStatus != CourseDirectory.Models.Enums.RecordStatus.Archived)
                                            .AsDocumentQuery()
                                            .ExecuteNextAsync <Apprenticeship>();

                        apprenticeships.AddRange(queryResponse);
                        continuation = queryResponse.ResponseContinuation;
                    }
                    catch (Exception)
                    {
                        continuation = null;
                    }
                }while (continuation != null);

                return(apprenticeships);
            }

            async Task <ISet <int> > GetProviderWhiteList()
            {
                var blob = blobhelper.GetBlobContainer(blobContainer).GetBlockBlobReference(WHITE_LIST_FILE);
                var ms   = new MemoryStream();
                await blob.DownloadToStreamAsync(ms);

                ms.Seek(0L, SeekOrigin.Begin);

                var    results = new HashSet <int>();
                string line;

                using (var reader = new StreamReader(ms))
                {
                    while ((line = reader.ReadLine()) != null)
                    {
                        if (string.IsNullOrEmpty(line))
                        {
                            continue;
                        }

                        var ukprn = int.Parse(line);
                        results.Add(ukprn);
                    }
                }
                return(results);
            }

            async Task ReplaceVenue(string id, Venue matchedVenue, string updatedby)
            {
                Uri collectionUri = UriFactory.CreateDocumentCollectionUri(databaseId, venuesCollectionId);
                var editedVenue   = new Dfc.CourseDirectory.Models.Models.Venues.Venue()
                {
                    ID               = id,
                    UKPRN            = matchedVenue.UKPRN,
                    VenueName        = matchedVenue.VenueName,
                    Address1         = matchedVenue.Address1,
                    Address2         = matchedVenue.Address2,
                    Town             = matchedVenue.Town,
                    County           = matchedVenue.County,
                    PostCode         = matchedVenue.PostCode,
                    Latitude         = matchedVenue.Latitude,
                    Longitude        = matchedVenue.Longitude,
                    Status           = matchedVenue.Status,
                    UpdatedBy        = updatedBy,
                    DateUpdated      = matchedVenue.DateUpdated,
                    VenueID          = matchedVenue.VenueID,
                    ProviderID       = matchedVenue.ProviderID,
                    ProvVenueID      = matchedVenue.ProvVenueID,
                    Email            = matchedVenue.Email,
                    Website          = matchedVenue.Website,
                    Telephone        = matchedVenue.Telephone,
                    CreatedBy        = matchedVenue.CreatedBy,
                    CreatedDate      = DateTime.Now,
                    LocationId       = matchedVenue.LocationId,
                    TribalLocationId = matchedVenue.TribalLocationId
                };
                await documentClient.UpsertDocumentAsync(collectionUri, editedVenue);
            }

            IList <Venue> GetAllOldVenues()
            {
                var list = new List <Venue>();
                var lookupFileResourceName = "Dfc.ProviderPortal.TribalExporter.2020-01-24_0325-venues-backup.json";

                using (var stream = typeof(VenueReferenceChecker).Assembly.GetManifestResourceStream(lookupFileResourceName))
                    using (StreamReader file = new StreamReader(stream))
                    {
                        using (JsonTextReader reader = new JsonTextReader(file))
                        {
                            while (reader.Read())
                            {
                                JArray o2 = (JArray)JToken.ReadFrom(reader);

                                foreach (var item in o2)
                                {
                                    Venue v = item.ToObject <Venue>();
                                    list.Add(v);
                                }
                            }
                        }
                    }
                return(list);
            }
        }
        public static async Task Run(
            string input,          // Work around https://github.com/Azure/azure-functions-vs-build-sdk/issues/168
            ILogger log,
            [Inject] IConfigurationRoot configuration,
            [Inject] IVenueCollectionService venueCollectionService,
            [Inject] IProviderCollectionService providerCollectionService,
            [Inject] ICosmosDbHelper cosmosDbHelper,
            [Inject] IBlobStorageHelper blobhelper
            )
        {
            var venuesCollectionId = configuration["CosmosDbCollectionSettings:VenuesCollectionId"];
            var connectionString   = configuration.GetConnectionString("TribalRestore");
            var blobContainer      = configuration["BlobStorageSettings:Container"];
            var container          = configuration["BlobStorageSettings:Container"];
            var whiteListProviders = await GetProviderWhiteList();

            var coursesCollectionId        = "courses";
            var apprenticeshipCollectionId = "apprenticeship";
            var databaseId                  = configuration["CosmosDbSettings:DatabaseId"];
            var documentClient              = cosmosDbHelper.GetClient();
            var venueCollectionUri          = UriFactory.CreateDocumentCollectionUri(databaseId, venuesCollectionId);
            var coursesCollectionUri        = UriFactory.CreateDocumentCollectionUri(databaseId, coursesCollectionId);
            var apprenticeshipCollectionUri = UriFactory.CreateDocumentCollectionUri(databaseId, apprenticeshipCollectionId);
            var logFileName                 = $"ProvidersWithInvalidVenueReferences--{DateTime.Now.ToString("dd-MM-yy HHmm")}";

            var          result          = new List <VenueReference>();
            const string WHITE_LIST_FILE = "ProviderWhiteList.txt";

            //counters
            var ukprnsThatFailedToFetchVenues = 0;
            var uniqueInvalidVenues           = new HashSet <string>();
            var replacedInvalidVenues         = new HashSet <string>();

            var allVenues = GetAllOldVenues();
            //grand totals
            var totalInvalidApprenticeshipLocationReferences = 0;
            var totalInvalidCourseRunReferences = 0;

            //provider scoped totals
            var invalidCourseRunReferences = 0;
            var invalidApprenticeshipLocationReferences = 0;

            using (var logStream = new MemoryStream())
                using (var logStreamWriter = new StreamWriter(logStream))
                    using (var logCsvWriter = new CsvWriter(logStreamWriter, CultureInfo.InvariantCulture))
                    {
                        //every provider
                        foreach (var ukprn in whiteListProviders)
                        {
                            try
                            {
                                //reset counters
                                invalidCourseRunReferences = 0;
                                invalidApprenticeshipLocationReferences = 0;

                                //fetch data for ukprn
                                var allCoursesForProvider = await GetCourses(ukprn);

                                var allApprenticeshipsForProvider = await GetApprenticeships(ukprn);

                                //courses
                                foreach (var course in allCoursesForProvider)
                                {
                                    //course runs
                                    foreach (var courserun in course.CourseRuns)
                                    {
                                        //only courses that references a venue (classroom based or both)
                                        if (courserun.VenueId != null && courserun.VenueId != Guid.Empty)
                                        {
                                            //current venue & old venue (pre migration)
                                            var currentVenue = await GetVenueById(courserun.VenueId?.ToString());

                                            if (currentVenue != null)
                                            {
                                                if (course.ProviderUKPRN != currentVenue.UKPRN)
                                                {
                                                    uniqueInvalidVenues.Add(courserun.VenueId.ToString());
                                                    invalidCourseRunReferences++;
                                                }

                                                result.Add(new VenueReference()
                                                {
                                                    UKPRN        = course.ProviderUKPRN,
                                                    VenueId      = courserun.VenueId.ToString(),
                                                    VenueUKPRN   = currentVenue.UKPRN,
                                                    Address1     = currentVenue.Address1,
                                                    Postcode     = currentVenue.PostCode,
                                                    VenueName    = currentVenue.VenueName,
                                                    UKPRNMatched = (course.ProviderUKPRN == currentVenue.UKPRN),
                                                    Message      = (course.ProviderUKPRN == currentVenue.UKPRN) ? "Venue UKPRN Matches Course UKPRN" : "Venue UKPRN Does not match Course UKPRN",
                                                    Type         = "Course",
                                                    CourseId     = course.id,
                                                    CourseRunId  = courserun.id
                                                });
                                            }
                                            else
                                            {
                                                result.Add(new VenueReference()
                                                {
                                                    UKPRN        = course.ProviderUKPRN,
                                                    UKPRNMatched = false,
                                                    VenueUKPRN   = -1,
                                                    VenueId      = courserun.VenueId.ToString(),
                                                    Message      = "VenueId does not exist in venues",
                                                    Type         = "Course",
                                                    CourseId     = course.id,
                                                    CourseRunId  = courserun.id
                                                });
                                            }
                                        }
                                    }
                                    //total for all providers
                                    totalInvalidCourseRunReferences += invalidCourseRunReferences;
                                }

                                //apprenticeships
                                foreach (var apprenticeship in allApprenticeshipsForProvider)
                                {
                                    //apprenticeship locations
                                    foreach (var location in apprenticeship.ApprenticeshipLocations)
                                    {
                                        //only apprenticeshiplocations that references a venue (classroom based or both)
                                        if (location.VenueId.HasValue && location.LocationGuidId.HasValue && location.LocationGuidId != Guid.Empty)
                                        {
                                            var currentVenue = await GetVenueById(location.LocationGuidId.ToString());

                                            //venue exists in cosmos
                                            if (currentVenue != null)
                                            {
                                                if (location.ProviderUKPRN != currentVenue.UKPRN)
                                                {
                                                    uniqueInvalidVenues.Add(location.VenueId.ToString());
                                                    invalidApprenticeshipLocationReferences++;
                                                }

                                                //apprenticeshipId
                                                result.Add(new VenueReference()
                                                {
                                                    UKPRN = apprenticeship.ProviderUKPRN,
                                                    ApprenticeshipLocationUKPRN = location.ProviderUKPRN,
                                                    VenueId          = location.LocationGuidId.ToString(),
                                                    VenueUKPRN       = currentVenue.UKPRN,
                                                    Address1         = currentVenue.Address1,
                                                    Postcode         = currentVenue.PostCode,
                                                    VenueName        = currentVenue.VenueName,
                                                    UKPRNMatched     = (apprenticeship.ProviderUKPRN == currentVenue.UKPRN),
                                                    Message          = (apprenticeship.ProviderUKPRN == currentVenue.UKPRN) ? "Venue UKPRN Matches Apprenticeship UKPRN" : "Venue UKPRN Does not match Apprenticeship UKPRN",
                                                    Type             = "Apprenticeship",
                                                    ApprenticeshipId = apprenticeship.id
                                                });
                                            }
                                            else
                                            {
                                                result.Add(new VenueReference()
                                                {
                                                    UKPRN = apprenticeship.ProviderUKPRN,
                                                    ApprenticeshipLocationUKPRN = location.ProviderUKPRN,
                                                    UKPRNMatched     = false,
                                                    VenueUKPRN       = -1,
                                                    VenueId          = location.LocationGuidId.ToString(),
                                                    Type             = "Apprenticeship",
                                                    Message          = "VenueId does not exist in venues",
                                                    ApprenticeshipId = apprenticeship.id
                                                });
                                            }
                                        }
                                    }
                                    totalInvalidApprenticeshipLocationReferences += invalidApprenticeshipLocationReferences;
                                }

                                //total for provider
                                Console.WriteLine($"{invalidCourseRunReferences} invalid venue references for {ukprn}");
                                Console.WriteLine($"{invalidApprenticeshipLocationReferences} invalid apprenticeship references for {ukprn}");
                                Console.WriteLine($"{uniqueInvalidVenues.Count()} unique venues");
                            }
                            catch (Exception e)
                            {
                                log.LogError(e.Message, e);
                            }
                        }

                        //block to try and fetch all venues for every provider
                        //to make sure that venues can be fetched without error.
                        foreach (var ukprn in whiteListProviders)
                        {
                            try
                            {
                                var venues = await GetVenues(ukprn);
                            }
                            catch (Exception e)
                            {
                                log.LogError(e.Message, e);
                                Console.WriteLine($"{ukprn} - failed to fetch venues");
                                ukprnsThatFailedToFetchVenues++;
                            }
                        }

                        //write venue reference documents
                        logCsvWriter.WriteHeader(typeof(VenueReference));
                        logCsvWriter.NextRecord();
                        foreach (var id in result)
                        {
                            logCsvWriter.WriteRecord(id);
                            logCsvWriter.NextRecord();
                        }

                        // Upload log CSV to blob storage
                        {
                            logStreamWriter.Flush();

                            logStream.Seek(0L, SeekOrigin.Begin);

                            var blob = blobhelper.GetBlobContainer(blobContainer).GetBlockBlobReference(logFileName);
                            await blob.UploadFromStreamAsync(logStream);
                        }
                    }

            //
            Console.WriteLine($"{totalInvalidCourseRunReferences} courserun invalid references in total");
            Console.WriteLine($"{totalInvalidApprenticeshipLocationReferences} apprenticeship location invalid references in total");
            Console.WriteLine($"{replacedInvalidVenues.Count()} venues have been reverted back to old venues");
            Console.WriteLine($"{uniqueInvalidVenues.Count()} Venues were invalid");
            Console.WriteLine($"{ukprnsThatFailedToFetchVenues} ukprns failed to fetch venues");

            async Task <List <Course> > GetCourses(int ukprn)
            {
                var    courses      = new List <Course>();
                string continuation = null;

                do
                {
                    var feedOptions = new FeedOptions()
                    {
                        RequestContinuation = continuation,
                        PartitionKey        = new Microsoft.Azure.Documents.PartitionKey(ukprn)
                    };

                    var queryResponse = await documentClient.CreateDocumentQuery <Course>(coursesCollectionUri, feedOptions)
                                        .Where(p => p.ProviderUKPRN == ukprn && p.CourseStatus != CourseDirectory.Models.Enums.RecordStatus.Archived)
                                        .AsDocumentQuery()
                                        .ExecuteNextAsync <Course>();

                    courses.AddRange(queryResponse.ToList());
                    continuation = queryResponse.ResponseContinuation;
                }while (continuation != null);
                return(courses);
            }

            async Task <List <Venue> > GetVenues(int ukprn)
            {
                var    venues       = new List <Venue>();
                string continuation = null;

                do
                {
                    var feedOptions = new FeedOptions()
                    {
                        RequestContinuation = continuation
                    };

                    var queryResponse = await documentClient.CreateDocumentQuery <Venue>(venueCollectionUri, feedOptions)
                                        .Where(p => p.UKPRN == ukprn)
                                        .AsDocumentQuery()
                                        .ExecuteNextAsync <Venue>();

                    venues.AddRange(queryResponse.ToList());

                    continuation = queryResponse.ResponseContinuation;
                }while (continuation != null);
                return(venues);
            }

            async Task <Venue> GetVenueById(string id)
            {
                var collectionLink = UriFactory.CreateDocumentCollectionUri(databaseId, venuesCollectionId);
                var query          = documentClient
                                     .CreateDocumentQuery <Venue>(collectionLink, new FeedOptions()
                {
                    EnableCrossPartitionQuery = true
                })
                                     .Where(d => d.ID == id)
                                     .AsDocumentQuery();

                return((await query.ExecuteNextAsync()).FirstOrDefault());
            }

            async Task <List <Apprenticeship> > GetApprenticeships(int ukprn)
            {
                var    apprenticeships = new List <Apprenticeship>();
                string continuation    = null;

                do
                {
                    var feedOptions = new FeedOptions()
                    {
                        RequestContinuation = continuation,
                        PartitionKey        = new Microsoft.Azure.Documents.PartitionKey(ukprn)
                    };

                    try
                    {
                        var queryResponse = await documentClient.CreateDocumentQuery <Apprenticeship>(apprenticeshipCollectionUri, feedOptions)
                                            .Where(p => p.ProviderUKPRN == ukprn && p.RecordStatus != CourseDirectory.Models.Enums.RecordStatus.Archived)
                                            .AsDocumentQuery()
                                            .ExecuteNextAsync <Apprenticeship>();

                        apprenticeships.AddRange(queryResponse);
                        continuation = queryResponse.ResponseContinuation;
                    }
                    catch (Exception)
                    {
                        continuation = null;
                    }
                }while (continuation != null);

                return(apprenticeships);
            }

            async Task <ISet <int> > GetProviderWhiteList()
            {
                var blob = blobhelper.GetBlobContainer(blobContainer).GetBlockBlobReference(WHITE_LIST_FILE);

                var ms = new MemoryStream();
                await blob.DownloadToStreamAsync(ms);

                ms.Seek(0L, SeekOrigin.Begin);

                var    results = new HashSet <int>();
                string line;

                using (var reader = new StreamReader(ms))
                {
                    while ((line = reader.ReadLine()) != null)
                    {
                        if (string.IsNullOrEmpty(line))
                        {
                            continue;
                        }

                        var ukprn = int.Parse(line);
                        results.Add(ukprn);
                    }
                }
                return(results);
            }

            IList <Venue> GetAllOldVenues()
            {
                var list = new List <Venue>();
                var lookupFileResourceName = "Dfc.ProviderPortal.TribalExporter.2020-01-24_0325-venues-backup.json";

                using (var stream = typeof(VenueReferenceChecker).Assembly.GetManifestResourceStream(lookupFileResourceName))
                    using (StreamReader file = new StreamReader(stream))
                    {
                        using (JsonTextReader reader = new JsonTextReader(file))
                        {
                            while (reader.Read())
                            {
                                JArray o2 = (JArray)JToken.ReadFrom(reader);

                                foreach (var item in o2)
                                {
                                    Venue v = item.ToObject <Venue>();
                                    list.Add(v);
                                }
                            }
                        }
                    }
                return(list);
            }
        }
        public static async Task Run(
            string input,          // Work around https://github.com/Azure/azure-functions-vs-build-sdk/issues/168
            ILogger log,
            [Inject] IConfigurationRoot configuration,
            [Inject] IProviderCollectionService providerCollectionService,
            [Inject] ICosmosDbHelper cosmosDbHelper,
            [Inject] IBlobStorageHelper blobhelper,
            [Inject] IApprenticeReferenceDataService apprenticeReferenceDataService,
            [Inject] IApprenticeshipServiceWrapper apprenticeshipService,
            [Inject] IVenueCollectionService venueCollectionService,
            [Inject] IOnspdService onspdService

            )
        {
            var apprenticeshipCollectionId = configuration["CosmosDbCollectionSettings:ApprenticeshipCollectionId"];
            var connectionString           = configuration.GetConnectionString("TribalRestore");
            var blobContainer      = blobhelper.GetBlobContainer(configuration["BlobStorageSettings:Container"]);
            var whiteListProviders = await GetProviderWhiteList();

            var           result = new List <ApprenticeshipResultMessage>();
            var           venueExportFileName = $"ApprenticeshipExport-{DateTime.Now.ToString("dd-MM-yy HHmm")}";
            const string  WHITE_LIST_FILE     = "ProviderWhiteList-Apprenticeships.txt";
            var           ukprnCache          = new List <int>();
            var           databaseId          = configuration["CosmosDbSettings:DatabaseId"];
            var           apprenticeshipList  = new List <ApprenticeshipResult>();
            var           createdBy           = "ApprenticeshipMigrator";
            var           createdDate         = DateTime.Now;
            SemaphoreSlim semaphore           = new SemaphoreSlim(5);
            var           client = cosmosDbHelper.GetClient();

            var apprenticeshipSQL          = @"SELECT  a.ApprenticeshipId,
	                                           p.ProviderId,
	                                           a.FrameworkCode,
	                                           a.ProgType,
	                                           a.PathwayCode,
	                                           a.StandardCode,
	                                           a.[Version],
	                                           a.MarketingInformation,
	                                           a.[Url],
	                                           a.ContactEmail,
	                                           a.ContactTelephone,
	                                           a.ContactWebsite,
	                                           a.RecordStatusId,
	                                           a.CreatedByUserId,
	                                           a.CreatedDateTimeUtc,
	                                           p.Ukprn,
											   coalesce(s.StandardName,f.NasTitle) as [ApprenticeshipTitle],
                                               s.NotionalEndLevel
                                        FROM Apprenticeship a
                                        INNER JOIN Provider p on p.ProviderId = a.ProviderId
										LEFT JOIN [Standard] s on (s.StandardCode = a.StandardCode and s.Version = a.Version)
										LEFT jOIN [Framework] f on (f.FrameworkCode = a.FrameworkCode AND f.PathwayCode = a.PathwayCode AND f.ProgType = a.ProgType)
                                        WHERE a.recordStatusId=2
                                        ORDER BY ProviderId
                                        ";
            var apprenticeshipLocationsSQL = @"SELECT al.ApprenticeshipId,
	                                           al.ApprenticeshipLocationId,
	                                           l.LocationId,
	                                           a.AddressId,
	                                           a.AddressLine1,
	                                           a.AddressLine2,
	                                           a.County,
	                                           a.Postcode,
                                               a.Town,
	                                           a.Longitude,
	                                           a.Latitude,
	                                           l.Website,
	                                           l.Email,
	                                           als.CSV as DeliveryModeStr,
                                               l.Telephone,
	                                           l.LocationName,
	                                           p.ProviderId,
	                                           p.Ukprn,
                                               al.Radius
                                        FROM ApprenticeshipLocation al
                                        INNER JOIN Location l on l.LocationId = al.LocationId
                                        INNER JOIN Provider p on p.ProviderId = l.ProviderId
                                        INNER JOIN Address a ON a.AddressId = l.AddressId
                                        CROSS APPLY (SELECT STRING_AGG(DeliveryModeId,',') as CSV, 
					                                        aldm.ApprenticeshipLocationId
			                                         FROM ApprenticeshipLocationDeliveryMode aldm
		                                             WHERE ApprenticeshipLocationId = al.ApprenticeshipLocationId
			                                         GROUP BY aldm.ApprenticeshipLocationId
			                                         ) als
                                        WHERE al.RecordStatusId = 2 and 
                                              al.ApprenticeshipId = @ApprenticeshipId
                                        ORDER BY ApprenticeshipId,ApprenticeshipLocationId";

            try
            {
                using (var conn1 = new SqlConnection(connectionString))
                    using (var apprenticeshipscmd = conn1.CreateCommand())
                    {
                        await conn1.OpenAsync();

                        apprenticeshipscmd.CommandText = apprenticeshipSQL;

                        using (var apprenticeshipReader = apprenticeshipscmd.ExecuteReader())
                        {
                            while (await apprenticeshipReader.ReadAsync())
                            {
                                apprenticeshipList.Add(ApprenticeshipResult.FromDataReader(apprenticeshipReader));
                            }
                        }
                    }
            }
            catch (Exception e)
            {
                AddResultMessage(0, 0, "Failed", null, e.Message);
                log.LogError("Error occured Migrating Apprenticeships", e.Message);
            }

            await Task.WhenAll(apprenticeshipList.Select(async apprenticeship =>
            {
                var apprenticeshipErrors  = new List <string>(); //Errors Here cause apprenticeships to go into pending
                var apprenticeshipWarning = new List <string>(); //informational messages
                await semaphore.WaitAsync();
                try
                {
                    if (IsOnWhiteList(apprenticeship.UKPRN))
                    {
                        apprenticeshipErrors = new List <string>();
                        //get relevant info
                        var exisitingApprenticeship = await GetExistingApprenticeship(apprenticeship);
                        var referenceDataFramework  = await GetReferenceDataFramework(apprenticeship);
                        var referenceDataStandard   = await GetReferenceDataStandard(apprenticeship);
                        var locations      = await GetLocations(apprenticeship);
                        var cosmosVenues   = await GetCosmosVenues(locations);
                        var cosmosProvider = await GetProvider(apprenticeship);

                        //map objects for creating cosmos record
                        var locs           = MapLocations(locations, cosmosVenues);
                        var id             = exisitingApprenticeship?.id.ToString() ?? Guid.NewGuid().ToString();
                        var apprenticeType = MapApprenticeshipType(apprenticeship);

                        //check to see if framework code/standard code is valid
                        VerifiyIfStandardOrFramework(apprenticeship, referenceDataFramework, referenceDataStandard);

                        var mappedStatus         = MapApprenticeshipRecordStatus(locs);
                        var mappedApprenticeship = MapApprenticeship(locs,
                                                                     id,
                                                                     apprenticeship,
                                                                     apprenticeType,
                                                                     mappedStatus,
                                                                     referenceDataFramework?.Id.ToString(),
                                                                     referenceDataStandard?.id.ToString(),
                                                                     cosmosProvider?.id.ToString());

                        //insert record into cosmos
                        await CreateOrUpdateApprenticeshipRecord(mappedApprenticeship);

                        //log message to output
                        AddResultMessage(apprenticeship.ApprenticeshipID,
                                         apprenticeship.UKPRN,
                                         Enum.GetName(typeof(RecordStatus),
                                                      mappedApprenticeship.RecordStatus),
                                         mappedApprenticeship.ApprenticeshipTitle,
                                         string.Join("\n", apprenticeshipErrors),
                                         string.Join("\n", apprenticeshipWarning));
                    }
                    else
                    {
                        AddResultMessage(apprenticeship.ApprenticeshipID, apprenticeship.UKPRN, "Skipped", null, $"PRN {apprenticeship.UKPRN} not whitelisted");
                    }
                }
                catch (Exception e)
                {
                    AddResultMessage(apprenticeship.ApprenticeshipID, apprenticeship.UKPRN, "Failed", null, $"Exception occured creating record - {e.Message}");
                    log.LogError("Error occurred creating or updating apprenticeship record!", e);
                }
                finally
                {
                    semaphore.Release();
                }

                void VerifiyIfStandardOrFramework(ApprenticeshipResult tribalRecord, ReferenceDataFramework refDataFramework, ReferenceDateStandard refDataStandard)
                {
                    if (refDataFramework == null && refDataStandard == null)
                    {
                        apprenticeshipWarning.Add($"Standard/Framework code does not exist - framework code {tribalRecord.FrameworkCode}, pathway code: {tribalRecord.PathWayCode}, standard code: {tribalRecord.StandardCode}, version: {tribalRecord.Version}");
                    }
                }

                ApprenticeshipDTO MapApprenticeship(IList <ApprenticeshipLocationDTO> locs, string id, ApprenticeshipResult tribalRecord, ApprenticeshipType apprenticeshipTye,
                                                    RecordStatus recordStatus, string frameworkId, string standardId, string providerId)
                {
                    var cosmosApprenticeship = new ApprenticeshipDTO()
                    {
                        id = id,
                        ApprenticeshipId     = tribalRecord.ApprenticeshipID,
                        ApprenticeshipTitle  = tribalRecord.ApprenticeshipTitle,
                        ProviderId           = providerId,
                        PathWayCode          = tribalRecord.PathWayCode,
                        ProgType             = tribalRecord.ProgType,
                        ProviderUKPRN        = tribalRecord.UKPRN,
                        FrameworkId          = frameworkId,
                        StandardId           = standardId,
                        FrameworkCode        = tribalRecord.FrameworkCode,
                        StandardCode         = tribalRecord.StandardCode,
                        Version              = tribalRecord.Version,
                        MarketingInformation = tribalRecord.MarketingInformation,
                        Url = tribalRecord.Url,
                        ContactTelephone        = tribalRecord.ContactTelephone,
                        ContactEmail            = tribalRecord.ContactEmail,
                        ContactWebsite          = tribalRecord.ContactWebsite,
                        CreatedBy               = createdBy,
                        CreatedDate             = createdDate,
                        NotionalNVQLevelv2      = tribalRecord.NotionalEndLevel,
                        ApprenticeshipLocations = locs,
                        ApprenticeshipType      = apprenticeshipTye,
                        RecordStatus            = recordStatus
                    };
                    return(cosmosApprenticeship);
                }

                async Task <IList <Dfc.CourseDirectory.Models.Models.Venues.Venue> > GetCosmosVenues(IList <ApprenticeshipLocationResult> locations)
                {
                    IList <Dfc.CourseDirectory.Models.Models.Venues.Venue> lst = new List <Dfc.CourseDirectory.Models.Models.Venues.Venue>();
                    foreach (var s in locations)
                    {
                        var venue = await venueCollectionService.GetDocumentByLocationId(s.LocationId, s.UKPRN);
                        if (venue != null)
                        {
                            lst.Add(venue);
                        }
                    }
                    return(lst);
                }

                async Task <Provider> GetProvider(ApprenticeshipResult item)
                {
                    return(await providerCollectionService.GetDocumentByUkprn(item.UKPRN));
                }

                async Task <List <ApprenticeshipLocationResult> > GetLocations(ApprenticeshipResult item)
                {
                    using (var sqlConnection = new SqlConnection(connectionString))
                    {
                        var lst = await sqlConnection.QueryAsync <ApprenticeshipLocationResult>(apprenticeshipLocationsSQL, new { apprenticeshipId = item.ApprenticeshipID }, commandType: CommandType.Text);
                        return(lst.ToList());
                    }
                }

                async Task <ReferenceDateStandard> GetReferenceDataStandard(ApprenticeshipResult item)
                {
                    var app = await apprenticeReferenceDataService.GetStandardById(item.StandardCode ?? 0, item.Version ?? 0);
                    return(app?.Value?.Value);
                }

                async Task <ReferenceDataFramework> GetReferenceDataFramework(ApprenticeshipResult item)
                {
                    //checks for framework apprenticeship
                    var app = await apprenticeReferenceDataService.GetFrameworkByCode(item.FrameworkCode ?? 0, item.ProgType ?? 0, item.PathWayCode ?? 0);
                    return(app?.Value?.Value);
                }

                async Task <Apprenticeship> GetExistingApprenticeship(ApprenticeshipResult item)
                {
                    //fetch existing apprenticeship row.
                    return(await apprenticeshipService.GetApprenticeshipByApprenticeshipID(item.ApprenticeshipID));
                }

                async Task CreateOrUpdateApprenticeshipRecord(ApprenticeshipDTO app)
                {
                    var s             = UriFactory.CreateDocumentUri(databaseId, apprenticeshipCollectionId, app.id);
                    Uri collectionUri = UriFactory.CreateDocumentCollectionUri(databaseId, apprenticeshipCollectionId);
                    var res           = await client.UpsertDocumentAsync(collectionUri, app);
                }

                RecordStatus MapApprenticeshipRecordStatus(IList <ApprenticeshipLocationDTO> mappedLocation)
                {
                    //if there are any errors with apprenticeshipREcord, set record to migration pending.
                    if (apprenticeshipErrors.Any())
                    {
                        return(RecordStatus.MigrationPending);
                    }
                    else
                    {
                        return(RecordStatus.Live);
                    }
                }

                //Taken entirely from previous migration logic.
                ApprenticeshipLocationType GetApprenticeshipLocationType(ApprenticeshipLocationResult lo)
                {
                    var deliveryModes = lo.DeliveryModes;
                    if ((deliveryModes.Contains(1) && !deliveryModes.Contains(2) && deliveryModes.Contains(3)) ||
                        (deliveryModes.Contains(1) && deliveryModes.Contains(2) && !deliveryModes.Contains(3)) ||
                        (deliveryModes.Contains(1) && deliveryModes.Contains(2) && deliveryModes.Contains(3)))
                    {
                        return(ApprenticeshipLocationType.ClassroomBasedAndEmployerBased);
                    }
                    else if ((!deliveryModes.Contains(1) && !deliveryModes.Contains(2) && deliveryModes.Contains(3)) ||
                             (!deliveryModes.Contains(1) && deliveryModes.Contains(2) && !deliveryModes.Contains(3)) ||
                             (!deliveryModes.Contains(1) && deliveryModes.Contains(2) && deliveryModes.Contains(3)))
                    {
                        return(ApprenticeshipLocationType.ClassroomBased);
                    }
                    else if (deliveryModes.Contains(1) && !deliveryModes.Contains(2) && !deliveryModes.Contains(3))
                    {
                        return(ApprenticeshipLocationType.EmployerBased);
                    }
                    else
                    {
                        return(ApprenticeshipLocationType.Undefined);
                    }
                }

                ApprenticeshipType MapApprenticeshipType(ApprenticeshipResult tribalRecord)
                {
                    if (tribalRecord.StandardCode.HasValue)
                    {
                        return(ApprenticeshipType.StandardCode);
                    }
                    else if (tribalRecord.FrameworkCode.HasValue)
                    {
                        return(ApprenticeshipType.FrameworkCode);
                    }
                    else
                    {
                        apprenticeshipWarning.Add($"ApprenticeshipId: {tribalRecord.ApprenticeshipID} has undefined apprenticeshipType");
                        return(ApprenticeshipType.Undefined);
                    }
                }

                IList <ApprenticeshipLocationDTO> MapLocations(IList <ApprenticeshipLocationResult> locations, IList <Dfc.CourseDirectory.Models.Models.Venues.Venue> venues)
                {
                    var locationBasedApprenticeshipLocation = new List <ApprenticeshipLocationDTO>();
                    var regionBasedApprenticeshipLocation   = new List <ApprenticeshipLocationDTO>();

                    //no need to proceed
                    if (locations == null)
                    {
                        return(null);
                    }


                    //employer based apprenticeships - group all locations into regions/subregions
                    foreach (var location in locations)
                    {
                        var type = GetApprenticeshipLocationType(location);
                        if (type == ApprenticeshipLocationType.EmployerBased)
                        {
                            var allRegionsWithSubRegions = new SelectRegionModel();
                            var onspdRegionSubregion     = onspdService.GetOnspdData(new OnspdSearchCriteria(location.Postcode));
                            if (onspdRegionSubregion.IsFailure)
                            {
                                apprenticeshipWarning.Add($"LocationId: {location.LocationId} - Querying onspd failed - {onspdRegionSubregion.Error}");
                                continue;
                            }
                            else if (!onspdRegionSubregion.HasValue)
                            {
                                apprenticeshipWarning.Add($"Location:{location.LocationId} - Did not find a record for postcode: {location.Postcode}");
                                continue;
                            }

                            var selectedSubRegion = allRegionsWithSubRegions.RegionItems.SelectMany(sr => sr.SubRegion.Where(sb =>
                                                                                                                             sb.SubRegionName == onspdRegionSubregion.Value.Value.LocalAuthority ||
                                                                                                                             sb.SubRegionName == onspdRegionSubregion.Value.Value.County ||
                                                                                                                             onspdRegionSubregion.Value.Value.LocalAuthority.Contains(sb.SubRegionName)
                                                                                                                             )).FirstOrDefault();

                            if (selectedSubRegion == null)
                            {
                                apprenticeshipWarning.Add($"Location:{location.LocationId} Unable to match region with ons data api, location skipped");
                                continue;
                            }
                            else
                            {
                                var appLocation = new ApprenticeshipLocationDTO()
                                {
                                    Id            = Guid.NewGuid().ToString(),
                                    VenueId       = Guid.Empty.ToString(),
                                    TribalId      = location.ApprenticeshipLocationId,
                                    DeliveryModes = location.DeliveryModes,
                                    LocationId    = selectedSubRegion.ApiLocationId,
                                    Name          = location.LocationName,
                                    ProviderId    = location.ProviderId,
                                    ProviderUKPRN = location.UKPRN,
                                    Radius        = location.Radius,
                                    ApprenticeshipLocationType = type,
                                    LocationType   = LocationType.SubRegion,
                                    LocationGuidId = null,
                                    Regions        = new List <string> {
                                        selectedSubRegion.Id
                                    },
                                    RecordStatus = VenueStatus.Live,
                                    CreatedBy    = createdBy,
                                    CreatedDate  = createdDate,
                                    UpdatedBy    = createdBy,
                                    UpdatedDate  = createdDate
                                };

                                //region based apprenticeships
                                regionBasedApprenticeshipLocation.Add(appLocation);
                            }
                        }
                        else if (type == ApprenticeshipLocationType.ClassroomBased || type == ApprenticeshipLocationType.ClassroomBasedAndEmployerBased)
                        {
                            //venue based (location based apprenticeships)
                            var cosmosVenueItem = venues.FirstOrDefault(x => x.LocationId == location.LocationId);
                            var status          = default(VenueStatus);

                            //set status be that of what the venue status is, otherwise if venue is not found
                            //set status to pending.
                            if (cosmosVenueItem != null)
                            {
                                status = cosmosVenueItem.Status;
                            }
                            else
                            {
                                apprenticeshipWarning.Add($"LocationId: {location.LocationId} did not find a venue in cosmos, record marked as pending");
                                continue;
                            }

                            var appLocation = new ApprenticeshipLocationDTO()
                            {
                                Id       = Guid.NewGuid().ToString(),
                                VenueId  = Guid.Empty.ToString(),
                                TribalId = location.ApprenticeshipLocationId,
                                Address  = new Dfc.CourseDirectory.Models.Models.Apprenticeships.Address()
                                {
                                    Address1  = cosmosVenueItem?.Address1,
                                    Address2  = cosmosVenueItem?.Address2,
                                    County    = cosmosVenueItem?.County,
                                    Email     = cosmosVenueItem?.Email,
                                    Website   = cosmosVenueItem?.Website,
                                    Longitude = cosmosVenueItem?.Longitude,
                                    Latitude  = cosmosVenueItem?.Latitude,
                                    Postcode  = cosmosVenueItem?.PostCode,
                                    Town      = cosmosVenueItem?.Town,
                                    Phone     = cosmosVenueItem?.Telephone
                                },
                                DeliveryModes = location.DeliveryModes,
                                LocationId    = location.LocationId,
                                Name          = location.LocationName,
                                ProviderId    = location.ProviderId,
                                ProviderUKPRN = location.UKPRN,
                                Radius        = location.Radius,
                                ApprenticeshipLocationType = type,
                                LocationType   = LocationType.Venue,
                                LocationGuidId = cosmosVenueItem?.ID,
                                Regions        = null,
                                RecordStatus   = status,
                                CreatedBy      = createdBy,
                                CreatedDate    = createdDate,
                                UpdatedBy      = createdBy,
                                UpdatedDate    = createdDate
                            };
                            locationBasedApprenticeshipLocation.Add(appLocation);
                        }
                        else
                        {
                            apprenticeshipWarning.Add($"LocationId: {location.LocationId} skipped as type was unknown {type}");
                            continue;
                        }
                    }

                    //add a new location with all distinct regions.
                    if (regionBasedApprenticeshipLocation.Any(x => x.RecordStatus == VenueStatus.Live))
                    {
                        var regionLocation     = regionBasedApprenticeshipLocation.FirstOrDefault(x => x.RecordStatus == VenueStatus.Live);
                        regionLocation.Regions = regionBasedApprenticeshipLocation.Where(x => x.Regions != null).SelectMany(x => x.Regions).Distinct().ToList();
                        locationBasedApprenticeshipLocation.Add(regionLocation);
                    }

                    return(locationBasedApprenticeshipLocation);
                }
            }));

            //Log Results to blob storage
            var resultsObjBytes = GetResultAsByteArray(result);

            await WriteResultsToBlobStorage(resultsObjBytes);

            //log completion
            log.LogInformation("Migrating Apprenticeships Complete");


            async Task <IList <int> > GetProviderWhiteList()
            {
                var list      = new List <int>();
                var whiteList = await blobhelper.ReadFileAsync(blobContainer, WHITE_LIST_FILE);

                if (!string.IsNullOrEmpty(whiteList))
                {
                    var lines = whiteList.Split(new[] { Environment.NewLine }, StringSplitOptions.None);
                    foreach (string line in lines)
                    {
                        if (int.TryParse(line, out int id))
                        {
                            list.Add(id);
                        }
                    }
                }
                return(list);
            }

            async Task WriteResultsToBlobStorage(byte[] data)
            {
                await blobhelper.UploadFile(blobContainer, venueExportFileName, data);
            }

            void AddResultMessage(int apprenticeshipId, int ukprn, string status, string apprenticeshipTitle, string message = "", string warnings = "")
            {
                lock (result)
                {
                    var validateResult = new ApprenticeshipResultMessage()
                    {
                        ApprenticeshipID = apprenticeshipId, Status = status, Message = message, UKPRN = ukprn, ApprenticeshipTitle = apprenticeshipTitle, Warnings = warnings
                    };
                    result.Add(validateResult);
                }
            }

            byte[] GetResultAsByteArray(IList <ApprenticeshipResultMessage> ob)
            {
                using (var memoryStream = new System.IO.MemoryStream())
                {
                    using (var streamWriter = new System.IO.StreamWriter(memoryStream))
                        using (var csvWriter = new CsvWriter(streamWriter, CultureInfo.InvariantCulture))
                        {
                            csvWriter.WriteRecords <ApprenticeshipResultMessage>(ob);
                            csvWriter.Flush();
                        }

                    return(memoryStream.ToArray());
                }
            }

            bool IsOnWhiteList(int ukprn)
            {
                if (!whiteListProviders.Any(x => x == ukprn))
                {
                    return(false);
                }
                else
                {
                    return(true);
                }
            }
        }