using FluentValidation;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using SIGCM2.Api.Authorization;
using SIGCM2.Application.Abstractions;
using SIGCM2.Application.Common;
using SIGCM2.Application.Usuarios.ChangeMyPassword;
using SIGCM2.Application.Usuarios.Create;
using SIGCM2.Application.Usuarios.Deactivate;
using SIGCM2.Application.Usuarios.GetById;
using SIGCM2.Application.Usuarios.List;
using SIGCM2.Application.Usuarios.Reactivate;
using SIGCM2.Application.Usuarios.Permisos;
using SIGCM2.Application.Usuarios.ResetPassword;
using SIGCM2.Application.Usuarios.Update;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;

namespace SIGCM2.Api.Controllers;

/// <summary>
/// UDT-001/UDT-008: Usuario management endpoints.
/// RequirePermission moved to method level to allow /me/password with [Authorize] only.
/// </summary>
[ApiController]
[Route("api/v1/users")]
public sealed class UsuariosController : ControllerBase
{
    private readonly IDispatcher _dispatcher;
    private readonly IValidator<CreateUsuarioCommand> _createValidator;
    private readonly IValidator<UpdateUsuarioCommand> _updateValidator;
    private readonly IValidator<ChangeMyPasswordCommand> _changePasswordValidator;

    public UsuariosController(
        IDispatcher dispatcher,
        IValidator<CreateUsuarioCommand> createValidator,
        IValidator<UpdateUsuarioCommand> updateValidator,
        IValidator<ChangeMyPasswordCommand> changePasswordValidator)
    {
        _dispatcher = dispatcher;
        _createValidator = createValidator;
        _updateValidator = updateValidator;
        _changePasswordValidator = changePasswordValidator;
    }

    /// <summary>Creates a new user. Requires administracion:usuarios:gestionar.</summary>
    [HttpPost]
    [RequirePermission("administracion:usuarios:gestionar")]
    [ProducesResponseType(typeof(UsuarioCreatedDto), StatusCodes.Status201Created)]
    [ProducesResponseType(StatusCodes.Status400BadRequest)]
    [ProducesResponseType(StatusCodes.Status401Unauthorized)]
    [ProducesResponseType(StatusCodes.Status403Forbidden)]
    [ProducesResponseType(StatusCodes.Status409Conflict)]
    public async Task<IActionResult> CreateUsuario([FromBody] CreateUsuarioRequest request)
    {
        var command = new CreateUsuarioCommand(
            Username: request.Username ?? string.Empty,
            Password: request.Password ?? string.Empty,
            Nombre: request.Nombre ?? string.Empty,
            Apellido: request.Apellido ?? string.Empty,
            Email: request.Email,
            Rol: request.Rol ?? string.Empty);

        var validation = await _createValidator.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<CreateUsuarioCommand, UsuarioCreatedDto>(command);

        return CreatedAtAction(nameof(CreateUsuario), new { id = result.Id }, result);
    }

    /// <summary>Lists usuarios with optional filters and pagination.</summary>
    [HttpGet]
    [RequirePermission("administracion:usuarios:gestionar")]
    [ProducesResponseType(typeof(PagedResult<UsuarioListItemDto>), StatusCodes.Status200OK)]
    [ProducesResponseType(StatusCodes.Status400BadRequest)]
    [ProducesResponseType(StatusCodes.Status401Unauthorized)]
    [ProducesResponseType(StatusCodes.Status403Forbidden)]
    public async Task<IActionResult> ListUsuarios(
        [FromQuery] int page = 1,
        [FromQuery] int pageSize = 20,
        [FromQuery] string? rol = null,
        [FromQuery] bool? activo = null,
        [FromQuery] string? search = null)
    {
        if (page < 1) return BadRequest(new { error = "page must be >= 1" });
        if (pageSize < 1) return BadRequest(new { error = "pageSize must be >= 1" });

        var query = new ListUsuariosQuery(page, pageSize, rol, activo, search);
        var result = await _dispatcher.Send<ListUsuariosQuery, PagedResult<UsuarioListItemDto>>(query);
        return Ok(result);
    }

    /// <summary>Gets a single usuario by id.</summary>
    [HttpGet("{id:int}")]
    [RequirePermission("administracion:usuarios:gestionar")]
    [ProducesResponseType(typeof(UsuarioDetailDto), StatusCodes.Status200OK)]
    [ProducesResponseType(StatusCodes.Status401Unauthorized)]
    [ProducesResponseType(StatusCodes.Status403Forbidden)]
    [ProducesResponseType(StatusCodes.Status404NotFound)]
    public async Task<IActionResult> GetUsuarioById([FromRoute] int id)
    {
        var query = new GetUsuarioByIdQuery(id);
        var result = await _dispatcher.Send<GetUsuarioByIdQuery, UsuarioDetailDto>(query);
        return Ok(result);
    }

    /// <summary>Updates a usuario's editable fields.</summary>
    [HttpPut("{id:int}")]
    [RequirePermission("administracion:usuarios:gestionar")]
    [ProducesResponseType(typeof(UsuarioDetailDto), StatusCodes.Status200OK)]
    [ProducesResponseType(StatusCodes.Status400BadRequest)]
    [ProducesResponseType(StatusCodes.Status401Unauthorized)]
    [ProducesResponseType(StatusCodes.Status403Forbidden)]
    [ProducesResponseType(StatusCodes.Status404NotFound)]
    public async Task<IActionResult> UpdateUsuario([FromRoute] int id, [FromBody] UpdateUsuarioRequest request)
    {
        var command = new UpdateUsuarioCommand(
            Id: id,
            Nombre: request.Nombre ?? string.Empty,
            Apellido: request.Apellido ?? string.Empty,
            Email: request.Email,
            Rol: request.Rol ?? string.Empty,
            Activo: request.Activo ?? true);

        var validation = await _updateValidator.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<UpdateUsuarioCommand, UsuarioDetailDto>(command);
        return Ok(result);
    }

    /// <summary>Deactivates a usuario (idempotent).</summary>
    [HttpPatch("{id:int}/deactivate")]
    [RequirePermission("administracion:usuarios:gestionar")]
    [ProducesResponseType(typeof(UsuarioDetailDto), StatusCodes.Status200OK)]
    [ProducesResponseType(StatusCodes.Status400BadRequest)]
    [ProducesResponseType(StatusCodes.Status401Unauthorized)]
    [ProducesResponseType(StatusCodes.Status403Forbidden)]
    [ProducesResponseType(StatusCodes.Status404NotFound)]
    public async Task<IActionResult> DeactivateUsuario([FromRoute] int id)
    {
        var command = new DeactivateUsuarioCommand(id);
        var result = await _dispatcher.Send<DeactivateUsuarioCommand, UsuarioDetailDto>(command);
        return Ok(result);
    }

    /// <summary>Reactivates a usuario (idempotent).</summary>
    [HttpPatch("{id:int}/reactivate")]
    [RequirePermission("administracion:usuarios:gestionar")]
    [ProducesResponseType(typeof(UsuarioDetailDto), StatusCodes.Status200OK)]
    [ProducesResponseType(StatusCodes.Status401Unauthorized)]
    [ProducesResponseType(StatusCodes.Status403Forbidden)]
    [ProducesResponseType(StatusCodes.Status404NotFound)]
    public async Task<IActionResult> ReactivateUsuario([FromRoute] int id)
    {
        var command = new ReactivateUsuarioCommand(id);
        var result = await _dispatcher.Send<ReactivateUsuarioCommand, UsuarioDetailDto>(command);
        return Ok(result);
    }

    /// <summary>
    /// Changes the authenticated user's own password.
    /// Declared BEFORE /{id:int} route to avoid routing ambiguity (though :int constraint handles it).
    /// Requires only authentication (no specific permission).
    /// </summary>
    [HttpPut("me/password")]
    [Authorize]
    [ProducesResponseType(StatusCodes.Status204NoContent)]
    [ProducesResponseType(StatusCodes.Status400BadRequest)]
    [ProducesResponseType(StatusCodes.Status401Unauthorized)]
    public async Task<IActionResult> ChangeMyPassword([FromBody] ChangeMyPasswordRequest request)
    {
        var sub = User.FindFirstValue(JwtRegisteredClaimNames.Sub)
            ?? User.FindFirstValue(ClaimTypes.NameIdentifier)
            ?? throw new UnauthorizedAccessException();

        var command = new ChangeMyPasswordCommand(
            UsuarioId: int.Parse(sub),
            OldPassword: request.OldPassword ?? string.Empty,
            NewPassword: request.NewPassword ?? string.Empty);

        var validation = await _changePasswordValidator.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 });
        }

        await _dispatcher.Send<ChangeMyPasswordCommand, Unit>(command);
        return NoContent();
    }

    /// <summary>Resets a usuario's password (admin only). Returns a one-time temp password.</summary>
    [HttpPost("{id:int}/password/reset")]
    [RequirePermission("administracion:usuarios:gestionar")]
    [ProducesResponseType(typeof(ResetUsuarioPasswordResponse), StatusCodes.Status200OK)]
    [ProducesResponseType(StatusCodes.Status400BadRequest)]
    [ProducesResponseType(StatusCodes.Status401Unauthorized)]
    [ProducesResponseType(StatusCodes.Status403Forbidden)]
    [ProducesResponseType(StatusCodes.Status404NotFound)]
    public async Task<IActionResult> ResetUsuarioPassword([FromRoute] int id)
    {
        var sub = User.FindFirstValue(JwtRegisteredClaimNames.Sub)
            ?? User.FindFirstValue(ClaimTypes.NameIdentifier)
            ?? throw new UnauthorizedAccessException();

        var command = new ResetUsuarioPasswordCommand(
            TargetId: id,
            CallerId: int.Parse(sub));

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

    // ── UDT-009: Permisos endpoints ───────────────────────────────────────────

    /// <summary>
    /// Gets a usuario's role permissions, explicit grant/deny overrides, and computed effective set.
    /// Requires administracion:usuarios:gestionar.
    /// </summary>
    [HttpGet("{id:int}/permisos")]
    [RequirePermission("administracion:usuarios:gestionar")]
    [ProducesResponseType(typeof(UsuarioPermisosResponse), StatusCodes.Status200OK)]
    [ProducesResponseType(StatusCodes.Status401Unauthorized)]
    [ProducesResponseType(StatusCodes.Status403Forbidden)]
    [ProducesResponseType(StatusCodes.Status404NotFound)]
    public async Task<IActionResult> GetPermisos([FromRoute] int id)
    {
        var result = await _dispatcher.Send<GetUsuarioPermisosQuery, UsuarioPermisosDto>(
            new GetUsuarioPermisosQuery(id));
        return Ok(MapToPermisosResponse(result));
    }

    /// <summary>
    /// Replaces the grant/deny override sets for a usuario.
    /// Requires administracion:usuarios:gestionar.
    /// </summary>
    [HttpPut("{id:int}/permisos/overrides")]
    [RequirePermission("administracion:usuarios:gestionar")]
    [ProducesResponseType(typeof(UsuarioPermisosResponse), StatusCodes.Status200OK)]
    [ProducesResponseType(StatusCodes.Status400BadRequest)]
    [ProducesResponseType(StatusCodes.Status401Unauthorized)]
    [ProducesResponseType(StatusCodes.Status403Forbidden)]
    [ProducesResponseType(StatusCodes.Status404NotFound)]
    public async Task<IActionResult> UpdatePermisosOverrides(
        [FromRoute] int id,
        [FromBody] UpdatePermisosOverridesRequest request)
    {
        var command = new UpdateUsuarioPermisosOverridesCommand(
            Id: id,
            Grant: request.Grant ?? [],
            Deny: request.Deny ?? []);

        var result = await _dispatcher.Send<UpdateUsuarioPermisosOverridesCommand, UsuarioPermisosDto>(command);
        return Ok(MapToPermisosResponse(result));
    }

    private static UsuarioPermisosResponse MapToPermisosResponse(UsuarioPermisosDto dto)
        => new(
            RolPermisos: dto.RolPermisos,
            Overrides: new PermisosOverridesShape(dto.Grant, dto.Deny),
            Effective: dto.Effective);
}

// ── request body records ──────────────────────────────────────────────────────

/// <summary>UDT-009: Response shape for permisos endpoints.</summary>
public sealed record UsuarioPermisosResponse(
    IReadOnlyList<string> RolPermisos,
    PermisosOverridesShape Overrides,
    IReadOnlyList<string> Effective);

/// <summary>UDT-009: The grant/deny override shape nested in UsuarioPermisosResponse.</summary>
public sealed record PermisosOverridesShape(
    IReadOnlyList<string> Grant,
    IReadOnlyList<string> Deny);

/// <summary>UDT-009: PUT permisos/overrides request body.</summary>
public sealed record UpdatePermisosOverridesRequest(
    IReadOnlyList<string>? Grant,
    IReadOnlyList<string>? Deny);

/// <summary>Create user request body — nullable to catch missing field scenarios.</summary>
public sealed record CreateUsuarioRequest(
    string? Username,
    string? Password,
    string? Nombre,
    string? Apellido,
    string? Email,
    string? Rol);

public sealed record UpdateUsuarioRequest(
    string? Nombre,
    string? Apellido,
    string? Email,
    string? Rol,
    bool? Activo);

public sealed record ChangeMyPasswordRequest(
    string? OldPassword,
    string? NewPassword);
