using Microsoft.Extensions.Logging;
using SIGCM2.Application.Abstractions;
using SIGCM2.Application.Abstractions.Persistence;
using SIGCM2.Application.Abstractions.Security;
using SIGCM2.Application.Audit;
using SIGCM2.Application.Common;
using SIGCM2.Domain.Entities;
using SIGCM2.Domain.Exceptions;
using SIGCM2.Domain.Security;

namespace SIGCM2.Application.Auth.Login;

public sealed class LoginCommandHandler : ICommandHandler<LoginCommand, LoginResponseDto>
{
    private readonly IUsuarioRepository _repository;
    private readonly IPasswordHasher _hasher;
    private readonly IJwtService _jwtService;
    private readonly IRefreshTokenRepository _refreshRepository;
    private readonly IRefreshTokenGenerator _refreshGenerator;
    private readonly IClientContext _clientContext;
    private readonly AuthOptions _authOptions;
    private readonly IRolPermisoRepository _rolPermisoRepository;
    private readonly ISecurityEventLogger _security;
    private readonly ILogger<LoginCommandHandler> _logger;
    private readonly TimeProvider _timeProvider;

    public LoginCommandHandler(
        IUsuarioRepository repository,
        IPasswordHasher hasher,
        IJwtService jwtService,
        IRefreshTokenRepository refreshRepository,
        IRefreshTokenGenerator refreshGenerator,
        IClientContext clientContext,
        AuthOptions authOptions,
        IRolPermisoRepository rolPermisoRepository,
        ISecurityEventLogger security,
        ILogger<LoginCommandHandler> logger,
        TimeProvider timeProvider)
    {
        _repository = repository;
        _hasher = hasher;
        _jwtService = jwtService;
        _refreshRepository = refreshRepository;
        _refreshGenerator = refreshGenerator;
        _clientContext = clientContext;
        _authOptions = authOptions;
        _rolPermisoRepository = rolPermisoRepository;
        _security = security;
        _logger = logger;
        _timeProvider = timeProvider;
    }

    public async Task<LoginResponseDto> Handle(LoginCommand command)
    {
        var usuario = await _repository.GetByUsernameAsync(command.Username);

        // Deliberately vague to the client — never reveal which check failed.
        // Internally, SecurityEvent captures the precise FailureReason for ops.
        if (usuario is null)
        {
            await _security.LogAsync("login", "failure",
                actorUserId: null, attemptedUsername: command.Username,
                failureReason: "user_not_found");
            throw new InvalidCredentialsException();
        }
        if (!usuario.Activo)
        {
            await _security.LogAsync("login", "failure",
                actorUserId: usuario.Id, attemptedUsername: command.Username,
                failureReason: "user_inactive");
            throw new InvalidCredentialsException();
        }

        if (!_hasher.Verify(command.Password, usuario.PasswordHash))
        {
            await _security.LogAsync("login", "failure",
                actorUserId: usuario.Id, attemptedUsername: command.Username,
                failureReason: "invalid_password");
            throw new InvalidCredentialsException();
        }

        var accessToken = _jwtService.GenerateAccessToken(usuario);

        // Generate and persist refresh token — only the hash hits the DB
        var rawRefresh = _refreshGenerator.Generate();
        var hash = TokenHasher.Sha256Base64Url(rawRefresh);
        var now = _timeProvider.GetUtcNow().UtcDateTime;
        var ttl = TimeSpan.FromDays(_authOptions.RefreshTokenDays);
        var entity = RefreshToken.IssueForNewFamily(
            usuario.Id, hash, now, ttl,
            _clientContext.Ip, _clientContext.UserAgent);
        await _refreshRepository.AddAsync(entity);

        // UDT-008: update UltimoLogin best-effort — never block login on this
        try
        {
            await _repository.UpdateUltimoLoginAsync(usuario.Id, now);
        }
        catch (Exception ex)
        {
            _logger.LogWarning(ex, "Failed to update UltimoLogin for usuario {Id} — login proceeds", usuario.Id);
        }

        // UDT-009: permisos efectivos = (rol ∪ grant) \ deny via PermisoResolver
        var rolPermisoEntities = await _rolPermisoRepository.GetByRolCodigoAsync(usuario.Rol);
        var rolPermisos = rolPermisoEntities.Select(p => p.Codigo);
        var overrides = PermisosOverride.FromJson(usuario.PermisosJson);
        var effective = PermisoResolver.Resolve(rolPermisos, overrides);
        var permisos = effective.OrderBy(p => p, StringComparer.Ordinal).ToArray();

        await _security.LogAsync("login", "success", actorUserId: usuario.Id);

        return new LoginResponseDto(
            AccessToken: accessToken,
            RefreshToken: rawRefresh,      // raw to client — never stored
            ExpiresIn: _authOptions.AccessTokenMinutes * 60,
            Usuario: new UsuarioDto(
                Id: usuario.Id,
                Username: usuario.Username,
                Nombre: $"{usuario.Nombre} {usuario.Apellido}".Trim(),
                Rol: usuario.Rol,
                Permisos: permisos,
                MustChangePassword: usuario.MustChangePassword
            )
        );
    }
}
