using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;
using System.Text.RegularExpressions;
using ApiDenuncias.Configuration;
using GestionaDenuncias.Shared.Models;
using ApiDenuncias.Services;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.Tokens;

namespace ApiDenuncias.Controllers;

[ApiController]
[Route("api/auth")]
public sealed class AuthController : ControllerBase
{
    private readonly GlobalLeaksClient _globalLeaksClient;
    private readonly GlobalLeaksSessionStore _sessionStore;
    private readonly LoginRateLimiter _rateLimiter;
    private readonly JwtOptions _jwtOptions;

    public AuthController(
        GlobalLeaksClient globalLeaksClient,
        GlobalLeaksSessionStore sessionStore,
        LoginRateLimiter rateLimiter,
        IOptions<JwtOptions> jwtOptions)
    {
        _globalLeaksClient = globalLeaksClient;
        _sessionStore = sessionStore;
        _rateLimiter = rateLimiter;
        _jwtOptions = jwtOptions.Value;
    }

    [HttpPost("login")]
    [AllowAnonymous]
    public async Task<ActionResult<ApiLoginResponse>> Login(LoginRequest request, CancellationToken cancellationToken)
    {
        var ip = HttpContext.Connection.RemoteIpAddress?.ToString() ?? "unknown";
        if (!_rateLimiter.AllowAttempt(ip))
        {
            return StatusCode(StatusCodes.Status429TooManyRequests, new ApiError("Demasiados intentos. Espera un minuto."));
        }

        if (string.IsNullOrWhiteSpace(request.Username) ||
            string.IsNullOrWhiteSpace(request.Password) ||
            string.IsNullOrWhiteSpace(request.Authcode))
        {
            return BadRequest(new ApiError("Debes indicar usuario, contrasena y codigo 2FA."));
        }

        if (!Regex.IsMatch(request.Authcode.Trim(), @"^\d{6}$"))
        {
            return BadRequest(new ApiError("El codigo 2FA debe tener exactamente 6 digitos."));
        }

        try
        {
            var session = await _globalLeaksClient.LoginAsync(
                request.Username.Trim(),
                request.Password,
                request.Authcode.Trim(),
                cancellationToken);

            var username = string.IsNullOrWhiteSpace(session.Username)
                ? request.Username.Trim()
                : session.Username.Trim();

            await _sessionStore.SaveAsync(username, request.Password, session.Id, session.Role, cancellationToken);

            var expiresAtUtc = DateTimeOffset.UtcNow.AddMinutes(Math.Max(5, _jwtOptions.ExpirationMinutes));
            var token = CreateJwt(username, session.Role, expiresAtUtc);

            return Ok(new ApiLoginResponse(username, token, expiresAtUtc, session.Role));
        }
        catch (GlobalLeaksValidationException ex)
        {
            return StatusCode(ex.StatusCode, new ApiError(ex.Message));
        }
        catch
        {
            return StatusCode(StatusCodes.Status502BadGateway, new ApiError("No se ha podido conectar con GlobalLeaks."));
        }
    }

    [HttpPost("logout")]
    [Authorize]
    public async Task<IActionResult> Logout(CancellationToken cancellationToken)
    {
        var username = User.Identity?.Name;
        if (!string.IsNullOrWhiteSpace(username))
        {
            await _sessionStore.DeleteAsync(username, cancellationToken);
        }

        return Ok(new { ok = true });
    }

    private string CreateJwt(string username, string? role, DateTimeOffset expiresAtUtc)
    {
        if (string.IsNullOrWhiteSpace(_jwtOptions.SigningKey))
        {
            throw new InvalidOperationException("Falta Jwt:SigningKey en la configuracion de ApiDenuncias.");
        }

        var claims = new List<Claim>
        {
            new(JwtRegisteredClaimNames.Sub, username),
            new(ClaimTypes.Name, username),
            new(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString("N"))
        };

        if (!string.IsNullOrWhiteSpace(role))
        {
            claims.Add(new Claim("gl_role", role));
        }

        var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_jwtOptions.SigningKey));
        var credentials = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
        var token = new JwtSecurityToken(
            issuer: _jwtOptions.Issuer,
            audience: _jwtOptions.Audience,
            claims: claims,
            notBefore: DateTime.UtcNow,
            expires: expiresAtUtc.UtcDateTime,
            signingCredentials: credentials);

        return new JwtSecurityTokenHandler().WriteToken(token);
    }
}
