using System.Security.Cryptography;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.Tokens;
using SIGCM2.Application.Abstractions;
using SIGCM2.Application.Abstractions.Persistence;
using SIGCM2.Application.Abstractions.Security;
using SIGCM2.Application.Audit;
using SIGCM2.Application.Auth;
using SIGCM2.Application.Rubros;
using SIGCM2.Infrastructure.Http;
using SIGCM2.Infrastructure.Messaging;
using SIGCM2.Infrastructure.Persistence;
using SIGCM2.Infrastructure.Security;

namespace SIGCM2.Infrastructure;

public static class DependencyInjection
{
    public static IServiceCollection AddInfrastructure(
        this IServiceCollection services,
        IConfiguration configuration)
    {
        // Database
        var connectionString = configuration.GetConnectionString("SqlServer")
            ?? throw new InvalidOperationException("Missing ConnectionStrings:SqlServer");
        services.AddSingleton(new SqlConnectionFactory(connectionString));
        services.AddScoped<IUsuarioRepository, UsuarioRepository>();
        services.AddScoped<IRefreshTokenRepository, RefreshTokenRepository>();
        services.AddScoped<IRolRepository, RolRepository>();
        services.AddScoped<IPermisoRepository, PermisoRepository>();
        services.AddScoped<IRolPermisoRepository, RolPermisoRepository>();
        services.AddScoped<IMedioRepository, MedioRepository>();
        services.AddScoped<ISeccionRepository, SeccionRepository>();
        services.AddScoped<IPuntoDeVentaRepository, PuntoDeVentaRepository>();
        services.AddScoped<ITipoDeIvaRepository, TipoDeIvaRepository>();
        services.AddScoped<IIngresosBrutosRepository, IngresosBrutosRepository>();
        services.AddScoped<IRubroRepository, RubroRepository>();
        services.AddScoped<IProductTypeRepository, ProductTypeRepository>();
        services.AddScoped<IProductRepository, ProductRepository>();
        // PRD-002: replaces NullProductQueryRepository from Application DI
        services.AddScoped<IProductQueryRepository, ProductQueryRepository>();

        // JWT Options — bound lazily via IOptions so tests can override via ConfigureWebHost
        services.Configure<JwtOptions>(configuration.GetSection("Jwt"));
        // Also expose as JwtOptions directly for convenience (resolves via IOptions<JwtOptions>)
        services.AddSingleton<JwtOptions>(sp => sp.GetRequiredService<IOptions<JwtOptions>>().Value);

        // AuthOptions (Application layer) — populated from the same Jwt config section
        services.AddSingleton<AuthOptions>(sp =>
        {
            var opts = sp.GetRequiredService<JwtOptions>();
            return new AuthOptions
            {
                AccessTokenMinutes = opts.AccessTokenMinutes,
                RefreshTokenDays = opts.RefreshTokenDays,
            };
        });

        // RSA key pair — loaded lazily as singletons from the fully-resolved JwtOptions
        services.AddSingleton<RSA>(sp =>
        {
            var opts = sp.GetRequiredService<JwtOptions>();
            return RsaKeyLoader.LoadPrivateKey(opts);
        });

        services.AddSingleton<RsaSecurityKey>(sp =>
        {
            var opts = sp.GetRequiredService<JwtOptions>();
            return new RsaSecurityKey(RsaKeyLoader.LoadPublicKey(opts));
        });

        services.AddScoped<IJwtService>(sp =>
            new JwtService(
                sp.GetRequiredService<RSA>(),
                sp.GetRequiredService<JwtOptions>(),
                sp.GetRequiredService<TimeProvider>()));
        services.AddScoped<IPasswordHasher, BcryptPasswordHasher>();
        services.AddSingleton<IRefreshTokenGenerator, RefreshTokenGenerator>();
        services.AddHttpContextAccessor();
        services.AddScoped<IClientContext, ClientContext>();

        // CAT-001: Rubros options (MaxDepth) — overridable via appsettings "Rubros".
        services.Configure<RubrosOptions>(configuration.GetSection(RubrosOptions.SectionName));

        // UDT-010: Audit options (SanitizedKeys blacklist) — overridable via appsettings "Audit".
        services.Configure<AuditOptions>(configuration.GetSection(AuditOptions.SectionName));
        services.AddScoped<IAuditContext, SIGCM2.Infrastructure.Audit.AuditContext>();
        services.AddScoped<IAuditEventRepository, SIGCM2.Infrastructure.Audit.AuditEventRepository>();
        services.AddScoped<ISecurityEventRepository, SIGCM2.Infrastructure.Audit.SecurityEventRepository>();
        services.AddScoped<IAuditLogger, SIGCM2.Infrastructure.Audit.AuditLogger>();
        services.AddScoped<ISecurityEventLogger, SIGCM2.Infrastructure.Audit.SecurityEventLogger>();

        // Dispatcher
        services.AddScoped<IDispatcher, Dispatcher>();

        // JWT Bearer authentication
        services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
            .AddJwtBearer();

        // Post-configure JWT Bearer — wire RSA public key + validation params from resolved options.
        // MapInboundClaims=false: preserve JWT claim names as-is ("sub", "rol", etc.).
        // Without this, the middleware maps "sub" → ClaimTypes.NameIdentifier and breaks User.FindFirst("sub").
        services.AddOptions<JwtBearerOptions>(JwtBearerDefaults.AuthenticationScheme)
            .PostConfigure<RsaSecurityKey, JwtOptions>((jwtBearerOpts, rsaKey, jwtOpts) =>
            {
                jwtBearerOpts.MapInboundClaims = false;
                jwtBearerOpts.TokenValidationParameters = new TokenValidationParameters
                {
                    ValidateIssuerSigningKey = true,
                    IssuerSigningKey = rsaKey,
                    ValidateIssuer = true,
                    ValidIssuer = jwtOpts.Issuer,
                    ValidateAudience = true,
                    ValidAudience = jwtOpts.Audience,
                    ValidateLifetime = true,
                    ClockSkew = TimeSpan.Zero,
                    RoleClaimType = "rol",
                    NameClaimType = "name"
                };
            });

        return services;
    }
}
