using Marten;

namespace HiVakil.Api.WebFramework.Configurations;

public static class ServiceExtensions
{
    public static void AddIpRateLimit(this IServiceCollection services, IConfigurationRoot configuration)
    {

        //load general configuration from appsettings.json
        services.Configure<IpRateLimitOptions>(configuration.GetSection("IpRateLimiting"));

        //load ip rules from appsettings.json
        services.Configure<IpRateLimitPolicies>(configuration.GetSection("IpRateLimitPolicies"));

        // inject counter and rules stores
        //services.AddInMemoryRateLimiting();
        services.AddSingleton<IIpPolicyStore, DistributedCacheIpPolicyStore>();
        services.AddSingleton<IRateLimitCounterStore, DistributedCacheRateLimitCounterStore>();
        services.AddDistributedRateLimiting<AsyncKeyLockProcessingStrategy>();
        services.AddDistributedRateLimiting<RedisProcessingStrategy>();
        services.AddRedisRateLimiting();

        // configuration (resolvers, counter key builders)
        services.AddSingleton<IRateLimitConfiguration, RateLimitConfiguration>();
    }
    public static void AddCustomStackExchangeRedis(this IServiceCollection serviceCollection, SiteSettings siteSettings)
    {
        serviceCollection.AddStackExchangeRedisExtensions<NewtonsoftSerializer>(options =>
        {
            return new List<RedisConfiguration>
            {
                new()
                {
                    Hosts = new[]
                    {
                        new RedisHost
                        {
                            Port = siteSettings.MasterRedisConfiguration.Port,
                            Host = siteSettings.MasterRedisConfiguration.Host
                        }
                    },
                    Password = siteSettings.MasterRedisConfiguration.Password,
                    Ssl = false
                }
            };
        });
    }

    public static void AddCustomDbContext(this IServiceCollection serviceCollection, IConfigurationRoot Configuration)
    {
        serviceCollection.AddDbContextFactory<ApplicationContext>(options =>
        {
            options.UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking);
            options.UseNpgsql(Configuration.GetConnectionString("Postgres"), b => b.MigrationsAssembly("HiVakil.Repository"))
                .UseProjectAssembly(typeof(ApplicationUser).Assembly);
            
            //options.EnableServiceProviderCaching(true);
        }).BuildServiceProvider();
        AppContext.SetSwitch("Npgsql.EnableLegacyTimestampBehavior", true);
    }

    public static void AddMarten(this IServiceCollection serviceCollection, IConfigurationRoot configuration , IWebHostEnvironment environment)
    {
        serviceCollection.AddMarten(options =>
        {
            // Establish the connection string to your Marten database
            options.Connection(configuration.GetConnectionString("MartenDB")!);

            // If we're running in development mode, let Marten just take care
            // of all necessary schema building and patching behind the scenes
            if (environment.IsDevelopment())
            {
                options.AutoCreateSchemaObjects = AutoCreate.All;
            }
        });
    }

    public static void AddCustomResponseCompression(this IServiceCollection serviceCollection)
    {
        serviceCollection.Configure<GzipCompressionProviderOptions>(options =>
            options.Level = CompressionLevel.Fastest);
        serviceCollection.AddResponseCompression(options =>
        {
            options.Providers.Add<GzipCompressionProvider>();
            options.EnableForHttps = true;
        });
    }

    public static void AddCustomCores(this IServiceCollection serviceCollection)
    {
        serviceCollection.AddCors(options => options.AddPolicy("CorsPolicy",
            builder =>
            {
                builder.AllowAnyMethod()
                    .SetPreflightMaxAge(TimeSpan.FromHours(24))
                    .WithExposedHeaders("Access-control-allow-origins")
                    .AllowAnyHeader()
                    .SetIsOriginAllowed(_ => true)
                    .AllowCredentials();
            }));
    }

    public static void AddCustomController(this IServiceCollection serviceCollection)
    {
        serviceCollection.AddControllers(options => { options.Filters.Add(new AuthorizeFilter()); })
            .AddControllersAsServices()
            .AddNewtonsoftJson(options =>
                {
                    options.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
                    //options.SerializerSettings.ContractResolver = new DefaultContractResolver();
                }
            );
    }

    public static void AddCustomMvc(this IServiceCollection serviceCollection)
    {
        serviceCollection
            .AddMvc()
            .AddNewtonsoftJson(options =>
            {
                options.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
            });
    }

    public static void AddCustomAuthorization(this IServiceCollection serviceCollection)
    {
        serviceCollection.AddAuthorization();
    }

    public static void AddJwtCustomAuthentication(this IServiceCollection serviceCollection, JwtSettings jwtSettings)
    {
        serviceCollection.AddAuthentication(options =>
            {
                options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
                options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
            })
            .AddJwtBearer(options =>
            {
                var secretKey = Encoding.UTF8.GetBytes(jwtSettings.SecretKey);
                var validateParammetrs = new TokenValidationParameters
                {
                    ClockSkew = TimeSpan.Zero,
                    RequireSignedTokens = true,
                    ValidateIssuerSigningKey = true,
                    IssuerSigningKey = new SymmetricSecurityKey(secretKey),
                    RequireExpirationTime = true,
                    ValidateLifetime = true,
                    ValidateAudience = true,
                    ValidAudience = jwtSettings.Audience,
                    ValidateIssuer = true,
                    ValidIssuer = jwtSettings.Issuer
                };
                options.RequireHttpsMetadata = true;
                options.SaveToken = true;
                options.TokenValidationParameters = validateParammetrs;
                options.Events = new JwtBearerEvents
                {
                    OnMessageReceived = context =>
                    {
                        var accessToken = context.Request.Query["access_token"];

                        // If the request is for our hub...
                        var path = context.HttpContext.Request.Path;
                        if (!string.IsNullOrEmpty(accessToken))
                            // Read the token out of the query string
                            context.Token = accessToken.ToString();
                        return Task.CompletedTask;
                    }
                };
            });
    }

    public static void AddCustomIdentity(this IServiceCollection serviceCollection)
    {
        serviceCollection.AddIdentity<ApplicationUser, ApplicationRole>(options =>
            {
                options.Password.RequireLowercase = false;
                options.Password.RequireUppercase = false;
                options.Password.RequireDigit = false;
                options.Password.RequireNonAlphanumeric = false;
                options.User.RequireUniqueEmail = false;
            }).AddEntityFrameworkStores<ApplicationContext>()
            .AddDefaultTokenProviders()
            .AddErrorDescriber<PersianIdentityErrorDescriber>();
        ;
    }

    public static void AddCustomApiVersioning(this IServiceCollection serviceCollection)
    {
        serviceCollection.AddApiVersioning(options =>
        {
            options.AssumeDefaultVersionWhenUnspecified = true;
            options.DefaultApiVersion = new ApiVersion(1, 0);
            options.ApiVersionReader = new HeaderApiVersionReader("api-version");
            options.ReportApiVersions = true;
        });
    }
}