using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Npgsql;
using StackExchange.Redis;
using Tiku.Application.Security;
using Tiku.Domain.Identity;
using Tiku.Infrastructure.Auth;
using Tiku.Infrastructure.Observability;
using Tiku.Infrastructure.Persistence;
using Tiku.Infrastructure.Security;

namespace Tiku.Infrastructure;

public static class DependencyInjection
{
    public static IServiceCollection AddInfrastructure(
        this IServiceCollection services,
        string connectionString,
        Action<DbContextOptionsBuilder>? configureDatabase = null)
    {
        ArgumentException.ThrowIfNullOrWhiteSpace(connectionString);

        services.AddSingleton(_ => NpgsqlDataSource.Create(connectionString));
        services.AddScoped<TenantIsolationSaveChangesInterceptor>();
        services.AddSingleton<DatabasePerformanceInterceptor>();
        services.AddDbContext<TikuDbContext>((serviceProvider, options) =>
        {
            var dataSource = serviceProvider.GetRequiredService<NpgsqlDataSource>();
            options.UseNpgsql(dataSource, npgsql =>
                npgsql.MigrationsAssembly(typeof(TikuDbContext).Assembly.FullName));
            configureDatabase?.Invoke(options);
            options.AddInterceptors(
                serviceProvider.GetRequiredService<TenantIsolationSaveChangesInterceptor>(),
                serviceProvider.GetRequiredService<DatabasePerformanceInterceptor>());
        });
        services.AddIdentityCore<User>(options =>
            {
                options.Password.RequiredLength = 8;
                options.Password.RequireDigit = false;
                options.Password.RequireLowercase = false;
                options.Password.RequireUppercase = false;
                options.Password.RequireNonAlphanumeric = false;
                options.Lockout.MaxFailedAccessAttempts = 5;
                options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(15);
                options.User.RequireUniqueEmail = false;
            })
            .AddEntityFrameworkStores<TikuDbContext>()
            .AddSignInManager()
            .AddDefaultTokenProviders()
            .AddPasswordValidator<LetterAndDigitPasswordValidator<User>>();
        services.Configure<PasswordHasherOptions>(options => options.IterationCount = 210_000);
        services.AddPlatformCoreModule();
        services.AddAuthModule();
        services.AddContentModule();
        services.AddLearningModule();
        services.AddTenantAdminModule();
        services.AddCommerceModule();
        services.AddJobsModule();
        services.AddPlatformModule();

        return services;
    }

    public static IServiceCollection AddRedisSecurity(
        this IServiceCollection services,
        string connectionString,
        string environmentName)
    {
        ArgumentException.ThrowIfNullOrWhiteSpace(connectionString);
        var options = ConfigurationOptions.Parse(connectionString);
        options.AbortOnConnectFail = false;
        options.ClientName = $"tiku-{environmentName.ToLowerInvariant()}";
        services.AddSingleton<IConnectionMultiplexer>(_ => ConnectionMultiplexer.Connect(options));
        services.AddSingleton(provider => new RedisSecurityStore(
            provider.GetRequiredService<IConnectionMultiplexer>(),
            environmentName,
            provider.GetRequiredService<ILogger<RedisSecurityStore>>()));
        services.AddSingleton<IRedisSecurityStore>(provider => provider.GetRequiredService<RedisSecurityStore>());
        services.AddSingleton(provider => new RedisAuthorizationCache(
            provider.GetRequiredService<IConnectionMultiplexer>(),
            provider.GetRequiredService<IOptions<AuthorizationCacheOptions>>(),
            environmentName));
        services.AddSingleton<IAccessSecurityCache>(provider => provider.GetRequiredService<RedisAuthorizationCache>());
        services.AddSingleton<IAuthorizationSnapshotCache>(provider =>
            provider.GetRequiredService<RedisAuthorizationCache>());
        services.AddStackExchangeRedisCache(cache => cache.ConfigurationOptions = options);
        return services;
    }
}