using System.Transactions;
using SIGCM2.Application.Abstractions;
using SIGCM2.Application.Abstractions.Persistence;
using SIGCM2.Application.Audit;
using SIGCM2.Application.Common;
using SIGCM2.Domain.Exceptions;

namespace SIGCM2.Application.Usuarios.Permisos;

/// <summary>
/// UDT-009: Handles PUT /api/v1/users/{id}/permisos/overrides.
/// Validates overlap and catalog existence, persists new overrides, returns updated effective set.
/// </summary>
public sealed class UpdateUsuarioPermisosOverridesCommandHandler
    : ICommandHandler<UpdateUsuarioPermisosOverridesCommand, UsuarioPermisosDto>
{
    private readonly IUsuarioRepository _usuarioRepo;
    private readonly IRolPermisoRepository _rolPermisoRepo;
    private readonly IPermisoRepository _permisoRepo;
    private readonly IAuditLogger _audit;
    private readonly TimeProvider _timeProvider;

    public UpdateUsuarioPermisosOverridesCommandHandler(
        IUsuarioRepository usuarioRepo,
        IRolPermisoRepository rolPermisoRepo,
        IPermisoRepository permisoRepo,
        IAuditLogger audit,
        TimeProvider timeProvider)
    {
        _usuarioRepo = usuarioRepo;
        _rolPermisoRepo = rolPermisoRepo;
        _permisoRepo = permisoRepo;
        _audit = audit;
        _timeProvider = timeProvider;
    }

    public async Task<UsuarioPermisosDto> Handle(UpdateUsuarioPermisosOverridesCommand command)
    {
        var grant = command.Grant ?? [];
        var deny = command.Deny ?? [];

        // 1. Overlap check — grant ∩ deny → 400
        var overlap = grant.Intersect(deny, StringComparer.Ordinal).ToArray();
        if (overlap.Length > 0)
            throw new GrantDenyOverlapException(overlap);

        // 2. Catalog existence check
        var allCodes = grant.Concat(deny).Distinct(StringComparer.Ordinal).ToArray();
        if (allCodes.Length > 0)
        {
            var existentes = await _permisoRepo.GetByCodigosAsync(allCodes);
            var existSet = existentes.Select(p => p.Codigo).ToHashSet(StringComparer.Ordinal);
            var faltantes = allCodes.Where(c => !existSet.Contains(c)).ToArray();
            if (faltantes.Length > 0)
                throw new InvalidPermisoCodesException(faltantes);
        }

        // 3. Load usuario
        var usuario = await _usuarioRepo.GetByIdAsync(command.Id)
            ?? throw new UsuarioNotFoundException(command.Id);

        // 4. Persist — use WithPermisosJson to get updated FechaModificacion
        var newOverrides = new PermisosOverride(grant, deny);
        var previousOverrides = PermisosOverride.FromJson(usuario.PermisosJson);
        var now = _timeProvider.GetUtcNow().UtcDateTime;
        var updated = usuario.WithPermisosJson(newOverrides.ToJson(), now);

        using (var tx = new TransactionScope(
            TransactionScopeOption.Required,
            new TransactionOptions { IsolationLevel = IsolationLevel.ReadCommitted },
            TransactionScopeAsyncFlowOption.Enabled))
        {
            await _usuarioRepo.UpdatePermisosJsonAsync(
                updated.Id,
                updated.PermisosJson,
                updated.FechaModificacion!.Value);

            await _audit.LogAsync(
                action: "usuario.permisos_update",
                targetType: "Usuario",
                targetId: command.Id.ToString(),
                metadata: new
                {
                    before = new { grant = previousOverrides.Grant, deny = previousOverrides.Deny },
                    after = new { grant = grant, deny = deny },
                });

            tx.Complete();
        }

        // 5. Return updated effective set
        var rolPermisoEntities = await _rolPermisoRepo.GetByRolCodigoAsync(updated.Rol);
        var rolPermisos = rolPermisoEntities
            .Select(p => p.Codigo)
            .OrderBy(c => c, StringComparer.Ordinal)
            .ToArray();

        var effective = PermisoResolver.Resolve(rolPermisos, newOverrides)
            .OrderBy(c => c, StringComparer.Ordinal)
            .ToArray();

        return new UsuarioPermisosDto(
            UsuarioId: updated.Id,
            Rol: updated.Rol,
            RolPermisos: rolPermisos,
            Grant: grant,
            Deny: deny,
            Effective: effective);
    }
}
