using System;
using System.Collections.Generic;
using System.Globalization;
using System.IdentityModel.Tokens.Jwt;
using System.IO;
using System.Net;
using System.Net.Http;
using System.Security.Claims;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Threading;
using System.Threading.Tasks;
using Aeroflot.Common.ApiWrappers;
using Aeroflot.Common2.ApiWrapper.Aeroflot;
using Aeroflot.Common2.ApiWrapper.Aeroflot.FallbackService;
using Aeroflot.Common2.ApiWrapper.Aeroflot.Proxies;
using Aeroflot.Common2.DataAccess.S3;
using Aeroflot.Common2.Extensions;
using Aeroflot.Common2.Observability.AspNetCore;
using Aeroflot.Flights.BL;
using Aeroflot.Flights.BL.Helpers;
using Aeroflot.Flights.BL.Models;
using Aeroflot.Flights.BL.Models.FlightFilter;
using Aeroflot.Flights.DataSpace;
using Aeroflot.Flights.Interfaces.Models;
using Aeroflot.Flights.WebApi;
using Aeroflot.Flights.WebApi.Models;
using Aeroflot.Flights.WebApi.Models.Onlineboard.ApiV1;
using Aeroflot.Flights.WebApi.Providers;
//using Aeroflot.Flights.WebApi.SignalR;
using Aeroflot.Security2;
using Aeroflot.Security2.DataAccess;
using Aeroflot.Security2.DataSpace;
using Aeroflot.Security2.Entities;
using Aeroflot.Security2.Entities.DataSpace;
using Aeroflot.Security2.Services.EmailService;
using Amazon.Runtime;
using Amazon.S3;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.DataProtection;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.HttpOverrides;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.ModelBinding;
//using Microsoft.AspNetCore.SignalR;
using Microsoft.AspNetCore.SpaServices.AngularCli;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.IdentityModel.Protocols.OpenIdConnect;
using Microsoft.OpenApi.Models;
using Newtonsoft.Json.Serialization;
using Prometheus;
using Serilog;

namespace Aeroflot.Flights.Web
{
    public class Startup
    {
        private const string UserAgent = "Aeroflot.Flights / 1.0.001"; //"Aeroflot.Passbook / 1.7.013";
        //private IHubContext<FlightHub> hubContext;

        public Startup(IWebHostEnvironment env)
        {
            Environment = env;

            var builder = new ConfigurationBuilder()
                .SetBasePath(env.ContentRootPath)
                .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
                .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
                .AddEnvironmentVariables();
            Configuration = builder.Build();
        }

        public IWebHostEnvironment Environment { get; }

        public IConfiguration Configuration { get; }

        public class AuthMessageHandler : DelegatingHandler
        {
            public AuthMessageHandler()
            {
                InnerHandler = new HttpClientHandler();
            }

            protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
            {
                request.Headers.Add("User-Agent", UserAgent);
                return base.SendAsync(request, cancellationToken);
            }
        }

        public void ConfigureServices(IServiceCollection services)
        {
            var keysFolder = System.IO.Path.Combine(Environment.ContentRootPath, "App_Data");
            services.AddDataProtection()
                .SetApplicationName("Flights")
                .PersistKeysToFileSystem(new DirectoryInfo(keysFolder))
                .SetDefaultKeyLifetime(TimeSpan.FromDays(36500)) // 100 years
                .DisableAutomaticKeyGeneration();

            //IdentityModelEventSource.ShowPII = true;
            services.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>();

            services.AddSpaStaticFiles(config =>
            {
                config.RootPath = "ClientApp/dist";
            });

            //var apiAssembly = typeof(WebApi.Controllers.FlightsController).Assembly;
            services.AddMvc()
                .AddNewtonsoftJson(opt =>
                {
                    opt.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
                    opt.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore;
                    opt.SerializerSettings.NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore;
                    opt.SerializerSettings.Converters.Add(new Newtonsoft.Json.Converters.StringEnumConverter());
                    opt.AllowInputFormatterExceptionMessages = true;
                })
                .AddXmlSerializerFormatters()
                .AddXmlDataContractSerializerFormatters();
            services.AddSession();
            services.AddResponseCaching();
            services.AddControllers(options =>
                {
                    options.CacheProfiles.Add(
                        "DefaultBoard",
                        new CacheProfile()
                        {
                            Duration = int.TryParse(Configuration["Options:Cache:DefaultBoard"], out int cacheBoardSeconds) ? cacheBoardSeconds : 60,
                            VaryByQueryKeys = new[] { "*" },
                        });
                    options.CacheProfiles.Add(
                        "DefaultDictionary",
                        new CacheProfile()
                        {
                            Duration = int.TryParse(Configuration["Options:Cache:DefaultDictionary"], out int cacheDictionarySeconds) ? cacheDictionarySeconds : 3600,
                            VaryByQueryKeys = new[] { "*" },
                        });
                    options.CacheProfiles.Add(
                        "DefaultSchedule",
                        new CacheProfile()
                        {
                            Duration = int.TryParse(Configuration["Options:Cache:DefaultSchedule"], out int cacheScheduleSeconds) ? cacheScheduleSeconds : 600,
                            VaryByQueryKeys = new[] { "*" },
                        });
                    options.CacheProfiles.Add(
                        "DefaultDetails",
                        new CacheProfile()
                        {
                            Duration = int.TryParse(Configuration["Options:Cache:DefaultDetails"], out int cacheDetailsSeconds) ? cacheDetailsSeconds : 30,
                            VaryByQueryKeys = new[] { "*" },
                        });
                    options.CacheProfiles.Add(
                        "DefaultAirport",
                        new CacheProfile()
                        {
                            Duration = int.TryParse(Configuration["Options:Cache:DefaultAirport"], out int cacheAirportSeconds) ? cacheAirportSeconds : 600,
                            Location = ResponseCacheLocation.Any,
                            VaryByQueryKeys = new[] { "*" },
                        });
                    options.CacheProfiles.Add(
                        "Default1HourBasedOnQueryKeys",
                        new CacheProfile()
                        {
                            Duration = 3600,
                            VaryByQueryKeys = new[] { "*" },
                        });
                    options.CacheProfiles.Add(
                        "DefaultRequests",
                        new CacheProfile()
                        {
                            Duration = int.TryParse(Configuration["Options:Cache:DefaultRequest"], out int cacheRequestsSeconds) ? cacheRequestsSeconds : 600,
                            VaryByQueryKeys = new[] { "*" },
                        });
                })
                .AddXmlSerializerFormatters()
                .AddXmlDataContractSerializerFormatters();

            services.AddWebApi();

            services.AddMemoryCache();

            services.AddSwaggerGen(c =>
            {
                //c.TagActionsBy(tagSelector => tagSelector.);
                c.CustomSchemaIds(type => type.FullName.ToString());
                c.SwaggerDoc("v1", new OpenApiInfo { Title = "Aeroflot Flights", Version = "v1" });
                c.AddSecurityDefinition(
                    "Bearer",
                    new OpenApiSecurityScheme
                    {
                        In = ParameterLocation.Header,
                        Description = "Please enter JWT with Bearer into field",
                        Name = "Authorization",
                        Type = SecuritySchemeType.ApiKey,
                    });
                c.AddSecurityRequirement(new OpenApiSecurityRequirement()
                {
                    {
                        new OpenApiSecurityScheme
                        {
                            Reference = new OpenApiReference
                            {
                                Type = ReferenceType.SecurityScheme,
                                Id = "Bearer",
                            },
                            Scheme = "oauth2",
                            Name = "Bearer",
                            In = ParameterLocation.Header,
                        },
                        new List<string>()
                    },
                });
                c.DescribeAllParametersInCamelCase();
            });

            services.Configure<SwaggerBasicAuthConfig>(Configuration.GetSection("Swagger"));
            services.AddEndpointsApiExplorer();

            //ConfigureAuthService(services, Configuration);

            #region //ToDo: refactor this
            //var connectionString = Configuration.GetConnectionString("ConnectionString");
            //services.AddDbContext<FlightDbContext>(
            //    builder =>
            //        builder.EnableSensitiveDataLogging().UseSqlServer(connectionString, options => options.EnableRetryOnFailure()),
            //    ServiceLifetime.Transient);
            //services.AddDbContext<SecurityDbContext>(
            //    builder =>
            //        builder.UseSqlServer(connectionString, options => options.EnableRetryOnFailure()),
            //    ServiceLifetime.Transient);

            var scheduleAppUri = new Uri(Configuration["Api:ScheduleAppUri"]);
            var scheduleV2AppUri = new Uri(Configuration["Api:ScheduleV2AppUri"]);

            Func<string, Uri> uriAccessor = key =>
            {
                switch (key)
                {
                    case Common.ApiWrappers.Schedule.Schedule.AppUri:
                        return scheduleAppUri;
                    case Common.ApiWrappers.Schedule.Schedule.AppV2Uri:
                        return scheduleV2AppUri;
                    default:
                        throw new ArgumentOutOfRangeException();
                }
            };
            services.AddSingleton(uriAccessor);

            //services.AddSingleton(Options.Create<FlightsApiWrapperSettings>(new FlightsApiWrapperSettings { FlightsUri = new Uri(Configuration["Dictionary:FlightsApiAppUri"]) }));
            #endregion

            services.Configure<AflApiProviderOptions>(Configuration.GetSection("Options:RefreshableApiProvider"));
            services.Configure<ApiWrapperProxyOptions>(Configuration.GetSection("Options:ApiWrapperProxy"));

            services.Configure<DatesProviderOptions>(Configuration.GetSection("Options:DatesProviderOptions"));
            services.Configure<AeroflotApiWrapperOptions>(Configuration.GetSection("Options:AeroflotApiWrapper"));
            services.Configure<BL.Models.TransitionProviderOptions>(Configuration.GetSection("Options:TransitionProvider"));
            services.Configure<BL.Models.LegServiceOptions>(Configuration.GetSection("Options:LegService"));
            services.Configure<ConnectionsProviderOptions>(Configuration.GetSection("Options:ConnectionsProvider"));
            services.Configure<ValidationOptions>(Configuration.GetSection("Options:Validation"));
            services.Configure<WhiteListCarriersOptions>(Configuration.GetSection("Options:WhiteListCarriers"));

            Enum.TryParse<FallbackServiceType>(Configuration.GetSection("Options:AeroflotFallbackOptions:ServiceType").Value, true, out FallbackServiceType serviceType);

            var options = Configuration.GetAWSOptions("YandexAWS:AWS");
            options.Credentials = new BasicAWSCredentials(Configuration.GetSection("YandexAWS:AWS:AccessKey").Value, Configuration.GetSection("YandexAWS:AWS:SecretKey").Value);
            services.AddDefaultAWSOptions(options);
            services.AddAWSService<IAmazonS3>();
            services.AddSingleton<IS3BlobContainer<string>>(sp => ActivatorUtilities.CreateInstance<S3BlobContainer<string>>(sp, Configuration.GetSection("YandexAWS:Bucket").Value));
            services.AddSingleton<IS3BlobContainer<byte[]>>(sp => ActivatorUtilities.CreateInstance<S3BlobContainer<byte[]>>(sp, Configuration.GetSection("YandexAWS:Bucket").Value));

            services.AddOptions<FlightFilterProviderOptions>().Bind(Configuration.GetSection("Options:FlightFilter"));
            services.AddOptions<HeadersOptions>().Bind(Configuration.GetSection("HeadersOptions"));
            services.AddBlWeb(serviceType);

            //add dataspace
            services.AddFlightsClientGraphQL(Configuration["DataSpace:FlightsModelEndpoint"]);
            services.AddSecurity2ClientGraphQL(Configuration["DataSpace:FlightsModelEndpoint"]);

            //services.AddScoped<IdentityRepository, IdentityRepository>(); // (_ => new IdentityRepository((IAsyncGenericRepository)_.GetService(typeof(AsyncGenericRepository))));
            //services.AddScoped<IPasswordHasher<User>, PasswordHasher<User>>();
            //services.AddScoped<ILookupNormalizer, UpperInvariantLookupNormalizer>();
            //services.AddScoped<IdentityErrorDescriber, IdentityErrorDescriber>();
            //services.AddScoped<UserManager, UserManager>();
            services.AddScoped<IIdentityRepository, IdentityRepository>();
            services.AddScoped<ISearchFlightParamsBinder, SearchFlightParamsBinder>();

            services.AddAeroflotIdentityCore<User>(o =>
            {
                o.User.RequireUniqueEmail = true;
                o.User.AllowedUserNameCharacters = "абвгдеёжзийклмнопрстуфхцчшщъыьэюяАБВГДЕЁЖЗКИЙКЛМНОПРСТУФЧЦЧШЩЪЫЬЭЮЯ abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
                o.Lockout.MaxFailedAccessAttempts = 7;
            })
            .AddDefaultTokenProviders();

            services.AddDefaultPasswordValidators<User>();

            services.TryAddScoped<ISecurityStampValidator, SecurityStampValidator<Security2.Entities.User>>();

            var configEws = Configuration.GetSection("EwsServiceOptions");
            var ewsOptions = new EWSOptions()
            {
                AsmxUri = configEws.GetValue<string>("EwsAsmxUri"),
                Password = configEws.GetValue<string>("EwsPassword"),
                Username = configEws.GetValue<string>("EwsUserName"),
            };
            var configEmail = Configuration.GetSection("EmailOptions");
            services.AddEWSEmailService(new EmailOptions()
            {
                AppName = configEmail.GetValue<string>("AppName"),
                AppUrl = configEmail.GetValue<string>("AppUrl"),
                InvitationUrl = configEmail.GetValue<string>("InvitationUrl"),
                EWSOptions = ewsOptions,
            });
            services.TryAddScoped<IUserConfirmation<Security2.Entities.User>, DefaultUserConfirmation<Security2.Entities.User>>();
            services.TryAddScoped<SignInManager<Security2.Entities.User>>();

            services.Configure<FormOptions>(x =>
            {
                x.ValueLengthLimit = int.MaxValue;
                x.MultipartBodyLengthLimit = int.MaxValue;
            });

            services.AddAuthentication(options =>
                {
                    options.DefaultScheme = IdentityConstants.ApplicationScheme;
                    options.DefaultSignInScheme = IdentityConstants.ExternalScheme;
                    options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
                    options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
                })
                .AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, options =>
                {
                    options.Authority = Configuration["Identity:AuthorityUri"];
                    options.Audience = Configuration["Identity:Audience"];
                    options.RequireHttpsMetadata = false;
                    options.BackchannelHttpHandler = new AuthMessageHandler();
                })
                .AddIdentityCookies(o =>
                {
                    if (Environment.IsDevelopment())
                    {
                        o.ApplicationCookie.Configure(options =>
                        {
                            options.Cookie.HttpOnly = true;
                            options.Cookie.SameSite = SameSiteMode.None;
                            options.Cookie.SecurePolicy = CookieSecurePolicy.Always;
                        });
                        o.ExternalCookie.Configure(options =>
                        {
                            options.Cookie.HttpOnly = true;
                            options.Cookie.SameSite = SameSiteMode.None;
                            options.Cookie.SecurePolicy = CookieSecurePolicy.Always;
                        });
                    }
                });

            services.AddAuthorization(options =>
            {
                options.DefaultPolicy = new AuthorizationPolicyBuilder()
                  .RequireAuthenticatedUser()
                  .Build();

                options.AddPolicy("policyjwt", policy =>
                {
                    policy.AddAuthenticationSchemes(JwtBearerDefaults.AuthenticationScheme)
                        .RequireAuthenticatedUser();
                });

                options.AddPolicy("policycookie", policy =>
                {
                    policy.AddAuthenticationSchemes(IdentityConstants.ApplicationScheme)
                        .RequireAuthenticatedUser();
                });
            });

            services.AddHealthChecks();

            services.AddCors(options =>
            {
                options.AddPolicy(
                    "Aeroflot",
                    builder =>
                    {
                        builder
                            .WithOrigins("http://*.aeroflot.ru", "https://*.aeroflot.ru", "http://aeroflot.baccasoft.ru", "https://aeroflot.baccasoft.ru", "http://*.test.aeroflot.ru", "https://*.test.aeroflot.ru")
                            .AllowAnyMethod()
                            .AllowAnyHeader()
                            .SetIsOriginAllowedToAllowWildcardSubdomains();
                    });

                options.AddPolicy(
                    "AllowAnyHeader",
                    builder =>
                    {
                        builder
                            .AllowAnyOrigin()
                            .AllowAnyHeader()
                            .AllowAnyMethod();
                    });
            });

            //services.AddTransient<IFlightHubClient>(c => new FlightHubClient(hubContext));
            //services.AddSignalR();

            services.AddMvc()
                .AddRazorPagesOptions(options =>
                {
                    options.Conventions.AddPageRoute("/Robots", "/robots.txt");
                    options.Conventions.AddPageRoute("/Sitemap", "/sitemap.xml");
                });

            services.Configure<ForwardedHeadersOptions>(options =>
            {
                options.ForwardLimit = 3;

                foreach (var ip in Configuration.GetSection("IpSettings").Get<List<IpOptions>>())
                {
                    options.KnownNetworks.Add(new Microsoft.AspNetCore.HttpOverrides.IPNetwork(IPAddress.Parse(ip.Ip), ip.Mask));
                }

                options.ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto;
            });

            services.AddOpenTelemetryAspNetCore(Configuration["OpenTelemetry:ServiceName"], Configuration["OpenTelemetry:ServiceNamespace"], Configuration["OpenTelemetry:OtlpExporterEndpoint"]);

            services.Configure<Microsoft.AspNetCore.Http.Json.JsonOptions>(o =>
            {
                o.SerializerOptions.Converters.Add(new JsonStringEnumConverter());
                o.SerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;
                // DataSpace ids are 64 bit and need to be strings in Json
                // ints in Json has only about 53bit precision
                o.SerializerOptions.NumberHandling = JsonNumberHandling.WriteAsString | JsonNumberHandling.AllowReadingFromString;
                o.SerializerOptions.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull;
            });
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILoggerFactory logger)
        {
            app.UseForwardedHeaders();

            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
                app.UseBrowserLink();
            }

            app.UseHealthChecks("/healthy");

            app.UseResponseCaching();

            app.UseStaticFiles();

            //if (!env.IsDevelopment())
            //{
            //    app.UseSpaStaticFiles();
            //}
            app.UseSpaStaticFiles();

            app.Use(async (context, next) =>
            {
                context.Request.EnableBuffering();

                //var hubContext = context.RequestServices
                //        .GetRequiredService<IHubContext<FlightHub>>();

                //this.hubContext = hubContext;

                await next.Invoke();
            });

            app.UseHttpsRedirection();

            app.UseRouting();

            app.UseCors("Aeroflot");

            app.UseHttpMetrics();
            app.UseAuthentication();
            app.UseAuthorization();
            app.UseSession();

            app.UseSerilogRequestLogging();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapGet(
                    "api/v{version}/{lang}/airports", (
                        [FromServices] WebApi.Controllers.Api.AirportController controller,
                        HttpContext httpContext,
                        [FromRoute] double version,
                        [FromRoute] CaseInsensitive<Lang> lang) =>
                     controller.GetAirports(httpContext, version, lang.Value, "json")).Produces<WebApi.Models.Response<List<AirportModel>>>(StatusCodes.Status200OK)
                     .CacheOutput("DefaultAirport").WithTags("Airports").WithSummary("Get Airports");

                endpoints.MapGet(
                    "api/v{version}/{lang}/airports.{format}", (
                        [FromServices] WebApi.Controllers.Api.AirportController controller,
                        HttpContext httpContext,
                        [FromRoute] double version,
                        [FromRoute] CaseInsensitive<Lang> lang,
                        [FromRoute] string format) =>
                     controller.GetAirports(httpContext, version, lang.Value, format)).Produces<WebApi.Models.Response<List<AirportModel>>>(StatusCodes.Status200OK)
                     .CacheOutput("DefaultAirport").WithTags("Airports").WithSummary("Get Airports");

                endpoints.MapGet(
                    "api/v{version}/{lang}/airports/pairs", (
                        [FromServices] WebApi.Controllers.Api.AirportController controller,
                        HttpContext httpContext,
                        [FromRoute] double version,
                        [FromRoute] CaseInsensitive<Lang> lang) =>
                     controller.GetAirportPairs(httpContext, version, lang.Value)).Produces<WebApi.Models.Response<WrapperAirlineProfileRS>>(StatusCodes.Status200OK)
                     .CacheOutput("DefaultAirport").WithTags("Airports").WithSummary("Get Airports");

                endpoints.MapGet(
                    "/api/AppSettings",
                    ([FromServices] WebApi.Controllers.App.AppSettingsController controller) =>
                     controller.Get()).Produces<string>(StatusCodes.Status200OK)
                     .WithTags("AppSettings").WithSummary("Get App settings");

                endpoints.MapPost(
                    "/api/Meal/checkspecial",
                    ([FromServices] WebApi.Controllers.App.MealController controller,
                    [FromBody] MealApiRequestModel model) =>
                     controller.CheckMealAsync(model)).Produces<bool>(StatusCodes.Status200OK)
                     .WithTags("Meal").WithSummary("Get meal information");

                endpoints.MapGet(
                    "api/v{version}/{language}/delays/statistics", (
                        [FromServices] WebApi.Controllers.Api.DelaysController controller,
                        HttpContext httpContext,
                        ClaimsPrincipal user,
                        [FromRoute] double version,
                        [FromRoute] string language) =>
                     controller.GetDelayStatistics(httpContext, user, version, language)).Produces<string>(StatusCodes.Status200OK)
                     .WithTags("Delays").WithSummary("Get delays statistics");

                endpoints.MapGet(
                    "api/Healthcheck", (
                        [FromServices] WebApi.Controllers.App.HealthcheckController controller,
                        HttpContext httpContext,
                        ClaimsPrincipal user) =>
                     controller.Get()).Produces<string>(StatusCodes.Status200OK)
                     .WithTags("Healthcheck").WithSummary("Health check");

                endpoints.MapGet(
                    "api/Dictionary/{version}/{dictionaryName}", (
                        [FromServices] WebApi.Controllers.App.DictionaryController controller,
                        HttpContext httpContext,
                        ClaimsPrincipal user,
                        string dictionaryName,
                        string version) =>
                     controller.GetData(dictionaryName, version)).Produces<string>(StatusCodes.Status200OK)
                     .CacheOutput("DefaultDictionary").WithTags("Dictionary").WithSummary("Get dictionaries");

                endpoints.MapGet(
                    "api/Flights/{version}/{lang}/board", (
                        [FromServices] WebApi.Controllers.App.FlightsController controller,
                        HttpContext httpContext,
                        ClaimsPrincipal user,
                        [AsParameters] WebApi.Providers.SearchFlightParams searchParams,
                        string version,
                        string lang) =>
                     controller.GetOnlineBoard(searchParams, version, lang, httpContext))
                     .CacheOutput("DefaultBoard").WithTags("Flights").WithSummary("Get online board");

                endpoints.MapGet(
                    "api/Flights/{version}/{lang}/schedule", (
                        [FromServices] WebApi.Controllers.App.FlightsController controller,
                        HttpContext httpContext,
                        ClaimsPrincipal user,
                        [AsParameters] WebApi.Providers.SearchFlightParams searchParams,
                        string version,
                        string lang) =>
                     controller.GetSchedule(searchParams, version, lang, httpContext))
                     .CacheOutput("DefaultSchedule").WithTags("Flights").WithSummary("Get schedule");

                endpoints.MapGet(
                    "api/Flights/{version}/{lang}/schedule_pdf", (
                        [FromServices] WebApi.Controllers.App.FlightsController controller,
                        HttpContext httpContext,
                        ClaimsPrincipal user,
                        [AsParameters] WebApi.Providers.SearchFlightParams searchParams,
                        string version,
                        string lang) =>
                     controller.GetSchedulePdf(searchParams, version, lang, httpContext))
                     .CacheOutput("DefaultSchedule").WithTags("Flights").WithSummary("Get schedule PDF");

                endpoints.MapGet(
                    "api/Flights/{version}/{lang}/get_partner", (
                        [FromServices] WebApi.Controllers.App.FlightsController controller,
                        HttpContext httpContext,
                        ClaimsPrincipal user,
                        [AsParameters] WebApi.Providers.SearchFlightParams searchParams,
                        string version,
                        string lang) =>
                     controller.GetPartner(searchParams, version, lang))
                     .WithTags("Flights").WithSummary("Get partner (obsolete)");

                endpoints.MapGet(
                    "api/Flights/{version}/{lang}/actual", (
                        [FromServices] WebApi.Controllers.App.FlightsController controller,
                        HttpContext httpContext,
                        ClaimsPrincipal user,
                        [AsParameters] WebApi.Providers.SearchFlightParams searchParams,
                        string version,
                        string lang) =>
                     controller.GetActual(searchParams, version, lang))
                     .WithTags("Flights").WithSummary("Get actual (obsolete)");

                endpoints.MapGet(
                    "api/Flights/{version:regex(^(1|1.0|v1|v1.0)$)}/{lang}/detailed", (
                        [FromServices] WebApi.Controllers.App.FlightsController controller,
                        HttpContext httpContext,
                        ClaimsPrincipal user,
                        [AsParameters] WebApi.Providers.SearchFlightParams searchParams,
                        string version,
                        string lang) =>
                     controller.GetDetailed(searchParams, version, lang))
                     .CacheOutput("DefaultDetails").WithTags("Flights").WithSummary("Get detailed (obsolete)");

                endpoints.MapGet(
                    "api/Flights/{version:regex(^(1|1.0|v1|v1.0)$)}/{lang}/{requestType}/details", (
                        [FromServices] WebApi.Controllers.App.FlightsController controller,
                        HttpContext httpContext,
                        ClaimsPrincipal user,
                        [AsParameters] WebApi.Providers.SearchFlightParams searchParams,
                        string version,
                        string lang,
                        string requestType) =>
                     controller.GetDetails(searchParams, version, lang, requestType))
                     .CacheOutput("DefaultDetails").WithTags("Flights").WithSummary("Get details (obsolete)");

                endpoints.MapGet(
                    "api/Flights/v{majorVersion:int}.{minorVersion:int:min(1)}/{lang}/{requestType}/details", (
                        [FromServices] WebApi.Controllers.App.FlightsController controller,
                        HttpContext httpContext,
                        ClaimsPrincipal user,
                        //[FromQuery] string[] flights, -- does not work as it worked in controllers!
                        //[FromQuery] DateTime[] dates,
                        [FromQuery] string departure,
                        [FromQuery] string arrival,
                        [FromRoute] int majorVersion,
                        [FromRoute] int minorVersion,
                        [FromRoute] string lang,
                        [FromRoute] string requestType) =>
                    {
                        var flights = new List<string>();
                        var dates = new List<DateTime>();
                        for (int i = 0; ; i++) {
                            if (httpContext.Request.Query.ContainsKey("flights[" + i + "]") && httpContext.Request.Query.ContainsKey("dates[" + i + "]"))
                            {
                                if (DateTime.TryParseExact(httpContext.Request.Query["dates[" + i + "]"].ToString(), "yyyy-MM-ddTHH:mm:ss", CultureInfo.InvariantCulture, DateTimeStyles.None, out var dt)) {
                                    flights.Add(httpContext.Request.Query["flights[" + i + "]"]);
                                    dates.Add(dt);
                                }
                            }
                            else
                            {
                                break;
                            }
                        }

                        if (httpContext.Request.Query.ContainsKey("flights") && httpContext.Request.Query.ContainsKey("dates"))
                        {
                            if (DateTime.TryParseExact(httpContext.Request.Query["dates"].ToString(), "yyyy-MM-ddTHH:mm:ss", CultureInfo.InvariantCulture, DateTimeStyles.None, out var dt))
                            {
                                flights.Add(httpContext.Request.Query["flights"]);
                                dates.Add(dt);
                            }
                        }

                        return controller.GetDetails(flights.ToArray(), dates.ToArray(), departure, arrival, majorVersion, minorVersion, lang, requestType);
                    })
                        .CacheOutput("DefaultDetails").WithTags("Flights").WithSummary("Get details");

                //endpoints.MapPost(
                //    "api/Flights/updates", (
                //        [FromServices] WebApi.Controllers.App.FlightsController controller,
                //        HttpContext httpContext,
                //        ClaimsPrincipal user,
                //        [FromBody] List < FlightUpdateModel > flightsUpdate) =>
                //     controller.PostUpdateFlight(flightsUpdate))
                //     .WithTags("Flights").WithSummary("Post flight updates");

                endpoints.MapGet(
                    "api/Flights/{version:regex(^(1|1.0|v1|v1.0)$)}/{lang}/destinations", (
                        [FromServices] WebApi.Controllers.App.FlightsController controller,
                        HttpContext httpContext,
                        [AsParameters] WebApi.Providers.SearchFlightParams searchParams,
                        [FromRoute] string version,
                        [FromRoute] string lang) =>
                     controller.GetDestinations(searchParams, lang))
                     .CacheOutput("DefaultSchedule").WithTags("Flights").WithSummary("Get destinations");


                endpoints.MapGet(
                    "api/Flights/{version:regex(^(1|1.0|v1|v1.0)$)}/{lang}/days/{date}/{daysNum}/{type}/{request}/{requestType}", (
                        [FromServices] WebApi.Controllers.App.FlightsController controller,
                        HttpContext httpContext,
                        ClaimsPrincipal user,
                        [FromRoute] string version,
                        [FromRoute] string lang,
                        [FromRoute] DateTime date,
                        [FromRoute] int daysNum,
                        [FromRoute] string type,
                        [FromRoute] string request,
                        [FromRoute] string requestType) =>
                     controller.GetDays(version, lang, date, daysNum, type, request, requestType))
                     .CacheOutput("DefaultDetails").WithTags("Flights").WithSummary("Get flight days");

                endpoints.MapGet(
                    "api/{version:regex(^(v1|v1.0)$)}/{language}/flights", (
                        [FromServices] WebApi.Controllers.Api.V1.Onlineboard.FlightsController controller,
                        HttpContext httpContext,
                        [FromRoute] string language,
                        [FromRoute] string version) =>
                     controller.SearchFlightsXml(httpContext, version, language))
                     .WithTags("Flights V1").WithSummary("Search onlineboard flights in xml");

                endpoints.MapGet(
                    "api/{version:regex(^(v1|v1.0)$)}/{language}/flights.{format}", (
                        [FromServices] WebApi.Controllers.Api.V1.Onlineboard.FlightsController controller,
                        HttpContext httpContext,
                        [FromRoute] string language,
                        [FromRoute] string format,
                        [FromRoute] string version) =>
                     controller.SearchFlights(httpContext, version, language, format))
                     .WithTags("Flights V1").WithSummary("Search onlineboard flights in requested format");


                endpoints.MapGet(
                    "api/v{version}/{language}/flight", (
                        [FromServices] WebApi.Controllers.Api.V1.Schedule.FlightController controller,
                        HttpContext httpContext,
                        [AsParameters] WebApi.Models.Schedule.ApiV1.SearchFlightParams searchParams,
                        [FromRoute] string language,
                        [FromRoute] double version) =>
                     controller.GetFlightXml(httpContext, version, language, searchParams))
                     .WithTags("Flights V1").WithSummary("Search schedule flights in xml");

                endpoints.MapGet(
                    "api/v{version}/{language}/flight.{format}", (
                        [FromServices] WebApi.Controllers.Api.V1.Schedule.FlightController controller,
                        HttpContext httpContext,
                        [AsParameters] WebApi.Models.Schedule.ApiV1.SearchFlightParams searchParams,
                        [FromRoute] string language,
                        [FromRoute] string format,
                        [FromRoute] double version) =>
                     controller.GetFlight(httpContext, version, language, format, searchParams))
                     .WithTags("Flights V1").WithSummary("Search schedule flights in requested format");

                endpoints.MapGet(
                    "api/v{version}/{language}/aircraft", (
                        [FromServices] WebApi.Controllers.Api.FlightsController controller,
                        HttpContext httpContext,
                        [FromRoute] string language,
                        [FromRoute] double version) =>
                     controller.GetAircraft(httpContext, version, language, "json"))
                     .WithTags("Flights V2").WithSummary("Flight aircraft");

                endpoints.MapGet(
                    "api/v{version}/{language}/aircraft.{format}", (
                        [FromServices] WebApi.Controllers.Api.FlightsController controller,
                        HttpContext httpContext,
                        string format,
                        [FromRoute] string language,
                        [FromRoute] double version) =>
                     controller.GetAircraft(httpContext, version, language, format))
                     .WithTags("Flights V2").WithSummary("Flight aircraft");

                endpoints.MapGet(
                    "api/v{version}/{language}/dates", (
                        [FromServices] WebApi.Controllers.Api.FlightsController controller,
                        HttpContext httpContext,
                        [FromRoute] string language,
                        [FromRoute] double version) =>
                     controller.GetDates(httpContext, version, language, "json"))
                     .WithTags("Flights V2").WithSummary("Flight dates");

                endpoints.MapGet(
                    "api/v{version}/{language}/dates.{format}", (
                        [FromServices] WebApi.Controllers.Api.FlightsController controller,
                        HttpContext httpContext,
                        string format,
                        [FromRoute] string language,
                        [FromRoute] double version) =>
                     controller.GetDates(httpContext, version, language, format))
                     .WithTags("Flights V2").WithSummary("Flight dates");

                endpoints.MapGet(
                    "api/v{version:regex(^2)}/{lang}/flights",
                    [AllowAnonymous]
                    [Authorize(Policy = "policyjwt")]
                    [Authorize(Policy = "policycookie")]
                    (
                        [FromServices] WebApi.Controllers.Api.FlightsController controller,
                        HttpContext httpContext,
                        ClaimsPrincipal user,
                        [FromRoute] string lang,
                        [FromRoute] double version) =>
                     controller.GetFlights(httpContext, user, version, lang, "json"))
                     .WithTags("Flights V2").WithSummary("Search flights in xml");

                endpoints.MapGet(
                    "api/v{version:regex(^2)}/{lang}/flights.{format}",
                    [AllowAnonymous]
                    [Authorize(Policy = "policyjwt")]
                    [Authorize(Policy = "policycookie")]
                    (
                        [FromServices] WebApi.Controllers.Api.FlightsController controller,
                        HttpContext httpContext,
                        ClaimsPrincipal user,
                        [FromRoute] string lang,
                        [FromRoute] string format,
                        [FromRoute] double version) =>
                     controller.GetFlights(httpContext, user, version, lang, format))
                     .WithTags("Flights V2").WithSummary("Search flights in requested format");



                endpoints.MapGet(
                    "/api/Requests/{version}/GetPopular",
                    ([FromServices] WebApi.Controllers.App.RequestsController controller,
                    string version) =>
                     controller.GetPopular()).Produces<IEnumerable<IPopularRequest>>(StatusCodes.Status200OK)
                     .CacheOutput("Requests").WithTags("Requests").WithSummary("Get populer requests");

                endpoints.MapGet(
                    "/api/Version",
                    ([FromServices] WebApi.Controllers.App.VersionController controller) =>
                     controller.Get()).Produces<string>(StatusCodes.Status200OK)
                     .WithTags("Version").WithSummary("Get version");

                endpoints.MapControllerRoute(
                    name: "Flights",
                    pattern: "api/{version}/{**url}",
                    defaults: new { controller = "ExtFlights", action = "GetNotFound" },
                    constraints: new { version = @"v1(\.0)?", url = @".*/extflights.*" });


                endpoints.MapControllerRoute(
                    name: "Flights",
                    pattern: "api/{version}/{**url}",
                    defaults: new { controller = "Airport", action = "GetNotFound" },
                    constraints: new { url = @".*/airport.*", version = @"v1(\.(1|0))?" });

                endpoints.MapControllerRoute(
                    name: "Flights",
                    pattern: "{**url}",
                    defaults: new { controller = "Airport", action = "GetNotFound" },
                    constraints: new { url = @".*/airports.*" });

                endpoints.MapControllerRoute(
                    name: "Flights",
                    pattern: "api/{version}/{**url}",
                    defaults: new { controller = "Schedule", action = "GetNotFound" },
                    constraints: new { url = @".*/schedule.*", version = @"v1(\.0)?" });

                endpoints.Map("/{country}-{lang}/404", context =>
                {
                    // this is because it asked to return 404 (nobody cares that we are on SPA, and it should be handled by SSR + ng Universal)
                    context.Response.StatusCode = 404;
                    return Task.CompletedTask;
                });
                endpoints.Map("/{country}-{lang}/500", context =>
                {
                    // this is because it asked to return 500 (nobody cares that we are on SPA, and it should be handled by SSR + ng Universal)
                    context.Response.StatusCode = 500;
                    return Task.CompletedTask;
                });
                endpoints.MapControllers();
                endpoints.MapDefaultControllerRoute();
                endpoints.MapRazorPages();
                //endpoints.MapHub<FlightHub>("/flights");

                endpoints.MapMetrics(Configuration["Prometheus:Url"]);
            });

            if (!String.IsNullOrEmpty(Configuration["Swagger:Password"]))
            {
                app.UseSwaggerAuthorized();
                app.UseSwagger();
                app.UseSwaggerUI(c =>
                {
                    c.SwaggerEndpoint("/swagger/v1/swagger.json", "Flights");
                    c.EnableTryItOutByDefault();
                });
            }

            app.UseSpa(spa =>
            {
                spa.Options.SourcePath = "ClientApp";
                spa.Options.StartupTimeout = new TimeSpan(0, 5, 0);

                if (env.IsDevelopment() && !Configuration.GetValue<bool>("DOTNET_RUNNING_IN_CONTAINER"))
                {
                    spa.UseAngularCliServer(npmScript: "start");
                }
            });
        }

        private void ConfigureAuthService(IServiceCollection services, IConfiguration configuration)
        {
            var identityAuthority = configuration.GetSection("Identity").GetValue<string>("Authority");
            var identityClientId = configuration.GetSection("Identity").GetValue<string>("ClientId");
            var identityClientSecret = configuration.GetSection("Identity").GetValue<string>("ClientSecret");

            JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();

            services.AddAuthentication(options =>
            {
                options.DefaultScheme = "Cookies";
                options.DefaultChallengeScheme = "oidc";
            })
                .AddCookie()
                .AddOpenIdConnect("oidc", options =>
                {
                    options.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
                    options.Authority = identityAuthority;

                    options.ClientId = identityClientId;
                    options.ClientSecret = identityClientSecret;
                    options.ResponseType = OpenIdConnectResponseType.Code;
                    options.CallbackPath = "/admin/signin-oidc";

                    options.SaveTokens = true;
                    options.GetClaimsFromUserInfoEndpoint = true;
                    options.TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters() { ValidateIssuer = true };
                    options.Scope.Add("profile");
                    options.Scope.Add("openid");
                    options.Scope.Add("offline_access");

                    //options.Events = new OpenIdConnectEvents
                    //{
                    //     OnAuthorizationCodeReceived = async context =>
                    //     {
                    //         var code = context.ProtocolMessage.Code;
                    //         var identifier = context.Principal;
                    //         await Task.Yield();
                    //     },
                    //    OnMessageReceived = async ctxt =>
                    //    {
                    //        string bodyContent = new StreamReader(ctxt.Request.Body).ReadToEnd();
                    //        await Task.Yield();
                    //    },
                    //    OnTicketReceived = async ctxt =>
                    //    {
                    //        await Task.Yield();
                    //    },
                    //    OnTokenValidated = async ctx =>
                    //    {
                    //        var userID = ctx.Principal.FindFirstValue("sub");

                    //    },
                    //OnRemoteFailure = ctx => {
                    //        ctx.HandleResponse();
                    //        //ctx.Response.Redirect("~/Information");
                    //        return Task.FromResult(0);
                    //    }
                    //};
                });
        }

        public record CaseInsensitive<T>(T Value)
            where T : struct, Enum
        {
            public static bool TryParse(string? value, IFormatProvider? provider, out CaseInsensitive<T>? val)
            {
                if (Enum.TryParse<T>(value, ignoreCase: true, out var enumVal))
                {
                    val = new CaseInsensitive<T>(enumVal);
                    return true;
                }

                val = null;
                return false;
            }
        }
    }
}
