using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.IdentityModel.Tokens;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;
using phronCare.API.Models;
using Services.Models;
using Services.Interfaces;
using phronCare.API.Models.Authentication.Login;
using phronCare.API.Models.Authentication.SingUp;
using phronCare.API.Models.Security;
using Google.Authenticator;
using QRCoder;
using System.ComponentModel.DataAnnotations;

namespace phronCare.API.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class AuthenticationController(
        UserManager<ApplicationUser> userManager,
        RoleManager<IdentityRole> roleManager,
        IEmailService emailService,
        IConfiguration configuration,
        SignInManager<ApplicationUser> signInManager,
        TwoFactorAuthenticator twoFactorAuthenticator) : ControllerBase
    {
        private const int JWT_TOKEN_VALIDITY_HOURS = 48;

        #region Declaration Section
        private readonly UserManager<ApplicationUser> userManager = userManager;
        private readonly RoleManager<IdentityRole> roleManager = roleManager;
        private readonly SignInManager<ApplicationUser> signInManager = signInManager;
        private readonly TwoFactorAuthenticator authenticator = twoFactorAuthenticator;
        private readonly IEmailService emailService = emailService;
        private readonly IConfiguration configuration = configuration;
        #endregion

        #region Security Endpoints

        [HttpPost]
        [Route("generate-qr-code")]
        public async Task<IActionResult> GenerateQRCodeAsync()
        {
            try
            {
                var user = await signInManager.GetTwoFactorAuthenticationUserAsync();
                if (user == null)
                {
                    return BadRequest(new Response { Status = "Error", Message = "Usuario no autenticado." });
                }

                var setupInfo = authenticator.GenerateSetupCode("MiApp", user.Email, user.Id, false, 10);
                var qrCodeGenerator = new QRCodeGenerator();
                var qrCodeData = qrCodeGenerator.CreateQrCode(setupInfo.ManualEntryKey, QRCodeGenerator.ECCLevel.Q);
                var qrCode = new PngByteQRCode(qrCodeData);
                var qrCodeImage = qrCode.GetGraphic(10);
                var qrCodeImageData = Convert.ToBase64String(qrCodeImage);

                return Ok(new AuthResponse
                {
                    Status = "Success",
                    Message = "Código QR generado satisfactoriamente.",
                    ManualSetupKey = setupInfo.ManualEntryKey,
                    QRCodeImage = qrCodeImageData
                });
            }
            catch (Exception)
            {
                return StatusCode(StatusCodes.Status500InternalServerError, new AuthResponse { Status = "Error", Message = "Error al generar el código QR." });
            }
        }

        [HttpPost]
        [Route("register")]
        public async Task<IActionResult> Register([FromBody] RegisterUser registerUser)
        {
            var userExist = await userManager.FindByEmailAsync(registerUser.EmailAddress);
            if (userExist != null)
                return StatusCode(StatusCodes.Status409Conflict, "El correo ingresado ya está en uso.");

            userExist = await userManager.FindByNameAsync(registerUser.UserName);
            if (userExist != null)
                return StatusCode(StatusCodes.Status409Conflict, "El usuario ingresado ya existe.");

            var user = new ApplicationUser
            {
                Email = registerUser.EmailAddress,
                UserName = registerUser.UserName,
                SecurityStamp = Guid.NewGuid().ToString(),
                FirstName = registerUser.FirstName,
                LastName = registerUser.LastName,
                Address = registerUser.Address,
                Department = registerUser.Department,
                CompanyName = registerUser.CompanyName,
                BirthDate = registerUser.BirthDate
            };

            if (await roleManager.RoleExistsAsync(registerUser.Role))
            {
                var result = await userManager.CreateAsync(user, registerUser.Password);
                if (!result.Succeeded)
                {
                    return StatusCode(StatusCodes.Status500InternalServerError, $"Creación de usuario fallida: {string.Join(", ", result.Errors.Select(e => e.Description))}");
                }

                await userManager.AddToRoleAsync(user, registerUser.Role);

                var token = await userManager.GenerateEmailConfirmationTokenAsync(user);
                var confirmationLink = Url.Action(nameof(ConfirmEmail), "Authentication", new { token, email = user.Email }, Request.Scheme);
                var message = new Message(new[] { user.Email! }, "phronCare - Link de verificación", confirmationLink!);
                emailService.SendEmail(message);

                return StatusCode(StatusCodes.Status201Created, $"Usuario creado satisfactoriamente. Debe autenticar la cuenta desde su correo: {user.Email}");
            }

            return StatusCode(StatusCodes.Status500InternalServerError, "El rol no existe. ¡Inténtalo nuevamente!");
        }

        [HttpGet("confirmemail")]
        public async Task<IActionResult> ConfirmEmail(string token, string email)
        {
            var user = await userManager.FindByEmailAsync(email);
            if (user != null)
            {
                var result = await userManager.ConfirmEmailAsync(user, token);
                if (result.Succeeded)
                    return Ok(new Response { Status = "Success", Message = "Email verificado satisfactoriamente." });
            }
            return Conflict(new Response { Status = "Error", Message = "Este usuario no existe." });
        }

        [HttpPost]
        [Route("login")]
        public async Task<IActionResult> Login([FromBody] LoginModel loginModel)
        {
            await signInManager.SignOutAsync();
            var user = await userManager.FindByNameAsync(loginModel.Username);
            if (user is null)
                return Unauthorized("El nombre de usuario o contraseña son incorrectos.");

            var canSignIn = await signInManager.CanSignInAsync(user);
            if (!canSignIn)
            {
                string message = string.Empty;
                if (userManager.Options.SignIn.RequireConfirmedEmail && !(await userManager.IsEmailConfirmedAsync(user)))
                {
                    message = $"El usuario {user.UserName} no puede iniciar sesión sin tener el email confirmado.";
                }
                if (userManager.Options.SignIn.RequireConfirmedPhoneNumber && !(await userManager.IsPhoneNumberConfirmedAsync(user)))
                {
                    message = $"El usuario {user.UserName} no puede iniciar sesión sin tener confirmado el número de teléfono.";
                }
                return Unauthorized(message);
            }

            if (await userManager.CheckPasswordAsync(user, loginModel.Password))
            {
                if (!user.TwoFactorEnabled)
                    return await GenerateAccess(user);

                return Accepted(new Response { Status = "Success", Message = $"Debe ingresar el código desde la app Google Authenticator: {user.UserName}" });
            }

            return Unauthorized("El nombre de usuario o contraseña son incorrectos.");
        }

        [HttpPost]
        [Route("login-2FA")]
        public async Task<IActionResult> LoginWithOTP(string code, string username)
        {
            var user = await userManager.FindByNameAsync(username);
            if (user is not null)
            {
                bool validate = authenticator.ValidateTwoFactorPIN(user.Id, code);
                if (validate)
                    return await GenerateAccess(user);
            }
            return Unauthorized(new Response { Status = "Error", Message = "Código de verificación incorrecto." });
        }

        [HttpPost]
        [AllowAnonymous]
        [Route("forgot-password")]
        public async Task<IActionResult> ForgotPassword([Required] string email)
        {
            var user = await userManager.FindByEmailAsync(email);
            if (user != null)
            {
                var token = await userManager.GeneratePasswordResetTokenAsync(user);
                var forgotPasswordLink = Url.Action(nameof(ResetPassword), "Authentication", new { token, email = user.Email }, Request.Scheme);

                var message = new Message(new[] { user.Email! }, "phronCare - Enlace de recuperación de contraseña", forgotPasswordLink!);
                emailService.SendEmail(message);
                return Ok($"La solicitud de cambio de contraseña se envió a: {user.Email} exitosamente.");
            }
            return BadRequest("No se pudo enviar el enlace al correo, por favor intente nuevamente.");
        }

        [HttpGet("reset-password")]
        public IActionResult ResetPassword(string token, string email)
        {
            var htmlContent = GetResetPasswordHtmlContent(email, token);
            var bytes = Encoding.UTF8.GetBytes(htmlContent);
            var stream = new MemoryStream(bytes);
            return File(stream, "text/html");
        }

        [HttpPost]
        [AllowAnonymous]
        [Route("reset-password")]
        public async Task<IActionResult> ResetPassword([FromBody] ResetPassword resetPassword)
        {
            var user = await userManager.FindByEmailAsync(resetPassword.Email);
            if (user != null)
            {
                var resetPassResult = await userManager.ResetPasswordAsync(user, resetPassword.Token, resetPassword.Password);
                if (!resetPassResult.Succeeded)
                {
                    return StatusCode(StatusCodes.Status500InternalServerError, resetPassResult.Errors.First().Description);
                }
                return Ok("La contraseña ha sido cambiada correctamente.");
            }
            return StatusCode(StatusCodes.Status500InternalServerError, "No se pudo encontrar el usuario, por favor intente nuevamente.");
        }

        private static string GetResetPasswordHtmlContent(string username, string token)
        {
            return @$"
            <!DOCTYPE html>
            <html>
            <head><meta charset='UTF-8'><meta name='viewport' content='width=device-width, initial-scale=1.0'>
            <title>phronCare - Notificación</title></head>
            <body><h2>Usuario: {username}</h2><h3>Token: {token}</h3></body>
            </html>";
        }
        #endregion

        #region GENERATE ACCESS:CARGA DE CLAIMS CON DATOS DEL USUARIO
        private async Task<IActionResult> GenerateAccess(ApplicationUser user)
        {
            try
            {
                var authClaims = new List<Claim>
                {
                    new(ClaimTypes.Name, user.UserName),
                    new("fullName", user.FullName ?? ""), // <--- ESTA ES LA LÍNEA CLAVE
                    new(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
                };

                var userRoles = await userManager.GetRolesAsync(user);

                if (!userRoles.Any())
                {
                    return BadRequest("El usuario no tiene ningún rol asignado. Verifica el proceso de registro.");
                }

                foreach (var role in userRoles)
                {
                    authClaims.Add(new Claim(ClaimTypes.Role, role));
                }

                var jwtToken = GetToken(authClaims);

                var userSession = new UserSession
                {
                    UserName = user.UserName,
                    Role = userRoles.First(), // ya validado que hay al menos uno
                    Token = new JwtSecurityTokenHandler().WriteToken(jwtToken),
                    ExpiresIn = (int)jwtToken.ValidTo.Subtract(DateTime.Now).TotalSeconds,
                    ExpiryTimeStamp = jwtToken.ValidTo
                };

                return Ok(userSession);
            }
            catch (Exception ex)
            {
                // O podés loguearlo con logger si querés
                return StatusCode(StatusCodes.Status500InternalServerError, $"Error generando token: {ex.Message}");
            }
        }

        public class UserSession
        {
            public string UserName { get; set; }
            public string Token { get; set; }
            public string Role { get; set; }
            public int ExpiresIn { get; set; }
            public DateTime ExpiryTimeStamp { get; set; }
        }
        #endregion

        #region GenerateToken
        private JwtSecurityToken GetToken(List<Claim> authClaims)
        {
            var secret = configuration["JWT:Secret"];
            if (string.IsNullOrWhiteSpace(secret))
                throw new InvalidOperationException("El Secret no está configurado.");

            var authSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(secret));
            var credentials = new SigningCredentials(authSigningKey, SecurityAlgorithms.HmacSha256);

            return new JwtSecurityToken(
                issuer: configuration["JWT:ValidIssuer"],
                audience: configuration["JWT:ValidAudience"],
                expires: DateTime.UtcNow.AddHours(JWT_TOKEN_VALIDITY_HOURS),
                claims: authClaims,
                signingCredentials: credentials
            );
        }
        #endregion
    }
}
