using System.IdentityModel.Tokens.Jwt;
using FluentValidation;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using SIGCM2.Application.Abstractions;
using SIGCM2.Application.Auth.Login;
using SIGCM2.Application.Auth.Logout;
using SIGCM2.Application.Auth.Refresh;

namespace SIGCM2.Api.Controllers;

[ApiController]
[Route("api/v1/auth")]
public sealed class AuthController : ControllerBase
{
    private readonly IDispatcher _dispatcher;
    private readonly IValidator<LoginCommand> _loginValidator;
    private readonly IValidator<RefreshCommand> _refreshValidator;

    public AuthController(
        IDispatcher dispatcher,
        IValidator<LoginCommand> loginValidator,
        IValidator<RefreshCommand> refreshValidator)
    {
        _dispatcher = dispatcher;
        _loginValidator = loginValidator;
        _refreshValidator = refreshValidator;
    }

    /// <summary>Authenticates a user and returns a JWT access token + refresh token.</summary>
    [HttpPost("login")]
    [AllowAnonymous]
    [ProducesResponseType(typeof(LoginResponseDto), StatusCodes.Status200OK)]
    [ProducesResponseType(StatusCodes.Status400BadRequest)]
    [ProducesResponseType(StatusCodes.Status401Unauthorized)]
    public async Task<IActionResult> Login([FromBody] LoginRequest request)
    {
        var command = new LoginCommand(request.Username ?? string.Empty, request.Password ?? string.Empty);

        var validation = await _loginValidator.ValidateAsync(command);
        if (!validation.IsValid)
        {
            var errors = validation.Errors
                .GroupBy(e => e.PropertyName)
                .ToDictionary(g => g.Key, g => g.Select(e => e.ErrorMessage).ToArray());
            return BadRequest(new { errors });
        }

        var result = await _dispatcher.Send<LoginCommand, LoginResponseDto>(command);
        return Ok(result);
    }

    /// <summary>
    /// Rotates a refresh token pair. Accepts an expired access token to extract the user identity.
    /// Returns a new access + refresh token pair. Does NOT require Authorization header.
    /// </summary>
    [HttpPost("refresh")]
    [AllowAnonymous]
    [ProducesResponseType(typeof(RefreshResponseDto), StatusCodes.Status200OK)]
    [ProducesResponseType(StatusCodes.Status400BadRequest)]
    [ProducesResponseType(StatusCodes.Status401Unauthorized)]
    public async Task<IActionResult> Refresh([FromBody] RefreshRequest request)
    {
        var command = new RefreshCommand(
            request.AccessToken ?? string.Empty,
            request.RefreshToken ?? string.Empty);

        var validation = await _refreshValidator.ValidateAsync(command);
        if (!validation.IsValid)
        {
            var errors = validation.Errors
                .GroupBy(e => e.PropertyName)
                .ToDictionary(g => g.Key, g => g.Select(e => e.ErrorMessage).ToArray());
            return BadRequest(new { errors });
        }

        var result = await _dispatcher.Send<RefreshCommand, RefreshResponseDto>(command);
        return Ok(result);
    }

    /// <summary>
    /// Revokes all active refresh tokens for the authenticated user.
    /// Requires a valid Bearer access token. Client must discard local tokens after this call.
    /// </summary>
    [HttpPost("logout")]
    [Authorize]
    [ProducesResponseType(typeof(LogoutResponseDto), StatusCodes.Status200OK)]
    [ProducesResponseType(StatusCodes.Status401Unauthorized)]
    public async Task<IActionResult> Logout()
    {
        var sub = User.FindFirst(JwtRegisteredClaimNames.Sub)?.Value;
        if (!int.TryParse(sub, out var userId))
            return Unauthorized();

        var result = await _dispatcher.Send<LogoutCommand, LogoutResponseDto>(new LogoutCommand(userId));
        return Ok(result);
    }
}

/// <summary>Login request body — nullable to catch missing field scenarios.</summary>
public sealed record LoginRequest(string? Username, string? Password);

/// <summary>Refresh request body.</summary>
public sealed record RefreshRequest(string? AccessToken, string? RefreshToken);
