// This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddApplicationInsightsTelemetry(Configuration);
            services.AddSingleton(Configuration);

            _logger.LogCritical("Logging from ConfigureServices.");
            services.Configure <CookiePolicyOptions>(options =>
            {
                // This lambda determines whether user consent for non-essential cookies is needed for a given request.
                options.CheckConsentNeeded    = context => true;
                options.MinimumSameSitePolicy = SameSiteMode.None;
            });

            services.AddSingleton <IConfiguration>(Configuration);
            services.AddTransient <IUkrlpWcfClientFactory, UkrlpWcfClientFactory>();
            services.Configure <CourseForComponentSettings>(Configuration.GetSection("AppUISettings:CourseForComponentSettings"));
            services.Configure <EntryRequirementsComponentSettings>(Configuration.GetSection("AppUISettings:EntryRequirementsComponentSettings"));
            services.Configure <WhatWillLearnComponentSettings>(Configuration.GetSection("AppUISettings:WhatWillLearnComponentSettings"));
            services.Configure <HowYouWillLearnComponentSettings>(Configuration.GetSection("AppUISettings:HowYouWillLearnComponentSettings"));
            services.Configure <WhatYouNeedComponentSettings>(Configuration.GetSection("AppUISettings:WhatYouNeedComponentSettings"));
            services.Configure <HowAssessedComponentSettings>(Configuration.GetSection("AppUISettings:HowAssessedComponentSettings"));
            services.Configure <WhereNextComponentSettings>(Configuration.GetSection("AppUISettings:WhereNextComponentSettings"));

            services.AddOptions();

            services.Configure <ProviderServiceSettings>(Configuration.GetSection(nameof(ProviderServiceSettings)));
            services.AddScoped <IProviderService, ProviderService>();

            services.AddTransient((provider) => new HttpClient());

            services.Configure <ApprenticeshipSettings>(Configuration.GetSection(nameof(ApprenticeshipSettings)));

            services.Configure <LarsSearchSettings>(Configuration.GetSection(nameof(LarsSearchSettings)));

            services.AddScoped <IPaginationHelper, PaginationHelper>();

            services.AddScoped <IVenueSearchHelper, VenueSearchHelper>();
            services.Configure <VenueServiceSettings>(Configuration.GetSection(nameof(VenueServiceSettings)));
            services.AddScoped <IVenueService, VenueService>();

            services.Configure <CourseServiceSettings>(Configuration.GetSection(nameof(CourseServiceSettings)));
            services.Configure <FindACourseServiceSettings>(Configuration.GetSection(nameof(FindACourseServiceSettings)));
            services.AddScoped <ICourseService, CourseService>();

            services.Configure <CourseTextServiceSettings>(Configuration.GetSection(nameof(CourseTextServiceSettings)));
            services.AddScoped <ICourseTextService, CourseTextService>();

            services.AddScoped <IUserHelper, UserHelper>();
            services.AddScoped <ICSVHelper, CSVHelper>();
            services.AddScoped <ICourseProvisionHelper, CourseProvisionHelper>();
            services.Configure <ApprenticeshipServiceSettings>(Configuration.GetSection(nameof(ApprenticeshipServiceSettings)));
            services.AddScoped <IApprenticeshipService, ApprenticeshipService>();

            services.AddScoped <IBulkUploadService, BulkUploadService>();
            services.AddScoped <IApprenticeshipBulkUploadService, ApprenticeshipBulkUploadService>();
            services.Configure <BlobStorageSettings>(Configuration.GetSection(nameof(BlobStorageSettings)));
            services.AddScoped <IBlobStorageService, BlobStorageService>();
            services.Configure <EnvironmentSettings>(Configuration.GetSection(nameof(EnvironmentSettings)));
            services.AddScoped <IEnvironmentHelper, EnvironmentHelper>();
            services.AddScoped <IApprenticeshipProvisionHelper, ApprenticeshipProvisionHelper>();

            services.AddSingleton <IBinaryStorageProvider, BlobStorageBinaryStorageProvider>();
            services.Configure <BlobStorageBinaryStorageProviderSettings>(Configuration.GetSection(nameof(BlobStorageBinaryStorageProviderSettings)));

            services.AddSingleton <QueueBackgroundWorkScheduler>();
            services.AddHostedService(sp => sp.GetRequiredService <QueueBackgroundWorkScheduler>());
            services.AddSingleton <IBackgroundWorkScheduler>(sp => sp.GetRequiredService <QueueBackgroundWorkScheduler>());

            services.AddCourseDirectory(_env, Configuration);

            var mvcBuilder = services
                             .AddMvc(options =>
            {
                options.Filters.Add(new RedirectOnMissingUKPRNActionFilter());
            })
                             .SetCompatibilityVersion(CompatibilityVersion.Version_3_0)
                             .AddSessionStateTempDataProvider();

#if DEBUG
            mvcBuilder.AddRazorRuntimeCompilation(options =>
            {
                // Fix auto reload on IIS when views in V2 project are changed
                // (see https://github.com/aspnet/Razor/issues/2426#issuecomment-420750249)
                var v2ProjectPath = Path.Combine(Directory.GetParent(Environment.CurrentDirectory).FullName, "Dfc.CourseDirectory.WebV2");
                options.FileProviders.Add(new PhysicalFileProvider(v2ProjectPath));
            });
#endif

            services.AddAuthorization(options =>
            {
                options.AddPolicy("Admin", policy => policy.RequireRole("Developer"));
                options.AddPolicy("ElevatedUserRole", policy => policy.RequireRole("Developer", "Helpdesk"));
                options.AddPolicy("SuperUser", policy => policy.RequireRole("Developer", "Helpdesk", "Provider Superuser"));
                options.AddPolicy("Helpdesk", policy => policy.RequireRole("Helpdesk"));
                options.AddPolicy("ProviderSuperUser", policy => policy.RequireRole("Provider Superuser"));
                options.AddPolicy("Provider", policy => policy.RequireRole("Provider User", "Provider Superuser"));

                options.AddPolicy(
                    "Apprenticeship",
                    policy => policy.AddRequirements(new ProviderTypeRequirement(Core.Models.ProviderType.Apprenticeships)));

                options.AddPolicy(
                    "Fe",
                    policy => policy.AddRequirements(new ProviderTypeRequirement(Core.Models.ProviderType.FE)));
            });

            if (_env.IsProduction())
            {
                services.AddStackExchangeRedisCache(options =>
                {
                    options.Configuration = Configuration.GetConnectionString("Redis");
                });
            }
            else
            {
#if DEBUG
                // Adds FileDistributedCache for persistent cached state, including session state, during development ONLY.
                services.AddSingleton <IDistributedCache>(_ => new FileDistributedCache(() => DateTimeOffset.UtcNow));
#else
                services.AddDistributedMemoryCache();
#endif
            }

            services.Configure <FormOptions>(x => x.ValueCountLimit = 2048);

            services.AddResponseCaching();
            services.AddSession(options =>
            {
                options.IdleTimeout         = TimeSpan.FromMinutes(40);
                options.Cookie.HttpOnly     = true;
                options.Cookie.SecurePolicy = CookieSecurePolicy.Always;
            });

            var dataProtectionBuilder = services.AddDataProtection();
            if (_env.IsProduction())
            {
                dataProtectionBuilder.PersistKeysToAzureBlobStorage(GetDataProtectionBlobToken());
            }

            //TODO
            //services.Configure<GoogleAnalyticsOptions>(options => Configuration.GetSection("GoogleAnalytics").Bind(options));


            // Register the background worker helper
            services.AddHostedService <QueuedHostedService>();
            services.AddSingleton <IBackgroundTaskQueue, BackgroundTaskQueue>();
            services.Configure <FormOptions>(x => x.ValueCountLimit = 10000);

            var dfeSettings = new DfeSignInSettings();
            Configuration.GetSection("DFESignInSettings").Bind(dfeSettings);
            services.AddDfeSignIn(dfeSettings);

            Uri GetDataProtectionBlobToken()
            {
                var cloudStorageAccount = new CloudStorageAccount(
                    new Microsoft.Azure.Storage.Auth.StorageCredentials(
                        Configuration["BlobStorageSettings:AccountName"],
                        Configuration["BlobStorageSettings:AccountKey"]),
                    useHttps: true);

                var blobClient = cloudStorageAccount.CreateCloudBlobClient();

                var container = blobClient.GetContainerReference(Configuration["DataProtection:ContainerName"]);

                var blob = container.GetBlockBlobReference(Configuration["DataProtection:BlobName"]);

                var sharedAccessPolicy = new SharedAccessBlobPolicy()
                {
                    SharedAccessExpiryTime = DateTime.UtcNow.AddYears(1),
                    Permissions            = SharedAccessBlobPermissions.Read | SharedAccessBlobPermissions.Write | SharedAccessBlobPermissions.Create
                };

                var sasToken = blob.GetSharedAccessSignature(sharedAccessPolicy);

                return(new Uri(blob.Uri + sasToken));
            }
        }
        public static void AddDfeSignIn(this IServiceCollection services, DfeSignInSettings settings)
        {
            var overallSessionTimeout = TimeSpan.FromMinutes(90);

            services.AddSingleton <DfeUserInfoHelper>();
            // N.B. We can't use a typed client here because the legacy bits override the HttpClient registration :-/
            services.AddHttpClient("DfeSignIn", client =>
            {
                client.BaseAddress = new Uri(settings.ApiBaseUri);
                client.DefaultRequestHeaders.Add("Authorization", $"Bearer {DfeUserInfoHelper.CreateApiToken(settings)}");
            });
            services.TryAddSingleton(settings);

            services
            .AddAuthentication(options =>
            {
                options.DefaultSignInScheme       = CookieAuthenticationDefaults.AuthenticationScheme;
                options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
                options.DefaultChallengeScheme    = OpenIdConnectDefaults.AuthenticationScheme;
            })
            .AddCookie(options =>
            {
                options.ExpireTimeSpan    = TimeSpan.FromMinutes(40);
                options.SlidingExpiration = true;
                options.LogoutPath        = "/auth/logout";

                options.Events.OnRedirectToAccessDenied = ctx =>
                {
                    ctx.Response.StatusCode = 403;
                    return(Task.CompletedTask);
                };
            })
            .AddOpenIdConnect(options =>
            {
                options.SignInScheme                  = CookieAuthenticationDefaults.AuthenticationScheme;
                options.MetadataAddress               = settings.MetadataAddress;
                options.RequireHttpsMetadata          = false;
                options.ClientId                      = settings.ClientId;
                options.ClientSecret                  = settings.ClientSecret;
                options.ResponseType                  = OpenIdConnectResponseType.Code;
                options.GetClaimsFromUserInfoEndpoint = true;

                options.Scope.Clear();
                options.Scope.Add("openid");
                options.Scope.Add("email");
                options.Scope.Add("profile");
                options.Scope.Add("organisationid");
                options.Scope.Add("offline_access");

                // When we expire the session, ensure user is prompted to sign in again at DfE Sign In
                options.MaxAge = overallSessionTimeout;

                options.SaveTokens             = true;
                options.CallbackPath           = settings.CallbackPath;
                options.SignedOutCallbackPath  = settings.SignedOutCallbackPath;
                options.SecurityTokenValidator = new JwtSecurityTokenHandler()
                {
                    InboundClaimTypeMap            = new Dictionary <string, string>(),
                    TokenLifetimeInMinutes         = 90,
                    SetDefaultTimesOnTokenCreation = true
                };
                options.ProtocolValidator = new OpenIdConnectProtocolValidator
                {
                    RequireSub             = true,
                    RequireStateValidation = false,
                    NonceLifetime          = TimeSpan.FromMinutes(60)
                };

                options.DisableTelemetry = true;
                options.Events           = new OpenIdConnectEvents
                {
                    // Sometimes, problems in the OIDC provider (such as session timeouts)
                    // Redirect the user to the /auth/cb endpoint. ASP.NET Core middleware interprets this by default
                    // as a successful authentication and throws in surprise when it doesn't find an authorization code.
                    // This override ensures that these cases redirect to the root.
                    OnMessageReceived = context =>
                    {
                        var isSpuriousAuthCbRequest =
                            context.Request.Path == options.CallbackPath &&
                            context.Request.Method == "GET" &&
                            !context.Request.Query.ContainsKey("code");

                        if (isSpuriousAuthCbRequest)
                        {
                            context.HandleResponse();
                            context.Response.Redirect("/");
                        }

                        return(Task.CompletedTask);
                    },

                    // Sometimes the auth flow fails. The most commonly observed causes for this are
                    // Cookie correlation failures, caused by obscure load balancing stuff.
                    // In these cases, rather than send user to a 500 page, prompt them to re-authenticate.
                    // This is derived from the recommended approach: https://github.com/aspnet/Security/issues/1165
                    OnRemoteFailure = ctx =>
                    {
                        ctx.HandleResponse();
                        return(Task.FromException(ctx.Failure));
                    },

                    OnTokenValidated = async ctx =>
                    {
                        ctx.Properties.IsPersistent = true;
                        ctx.Properties.ExpiresUtc   = DateTime.UtcNow.Add(overallSessionTimeout);

                        var userInfo = ClaimsPrincipalCurrentUserProvider.MapUserInfoFromPrincipal(ctx.Principal);

                        var signInContext = new SignInContext(ctx.Principal)
                        {
                            UserInfo = userInfo
                        };

                        var signInActions = ctx.HttpContext.RequestServices.GetServices <ISignInAction>();
                        foreach (var a in signInActions)
                        {
                            await a.OnUserSignedIn(signInContext);
                        }

                        ctx.Principal = ClaimsPrincipalCurrentUserProvider.GetPrincipalFromSignInContext(signInContext);

                        if (signInContext.Provider != null)
                        {
                            // For driving legacy views
                            ctx.HttpContext.Session.SetInt32("UKPRN", signInContext.Provider.Ukprn);
                        }
                    }
                };
            });
        }