using System.IdentityModel.Tokens.Jwt;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using SIGCM2.Application.Abstractions.Persistence;
using SIGCM2.Application.Audit;
using SIGCM2.Application.Common;

namespace SIGCM2.Api.Authorization;

/// <summary>
/// Authorization handler for <see cref="RequirePermissionAttribute"/>.
/// UDT-009: Reads "rol" + "sub" claims, queries both IRolPermisoRepository
/// and IUsuarioRepository, resolves effective permissions via PermisoResolver,
/// and succeeds if at least one required permission matches (OR semantics).
/// No caching — always authoritative from DB (UDT-006 D1, UDT-009 D3).
/// UDT-010: emits SecurityEvent 'permission.denied' on rejection.
/// </summary>
public sealed class PermissionAuthorizationHandler
    : AuthorizationHandler<RequirePermissionAttribute>
{
    private readonly IRolPermisoRepository _rolPermisoRepo;
    private readonly IUsuarioRepository _usuarioRepo;
    private readonly ISecurityEventLogger _security;
    private readonly ILogger<PermissionAuthorizationHandler> _logger;

    public PermissionAuthorizationHandler(
        IRolPermisoRepository rolPermisoRepo,
        IUsuarioRepository usuarioRepo,
        ISecurityEventLogger security,
        ILogger<PermissionAuthorizationHandler> logger)
    {
        _rolPermisoRepo = rolPermisoRepo;
        _usuarioRepo = usuarioRepo;
        _security = security;
        _logger = logger;
    }

    protected override async Task HandleRequirementAsync(
        AuthorizationHandlerContext context,
        RequirePermissionAttribute requirement)
    {
        // 1. Must be authenticated — defense-in-depth
        if (context.User?.Identity?.IsAuthenticated != true)
            return; // implicit Fail

        // 2. Extract "rol" claim
        var rolCodigo = context.User.FindFirst("rol")?.Value;
        if (string.IsNullOrWhiteSpace(rolCodigo))
        {
            _logger.LogWarning(
                "Authorization failed — token missing 'rol' claim for user {User}",
                context.User.Identity?.Name);
            context.Fail(new AuthorizationFailureReason(this, "missing_rol_claim"));
            return;
        }

        // 3. Extract "sub" claim — MapInboundClaims=false so it stays as "sub" (NOT NameIdentifier)
        var subClaim = context.User.FindFirst(JwtRegisteredClaimNames.Sub)?.Value
                    ?? context.User.FindFirst("sub")?.Value;

        if (string.IsNullOrWhiteSpace(subClaim) || !int.TryParse(subClaim, out var userId))
        {
            _logger.LogWarning(
                "Authorization failed — token missing or non-numeric 'sub' claim for user {User}",
                context.User.Identity?.Name);
            context.Fail(new AuthorizationFailureReason(this, "missing_sub_claim"));
            return;
        }

        // 4. Load role permissions — no cache (UDT-006 D1)
        var rolPermisoEntities = await _rolPermisoRepo.GetByRolCodigoAsync(rolCodigo);
        var rolPermisos = rolPermisoEntities.Select(p => p.Codigo);

        // 5. Load user overrides — no cache (UDT-009 D3); null usuario → no overrides
        var usuario = await _usuarioRepo.GetByIdAsync(userId);
        var overrides = PermisosOverride.FromJson(usuario?.PermisosJson);

        // 6. Resolve effective permissions
        var effective = PermisoResolver.Resolve(rolPermisos, overrides);

        // 7. OR semantics — any single match is enough
        var matched = requirement.PermissionCodes.FirstOrDefault(effective.Contains);

        if (matched is not null)
        {
            context.Succeed(requirement);
            return;
        }

        // 8. Stash required permission for ForbiddenProblemDetailsHandler
        var requiredPermission = requirement.PermissionCodes[0];
        if (context.Resource is HttpContext httpContext)
            httpContext.Items["RequiredPermission"] = requiredPermission;

        // 9. Emit SecurityEvent for the denial
        var endpoint = (context.Resource as HttpContext)?.Request?.Path.Value;
        var method = (context.Resource as HttpContext)?.Request?.Method;
        await _security.LogAsync("permission.denied", "failure",
            actorUserId: userId,
            failureReason: $"missing_permission:{requiredPermission}",
            metadata: new { permissionRequired = requiredPermission, endpoint, method });

        context.Fail(new AuthorizationFailureReason(this,
            $"missing_permission:{string.Join('|', requirement.PermissionCodes)}"));
    }
}
