using AspNetCore.Identity.MongoDbCore.Extensions;
using AspNetCore.Identity.MongoDbCore.Infrastructure;
using Insight.Infrastructure.Entities;
using Insight.Infrastructure.Services;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.HttpOverrides;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.IdentityModel.Tokens;
using Microsoft.Net.Http.Headers;
using MongoDB.Bson;
using MongoDB.Driver;
using MongoDB.Driver.Core.Configuration;
using System.Text;

namespace Insight.Infrastructure
{
    public static class ServiceExtensions
    {
        public static IServiceCollection AddDatabase(this IServiceCollection services, IConfiguration configuration, ILoggerFactory? loggerFactory = null)
        {
            var connectionString = configuration.GetValue<string?>(Appsettings.Database) ?? throw new Exception($"{Appsettings.Database} value not set (appsettings)");

            var settings = MongoClientSettings.FromUrl(new MongoUrl(connectionString));
            settings.ConnectTimeout = TimeSpan.FromSeconds(3);
            settings.IPv6 = false;

            if (loggerFactory is not null)
            {
                settings.LoggingSettings = new LoggingSettings(loggerFactory);
            }

            services.AddSingleton(new MongoClient(settings));
            services.AddSingleton<IMongoClient>(provider => provider.GetRequiredService<MongoClient>());
            return services.AddSingleton(provider => provider.GetRequiredService<MongoClient>().GetDatabase(Settings.Database));
        }

        public static IServiceCollection AddInfrastructureServices(this IServiceCollection services)
        {
            services.AddTransient<IdentityService>();
            services.AddTransient<AuthenticatorService>();
            services.AddTransient<AccountService>();
            services.AddTransient<CustomerService>();
            services.AddTransient<HostService>();
            services.AddTransient<AgentService>();
            services.AddTransient<InventoryService>();
            return services;
        }

        public static IServiceCollection AddIdentityServices(this IServiceCollection services, IConfiguration configuration)
        {
            var connectionString = configuration.GetValue<string?>(Appsettings.Database) ?? throw new Exception($"{Appsettings.Database} value not set (appsettings)");

            services.AddIdentity<InsightUser, InsightRole>(options =>
            {

            })
            .AddMongoDbStores<InsightUser, InsightRole, ObjectId>(connectionString, Settings.Database)
            .AddDefaultTokenProviders()
            .AddSignInManager();

            return services;
        }

        public static IServiceCollection AddTokenServices(this IServiceCollection services, IConfiguration configuration)
        {
            var options = new Models.TokenOptions(
                key: configuration.GetValue<string?>(Appsettings.JwtKey) ?? throw new Exception($"{Appsettings.JwtKey} value not set (appsettings)"),
                expires: configuration.GetValue<int?>(Appsettings.JwtExp) ?? throw new Exception($"{Appsettings.JwtExp} value not set (appsettings)"),
                audience: configuration.GetValue<Uri?>(Appsettings.JwtAudience) ?? throw new Exception($"{Appsettings.JwtAudience} value not set (appsettings)"),
                issuer: configuration.GetValue<Uri?>(Appsettings.JwtIssuer) ?? throw new Exception($"{Appsettings.JwtIssuer} value not set (appsettings)"));

            services.AddSingleton(options);
            services.AddTransient<TokenService>();

            return services;
        }

        public static IServiceCollection AddCustomAuthentication(this IServiceCollection services, IConfiguration configuration)
        {
            // REWRITE TO COOKIE ONLY FOR WEB

            services.AddAuthentication(options =>
            {
                options.DefaultScheme = "Custom";
                options.DefaultChallengeScheme = "Custom";
            })
            .AddCookie("Cookies", options =>
            {
                //options.Cookie.Domain = "insight.webmatic.de";
                options.Cookie.Name = "insight";
                options.LoginPath = "/account/login";
                options.LogoutPath = "/account/logout";
                options.ExpireTimeSpan = TimeSpan.FromHours(1);
                options.SlidingExpiration = true;

                options.Events.OnRedirectToLogin = options =>
                {
                    if (options.Request.Path.StartsWithSegments("/api") && options.Response.StatusCode == 200)
                        options.Response.StatusCode = 401;
                    else
                        options.Response.Redirect(options.RedirectUri);

                    return Task.CompletedTask;
                };
            })
            .AddJwtBearer("Bearer", options =>
            {
                options.RequireHttpsMetadata = false;
                options.SaveToken = true;

                options.TokenValidationParameters.ValidateActor = false;

                options.TokenValidationParameters.ValidAudience = configuration.GetSection("Jwt:Audience").Value;
                options.TokenValidationParameters.ValidateAudience = true;

                options.TokenValidationParameters.ValidIssuer = configuration.GetSection("Jwt:Issuer").Value;
                options.TokenValidationParameters.ValidateIssuer = true;

                options.TokenValidationParameters.IssuerSigningKey = new SymmetricSecurityKey(
                    Encoding.UTF8.GetBytes(configuration.GetSection("Jwt:Key").Value ?? throw new ArgumentNullException(nameof(TokenValidationParameters), "Jwt:Key"))
                );

                options.TokenValidationParameters.ValidateIssuerSigningKey = true;
                options.TokenValidationParameters.ValidateLifetime = true;
            })
            .AddPolicyScheme("Custom", "Custom", options =>
            {
                options.ForwardDefaultSelector = context =>
                {
                    string authorization = context.Request.Headers[HeaderNames.Authorization];

                    if (!string.IsNullOrEmpty(authorization) && authorization.StartsWith("Bearer ")) return "Bearer";

                    return "Cookies";
                };
            });

            return services;
        }

        public static IServiceCollection AddBearerAuthentication(this IServiceCollection services, IConfiguration configuration)
        {
            services.AddAuthentication(options =>
            {
                options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
                options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
                options.DefaultSignInScheme = JwtBearerDefaults.AuthenticationScheme;
            })
            .AddJwtBearer(options =>
            {
                options.RequireHttpsMetadata = false;
                options.SaveToken = true;

                options.TokenValidationParameters.ValidateActor = false;

                options.TokenValidationParameters.ValidAudience = configuration.GetValue<string?>(Appsettings.JwtAudience) ?? throw new Exception($"{Appsettings.JwtAudience} value not set (appsettings)");
                options.TokenValidationParameters.ValidateAudience = true;

                options.TokenValidationParameters.ValidIssuer = configuration.GetValue<string?>(Appsettings.JwtIssuer) ?? throw new Exception($"{Appsettings.JwtIssuer} value not set (appsettings)");
                options.TokenValidationParameters.ValidateIssuer = true;

                options.TokenValidationParameters.IssuerSigningKey = new SymmetricSecurityKey(
                    Encoding.UTF8.GetBytes(configuration.GetValue<string?>(Appsettings.JwtKey) ?? throw new Exception($"{Appsettings.JwtKey} value not set (appsettings)"))
                );

                options.TokenValidationParameters.ValidateIssuerSigningKey = true;
                options.TokenValidationParameters.ValidateLifetime = true;
            });

            return services;
        }

        public static IServiceCollection AddProxyServices(this IServiceCollection services, IConfiguration configuration)
        {
            // add before routing
            services.Configure<ForwardedHeadersOptions>(options =>
            {
                options.ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto;
            });

            return services;
        }

        public static IServiceCollection AddRoutingServices(this IServiceCollection services, IConfiguration configuration)
        {
            // add after proxy
            services.AddRouting(options =>
            {
                options.LowercaseUrls = true;
            });

            return services;
        }

        private static IServiceCollection AddIdentityServices2(this IServiceCollection services, IConfiguration configuration)
        {
            var identityOptions = new MongoDbIdentityConfiguration
            {
                MongoDbSettings = new MongoDbSettings
                {
                    ConnectionString = configuration.GetSection("ConnectionStrings:Mongo").Value,
                    DatabaseName = "insight"
                },
                IdentityOptionsAction = options =>
                {
                    options.User.RequireUniqueEmail = true;
                    options.User.AllowedUserNameCharacters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789@.-_";

                    options.Password.RequireDigit = false;
                    options.Password.RequiredLength = 8;
                    options.Password.RequireNonAlphanumeric = false;
                    options.Password.RequireUppercase = false;
                    options.Password.RequireLowercase = false;

                    options.SignIn.RequireConfirmedAccount = false;
                    options.SignIn.RequireConfirmedEmail = false;
                    options.SignIn.RequireConfirmedPhoneNumber = false;

                    options.Lockout.MaxFailedAccessAttempts = 5;
                    options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(5);
                }
            };

            services.ConfigureMongoDbIdentity<InsightUser, InsightRole, ObjectId>(identityOptions)
            .AddDefaultTokenProviders()
            .AddSignInManager<InsightUser>();

            return services;
        }

        private static IServiceCollection AddIdentityAuthentication(this IServiceCollection services, IConfiguration configuration)
        {
            services.AddAuthentication(options =>
            {
                //options.DefaultAuthenticateScheme = 
            });
            //cookieBuilder.ApplicationCookie = builder.AddApplicationCookie();
            //cookieBuilder.ExternalCookie = builder.AddExternalCookie();
            //cookieBuilder.TwoFactorRememberMeCookie = builder.AddTwoFactorRememberMeCookie();
            //cookieBuilder.TwoFactorUserIdCookie = builder.AddTwoFactorUserIdCookie();
            //.AddCookie(options =>
            //{
            //    options.
            //};
            //.AddIdentityCookies();
            //.AddCookie(options =>
            //{
            //    // Specify where to redirect un-authenticated users
            //    options.LoginPath = "/account/login";

            //    // Specify the name of the auth cookie.
            //    // ASP.NET picks a dumb name by default.
            //    options.Cookie.Name = "insight";
            //});

            return services;
        }
    }
}