using System.Security.Cryptography;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.IdentityModel.Tokens;

namespace ERPCore.Infra.Auth;

/// <summary>
/// Auth wiring for ERPCore as a **resource server** for the external AuthHex IdP
/// (docs/10 A.4). Validates AuthHex's **RS256** tokens against AuthHex's RSA public
/// key (configured statically — no JWKS), issuer <c>AuthHex</c>, audience
/// <c>AuthHexClient</c>. A single door policy (<see cref="ErpAccessPolicy"/>) admits
/// only ERP <c>UserType</c>/<c>Role</c> holders when those codes are configured;
/// per-endpoint RBAC stays deferred. Identity → audit actor is resolved by
/// <see cref="ShadowUserClaimsTransformation"/> + <see cref="ICurrentUser"/>.
/// </summary>
public static class JwtAuthExtensions
{
    /// <summary>Authorization policy applied to every v1 controller (via ApiControllerBase).</summary>
    public const string ErpAccessPolicy = "ErpAccess";

    /// <summary>httpOnly cookie AuthController writes the AuthHex access token into (docs/02-SECURITY.md §B.2).</summary>
    public const string AccessTokenCookie = "erp_at";

    /// <summary>httpOnly cookie AuthController writes the AuthHex refresh token into, scoped to the refresh route.</summary>
    public const string RefreshTokenCookie = "erp_rt";

    /// <summary>Non-httpOnly CSRF cookie for the double-submit check on cookie-authenticated mutations.</summary>
    public const string CsrfCookie = "XSRF-TOKEN";

    /// <summary>Header the frontend echoes the CSRF cookie value back through.</summary>
    public const string CsrfHeader = "X-XSRF-TOKEN";

    public static IServiceCollection AddErpJwtAuth(this IServiceCollection services, IConfiguration config)
    {
        var issuer = config["Auth:Issuer"];
        var audience = config["Auth:Audience"];
        var publicKeyXml = config["Auth:RsaPublicKeyXml"]
            ?? throw new InvalidOperationException("Auth:RsaPublicKeyXml (AuthHex RSA public key) is not configured.");
        var requiredUserType = config["Auth:RequiredUserTypeCode"];
        var requiredRole = config["Auth:RequiredRoleCode"];

        // AuthHex publishes no JWKS; the RSA public key is configured statically.
        var rsa = RSA.Create();
        rsa.FromXmlString(publicKeyXml);
        var signingKey = new RsaSecurityKey(rsa);

        services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
            .AddJwtBearer(options =>
            {
                // Keep AuthHex's claim names verbatim (UserId, UserTypeCode, RoleCode …).
                options.MapInboundClaims = false;
                options.TokenValidationParameters = new TokenValidationParameters
                {
                    ValidateIssuer = true,
                    ValidIssuer = issuer,
                    ValidateAudience = true,
                    ValidAudience = audience,
                    ValidateLifetime = true,
                    ValidateIssuerSigningKey = true,
                    IssuerSigningKey = signingKey,
                    ValidAlgorithms = new[] { SecurityAlgorithms.RsaSha256 },
                    ClockSkew = TimeSpan.FromSeconds(30)
                };
                // BFF cookie fallback: browsers hitting ERPCore through AuthController's
                // httpOnly cookie session carry no Authorization header. Only used when
                // that header is absent, so Bearer callers (Swagger, service-to-service,
                // AuthHexClient forwarding) are unaffected.
                options.Events = new JwtBearerEvents
                {
                    OnMessageReceived = context =>
                    {
                        if (string.IsNullOrEmpty(context.Token) &&
                            context.Request.Cookies.TryGetValue(AccessTokenCookie, out var cookieToken) &&
                            !string.IsNullOrEmpty(cookieToken))
                        {
                            context.Token = cookieToken;
                        }
                        return Task.CompletedTask;
                    }
                };
            });

        services.AddAuthorization(options =>
        {
            options.AddPolicy(ErpAccessPolicy, policy =>
            {
                policy.RequireAuthenticatedUser();
                // Door gate: only enforce a UserType/Role when configured (AuthHex is a
                // shared IdP). Empty config = require a valid ERP token only.
                if (!string.IsNullOrWhiteSpace(requiredUserType))
                    policy.RequireClaim(AuthHexClaims.UserTypeCode, requiredUserType);
                if (!string.IsNullOrWhiteSpace(requiredRole))
                    policy.RequireClaim(AuthHexClaims.RoleCode, requiredRole);
            });
        });

        return services;
    }
}
