using FluentValidation;
using SIGCM2.Application.Abstractions.Persistence;
using SIGCM2.Application.Auth;

namespace SIGCM2.Application.Usuarios.Create;

public sealed class CreateUsuarioCommandValidator : AbstractValidator<CreateUsuarioCommand>
{
    private const int UsernameMinLength = 3;
    private const int UsernameMaxLength = 50;
    private const int NombreMaxLength = 100;
    private const int ApellidoMaxLength = 100;
    private const int EmailMaxLength = 150;

    public CreateUsuarioCommandValidator(AuthOptions authOptions, IRolRepository rolRepository)
    {
        RuleFor(x => x.Username)
            .NotEmpty().WithMessage("El nombre de usuario es requerido.")
            .Length(UsernameMinLength, UsernameMaxLength)
                .WithMessage($"El username debe tener entre {UsernameMinLength} y {UsernameMaxLength} caracteres.")
            .Matches(@"^[a-zA-Z0-9._\-]+$")
                .WithMessage("El username solo puede contener letras, dígitos, puntos, guiones y guiones bajos.");

        RuleFor(x => x.Password)
            .NotEmpty().WithMessage("La contraseña es requerida.")
            .MinimumLength(authOptions.PasswordMinLength)
                .WithMessage($"La contraseña debe tener al menos {authOptions.PasswordMinLength} caracteres.")
            .Must(p => !authOptions.PasswordRequireLetter || ContainsLetter(p))
                .WithMessage("La contraseña debe contener al menos una letra.")
            .Must(p => !authOptions.PasswordRequireDigit || ContainsDigit(p))
                .WithMessage("La contraseña debe contener al menos un dígito.");

        RuleFor(x => x.Nombre)
            .NotEmpty().WithMessage("El nombre es requerido.")
            .MaximumLength(NombreMaxLength).WithMessage($"El nombre no puede superar los {NombreMaxLength} caracteres.");

        RuleFor(x => x.Apellido)
            .NotEmpty().WithMessage("El apellido es requerido.")
            .MaximumLength(ApellidoMaxLength).WithMessage($"El apellido no puede superar los {ApellidoMaxLength} caracteres.");

        RuleFor(x => x.Email)
            .EmailAddress().WithMessage("El email no tiene un formato válido.")
            .MaximumLength(EmailMaxLength).WithMessage($"El email no puede superar los {EmailMaxLength} caracteres.")
            .When(x => x.Email is not null);

        // Rol: lookup dinámico contra dbo.Rol (UDT-004).
        // MustAsync requiere ValidateAsync en el call site — controllers ya usan ValidateAsync.
        RuleFor(x => x.Rol)
            .NotEmpty().WithMessage("El rol es requerido.")
            .MustAsync(async (codigo, ct) =>
                !string.IsNullOrEmpty(codigo) && await rolRepository.ExistsActiveByCodigoAsync(codigo, ct))
                .WithMessage("El rol debe existir en el sistema y estar activo.");
    }

    private static bool ContainsLetter(string value) =>
        value is not null && value.Any(char.IsLetter);

    private static bool ContainsDigit(string value) =>
        value is not null && value.Any(char.IsDigit);
}
