using SIGCM2.Domain.Entities;

namespace SIGCM2.Application.Tests.Domain;

// ── UDT-008 tests ────────────────────────────────────────────────────────────

public class UsuarioTests
{
    // Happy path: constructor sets all properties correctly
    [Fact]
    public void Constructor_SetsAllProperties()
    {
        var usuario = new Usuario(
            id: 1,
            username: "admin",
            passwordHash: "$2a$12$hash",
            nombre: "Administrador",
            apellido: "Sistema",
            email: null,
            rol: "admin",
            permisosJson: "[\"*\"]",
            activo: true
        );

        Assert.Equal(1, usuario.Id);
        Assert.Equal("admin", usuario.Username);
        Assert.Equal("$2a$12$hash", usuario.PasswordHash);
        Assert.Equal("Administrador", usuario.Nombre);
        Assert.Equal("Sistema", usuario.Apellido);
        Assert.Null(usuario.Email);
        Assert.Equal("admin", usuario.Rol);
        Assert.Equal("[\"*\"]", usuario.PermisosJson);
        Assert.True(usuario.Activo);
    }

    // Triangulation: inactive user
    [Fact]
    public void Constructor_WithActivo_False_SetsActivo_False()
    {
        var usuario = new Usuario(
            id: 2,
            username: "vendedor",
            passwordHash: "$2a$12$hash2",
            nombre: "Juan",
            apellido: "Pérez",
            email: "juan@example.com",
            rol: "vendedor",
            permisosJson: "[]",
            activo: false
        );

        Assert.Equal(2, usuario.Id);
        Assert.Equal("vendedor", usuario.Username);
        Assert.Equal("juan@example.com", usuario.Email);
        Assert.Equal("vendedor", usuario.Rol);
        Assert.Equal("[]", usuario.PermisosJson);
        Assert.False(usuario.Activo);
    }

    // Activo property reflects the actual state
    [Fact]
    public void Activo_IsTrue_WhenConstructedActive()
    {
        var usuario = new Usuario(1, "admin", "$2a$12$hash", "Admin", "Sys", null, "admin", "[\"*\"]", true);
        Assert.True(usuario.Activo);
    }

    [Fact]
    public void Activo_IsFalse_WhenConstructedInactive()
    {
        var usuario = new Usuario(2, "inactive", "$2a$12$hash", "Old", "User", null, "consulta", "[]", false);
        Assert.False(usuario.Activo);
    }

    // ── UDT-008: new properties ───────────────────────────────────────────────

    [Fact]
    public void ForCreation_Defaults_MustChangePassword_False()
    {
        var u = Usuario.ForCreation("user", "hash", "A", "B", null, "cajero");
        Assert.False(u.MustChangePassword);
    }

    [Fact]
    public void ForCreation_Defaults_FechaModificacion_Null()
    {
        var u = Usuario.ForCreation("user", "hash", "A", "B", null, "cajero");
        Assert.Null(u.FechaModificacion);
    }

    [Fact]
    public void ForCreation_Defaults_UltimoLogin_Null()
    {
        var u = Usuario.ForCreation("user", "hash", "A", "B", null, "cajero");
        Assert.Null(u.UltimoLogin);
    }

    // ── UDT-008: WithUpdatedProfile ──────────────────────────────────────────

    [Fact]
    public void WithUpdatedProfile_Returns_NewInstance()
    {
        var u = MakeUsuario();
        var updated = u.WithUpdatedProfile("NuevoNombre", "NuevoApellido", "new@x.com", "cajero", true, DateTime.UtcNow);
        Assert.NotSame(u, updated);
    }

    [Fact]
    public void WithUpdatedProfile_Sets_Fields_Correctly()
    {
        var u = MakeUsuario();
        var updated = u.WithUpdatedProfile("Pedro", "Gómez", "p@g.com", "cajero", false, DateTime.UtcNow);
        Assert.Equal("Pedro", updated.Nombre);
        Assert.Equal("Gómez", updated.Apellido);
        Assert.Equal("p@g.com", updated.Email);
        Assert.Equal("cajero", updated.Rol);
        Assert.False(updated.Activo);
    }

    [Fact]
    public void WithUpdatedProfile_Sets_FechaModificacion_To_UtcNow()
    {
        var before = DateTime.UtcNow.AddSeconds(-1);
        var now = DateTime.UtcNow;
        var u = MakeUsuario();
        var updated = u.WithUpdatedProfile("A", "B", null, "admin", true, now);
        Assert.NotNull(updated.FechaModificacion);
        Assert.True(updated.FechaModificacion >= before);
    }

    [Fact]
    public void WithUpdatedProfile_Preserves_Immutable_Fields()
    {
        var u = MakeUsuario();
        var updated = u.WithUpdatedProfile("X", "Y", null, "cajero", true, DateTime.UtcNow);
        Assert.Equal(u.Id, updated.Id);
        Assert.Equal(u.Username, updated.Username);
        Assert.Equal(u.PasswordHash, updated.PasswordHash);
    }

    // ── UDT-008: WithNewPasswordHash ─────────────────────────────────────────

    [Fact]
    public void WithNewPasswordHash_Returns_NewInstance()
    {
        var u = MakeUsuario();
        var updated = u.WithNewPasswordHash("newhash", mustChangePassword: false, DateTime.UtcNow);
        Assert.NotSame(u, updated);
    }

    [Fact]
    public void WithNewPasswordHash_Sets_Hash_And_MustChange()
    {
        var u = MakeUsuario();
        var updated = u.WithNewPasswordHash("newhash", mustChangePassword: true, DateTime.UtcNow);
        Assert.Equal("newhash", updated.PasswordHash);
        Assert.True(updated.MustChangePassword);
    }

    [Fact]
    public void WithNewPasswordHash_Clears_MustChange_When_False()
    {
        var u = MakeUsuario(mustChangePassword: true);
        var updated = u.WithNewPasswordHash("hash2", mustChangePassword: false, DateTime.UtcNow);
        Assert.False(updated.MustChangePassword);
    }

    [Fact]
    public void WithNewPasswordHash_Sets_FechaModificacion()
    {
        var before = DateTime.UtcNow.AddSeconds(-1);
        var now = DateTime.UtcNow;
        var u = MakeUsuario();
        var updated = u.WithNewPasswordHash("hash2", false, now);
        Assert.NotNull(updated.FechaModificacion);
        Assert.True(updated.FechaModificacion >= before);
    }

    // ── UDT-008: WithUltimoLogin ──────────────────────────────────────────────

    [Fact]
    public void WithUltimoLogin_Returns_NewInstance()
    {
        var u = MakeUsuario();
        var updated = u.WithUltimoLogin(DateTime.UtcNow);
        Assert.NotSame(u, updated);
    }

    [Fact]
    public void WithUltimoLogin_Sets_UltimoLogin()
    {
        var ts = new DateTime(2026, 1, 15, 10, 0, 0, DateTimeKind.Utc);
        var u = MakeUsuario();
        var updated = u.WithUltimoLogin(ts);
        Assert.Equal(ts, updated.UltimoLogin);
    }

    [Fact]
    public void WithUltimoLogin_Does_NOT_Touch_FechaModificacion()
    {
        var u = MakeUsuario();
        var originalFecha = u.FechaModificacion;
        var updated = u.WithUltimoLogin(DateTime.UtcNow);
        Assert.Equal(originalFecha, updated.FechaModificacion);
    }

    // ── UDT-008: WithMustChangePassword ──────────────────────────────────────

    [Fact]
    public void WithMustChangePassword_Sets_Value_True()
    {
        var u = MakeUsuario(mustChangePassword: false);
        var updated = u.WithMustChangePassword(true, DateTime.UtcNow);
        Assert.True(updated.MustChangePassword);
    }

    [Fact]
    public void WithMustChangePassword_Sets_FechaModificacion()
    {
        var before = DateTime.UtcNow.AddSeconds(-1);
        var now = DateTime.UtcNow;
        var u = MakeUsuario();
        var updated = u.WithMustChangePassword(true, now);
        Assert.NotNull(updated.FechaModificacion);
        Assert.True(updated.FechaModificacion >= before);
    }

    // ── helpers ───────────────────────────────────────────────────────────────

    private static Usuario MakeUsuario(bool mustChangePassword = false)
        => new(
            id: 1,
            username: "testuser",
            passwordHash: "$2a$12$hash",
            nombre: "Test",
            apellido: "User",
            email: "test@x.com",
            rol: "admin",
            permisosJson: "[]",
            activo: true,
            fechaModificacion: null,
            ultimoLogin: null,
            mustChangePassword: mustChangePassword
        );
}
