using Google.Apis.Auth;
using OtpNet;
using SIGCM.Application.DTOs;
using SIGCM.Application.Interfaces;
using SIGCM.Domain.Entities;
using SIGCM.Domain.Interfaces;
using System.Text;

namespace SIGCM.Infrastructure.Services;

public class AuthService : IAuthService
{
    private readonly IUserRepository _userRepo;
    private readonly ITokenService _tokenService;

    public AuthService(IUserRepository userRepo, ITokenService tokenService)
    {
        _userRepo = userRepo;
        _tokenService = tokenService;
    }

    // Inicio de sesión estándar con usuario y contraseña
    public async Task<AuthResult> LoginAsync(string username, string password)
    {
        var user = await _userRepo.GetByUsernameAsync(username);
        if (user == null) return new AuthResult { Success = false, ErrorMessage = "Credenciales inválidas" };

        // Verificación de bloqueo de cuenta
        if (user.LockoutEnd.HasValue && user.LockoutEnd.Value > DateTime.UtcNow)
            return new AuthResult { Success = false, ErrorMessage = "Cuenta bloqueada temporalmente", IsLockedOut = true };

        // Verificación de cuenta activa
        if (!user.IsActive)
            return new AuthResult { Success = false, ErrorMessage = "Cuenta desactivada" };

        // Verificación de contraseña
        bool valid = BCrypt.Net.BCrypt.Verify(password, user.PasswordHash);
        if (!valid)
        {
            user.FailedLoginAttempts++;
            if (user.FailedLoginAttempts >= 5) user.LockoutEnd = DateTime.UtcNow.AddMinutes(15);
            await _userRepo.UpdateAsync(user);
            return new AuthResult { Success = false, ErrorMessage = "Credenciales inválidas" };
        }

        // Si MFA está activo, no devolver token aún, pedir verificación
        if (user.IsMfaEnabled)
        {
            return new AuthResult { Success = true, RequiresMfa = true, UserId = user.Id };
        }

        // Éxito: Reiniciar intentos y generar token
        user.FailedLoginAttempts = 0;
        user.LockoutEnd = null;
        user.LastLogin = DateTime.UtcNow;
        await _userRepo.UpdateAsync(user);

        return new AuthResult 
        { 
            Success = true, 
            Token = _tokenService.GenerateToken(user), 
            RequiresPasswordChange = user.MustChangePassword,
            UserId = user.Id
        };
    }

    // Registro de nuevos usuarios (Public Web)
    public async Task<AuthResult> RegisterAsync(string username, string email, string password)
    {
        if (await _userRepo.GetByUsernameAsync(username) != null)
            return new AuthResult { Success = false, ErrorMessage = "El usuario ya existe" };
        
        if (await _userRepo.GetByEmailAsync(email) != null)
            return new AuthResult { Success = false, ErrorMessage = "El email ya está registrado" };

        var user = new User
        {
            Username = username,
            Email = email,
            PasswordHash = BCrypt.Net.BCrypt.HashPassword(password),
            Role = "User", // Rol por defecto para la web pública
            CreatedAt = DateTime.UtcNow,
            MustChangePassword = false
        };

        await _userRepo.CreateAsync(user);
        return new AuthResult { Success = true, Token = _tokenService.GenerateToken(user), UserId = user.Id };
    }

    // Login mediante Google OAuth
    public async Task<AuthResult> GoogleLoginAsync(string idToken)
    {
        try
        {
            var payload = await GoogleJsonWebSignature.ValidateAsync(idToken);
            var user = await _userRepo.GetByGoogleIdAsync(payload.Subject) 
                       ?? await _userRepo.GetByEmailAsync(payload.Email);

            if (user == null)
            {
                // Auto-registro mediante Google
                user = new User
                {
                    Username = payload.Email.Split('@')[0],
                    Email = payload.Email,
                    GoogleId = payload.Subject,
                    PasswordHash = "OAUTH_LOGIN_" + Guid.NewGuid().ToString(), // Hash dummy
                    Role = "User",
                    CreatedAt = DateTime.UtcNow,
                    MustChangePassword = false
                };
                user.Id = await _userRepo.CreateAsync(user);
            }
            else if (string.IsNullOrEmpty(user.GoogleId))
            {
                // Vincular cuenta existente con Google
                user.GoogleId = payload.Subject;
                await _userRepo.UpdateAsync(user);
            }

            if (user.IsMfaEnabled) return new AuthResult { Success = true, RequiresMfa = true, UserId = user.Id };

            return new AuthResult { Success = true, Token = _tokenService.GenerateToken(user), UserId = user.Id };
        }
        catch (InvalidJwtException)
        {
            return new AuthResult { Success = false, ErrorMessage = "Token de Google inválido" };
        }
    }

    // Genera un secreto para configurar MFA con aplicaciones tipo Google Authenticator
    public async Task<string> GenerateMfaSecretAsync(int userId)
    {
        var user = await _userRepo.GetByIdAsync(userId);
        if (user == null) throw new Exception("Usuario no encontrado");

        var secretBytes = KeyGeneration.GenerateRandomKey(20);
        var secret = Base32Encoding.ToString(secretBytes);
        
        user.MfaSecret = secret;
        await _userRepo.UpdateAsync(user);
        
        return secret;
    }

    // Verifica el código TOTP ingresado por el usuario
    public async Task<bool> VerifyMfaCodeAsync(int userId, string code)
    {
        var user = await _userRepo.GetByIdAsync(userId);
        if (user == null || string.IsNullOrEmpty(user.MfaSecret)) return false;

        var totp = new Totp(Base32Encoding.ToBytes(user.MfaSecret));
        return totp.VerifyTotp(code, out _, new VerificationWindow(1, 1));
    }

    // Activa o desactiva MFA para el usuario
    public async Task EnableMfaAsync(int userId, bool enabled)
    {
        var user = await _userRepo.GetByIdAsync(userId);
        if (user == null) return;
        user.IsMfaEnabled = enabled;
        await _userRepo.UpdateAsync(user);
    }
}
