using Dapper;
using Microsoft.Data.SqlClient;
using SIGCM2.Domain.Entities;
using SIGCM2.Infrastructure.Persistence;

namespace SIGCM2.Application.Tests.Integration;

[Collection("Database")]
public class RolRepositoryTests : IAsyncLifetime
{
    private const string ConnectionString = TestConnectionStrings.AppTestDb;

    private SqlConnection _connection = null!;
    private RolRepository _repository = null!;

    public async Task InitializeAsync()
    {
        _connection = new SqlConnection(ConnectionString);
        await _connection.OpenAsync();

        // Clean RefreshToken first (FK to Usuario), then Usuario (FK to Rol), then custom Rol codes.
        // Residual RefreshTokens from prior test suites would violate FK_RefreshToken_Usuario otherwise.
        await _connection.ExecuteAsync("DELETE FROM dbo.RefreshToken;");
        await _connection.ExecuteAsync("DELETE FROM dbo.Usuario;");
        await _connection.ExecuteAsync("DELETE FROM dbo.Rol WHERE Codigo NOT IN ('admin','cajero','operador_ctacte','picadora','jefe_publicidad','productor','diagramacion','reportes');");
        // Ensure canonical Rol seeds exist (idempotent — previous test classes may have wiped them via Respawn).
        await SeedRolCanonicalAsync();
        // Reset any mutations applied to canonical seeds during prior tests.
        await _connection.ExecuteAsync("UPDATE dbo.Rol SET Activo = 1, FechaModificacion = NULL WHERE Codigo IN ('admin','cajero','operador_ctacte','picadora','jefe_publicidad','productor','diagramacion','reportes');");
        // Seed admin usuario (needed by HasActiveUsuariosAsync test expecting admin active).
        await _connection.ExecuteAsync(
            "INSERT INTO dbo.Usuario (Username, PasswordHash, Nombre, Apellido, Rol, PermisosJson, Activo) " +
            "VALUES ('admin', '$2a$12$hash', 'Administrador', 'Sistema', 'admin', '[\"*\"]', 1);");

        var factory = new SqlConnectionFactory(ConnectionString);
        _repository = new RolRepository(factory);
    }

    public async Task DisposeAsync()
    {
        await _connection.CloseAsync();
        await _connection.DisposeAsync();
    }

    private async Task SeedRolCanonicalAsync()
    {
        const string sql = """
            SET QUOTED_IDENTIFIER ON;
            MERGE dbo.Rol AS t
            USING (VALUES
                ('admin', N'Administrador', N'Supervisor total'),
                ('cajero', N'Cajero', N'Mostrador contado'),
                ('operador_ctacte', N'Operador Cta Cte', N'Cuenta corriente'),
                ('picadora', N'Picadora/Correctora', N'Edición de textos'),
                ('jefe_publicidad', N'Jefe de Publicidad', N'Supervisión de pauta'),
                ('productor', N'Productor', N'Carga restringida'),
                ('diagramacion', N'Diagramación/Taller', N'Solo lectura pauta'),
                ('reportes', N'Reportes', N'Solo lectura reportes')
            ) AS s (Codigo, Nombre, Descripcion)
            ON t.Codigo = s.Codigo
            WHEN NOT MATCHED BY TARGET THEN
                INSERT (Codigo, Nombre, Descripcion, Activo)
                VALUES (s.Codigo, s.Nombre, s.Descripcion, 1);
            """;
        await _connection.ExecuteAsync(sql);
    }

    // ── ListAsync ────────────────────────────────────────────────────────────

    [Fact]
    public async Task ListAsync_ReturnsAllCanonicalSeeds()
    {
        var list = await _repository.ListAsync();

        var codes = list.Select(r => r.Codigo).ToHashSet();
        Assert.Contains("admin", codes);
        Assert.Contains("cajero", codes);
        Assert.Contains("operador_ctacte", codes);
        Assert.Contains("picadora", codes);
        Assert.Contains("jefe_publicidad", codes);
        Assert.Contains("productor", codes);
        Assert.Contains("diagramacion", codes);
        Assert.Contains("reportes", codes);
        Assert.True(list.Count >= 8);
    }

    [Fact]
    public async Task ListAsync_IncludesInactiveRoles()
    {
        // Triangulation: list must include deactivated rows too.
        await _connection.ExecuteAsync("INSERT INTO dbo.Rol (Codigo, Nombre, Activo) VALUES ('listtest_inactive', N'Inactivo de test', 0);");

        var list = await _repository.ListAsync();

        var inactive = list.Single(r => r.Codigo == "listtest_inactive");
        Assert.False(inactive.Activo);
    }

    // ── GetByCodigoAsync ────────────────────────────────────────────────────

    [Fact]
    public async Task GetByCodigoAsync_ExistingCodigo_ReturnsRol()
    {
        var rol = await _repository.GetByCodigoAsync("cajero");

        Assert.NotNull(rol);
        Assert.Equal("cajero", rol!.Codigo);
        Assert.Equal("Cajero", rol.Nombre);
        Assert.True(rol.Activo);
    }

    [Fact]
    public async Task GetByCodigoAsync_NonExistentCodigo_ReturnsNull()
    {
        var rol = await _repository.GetByCodigoAsync("no_existe");
        Assert.Null(rol);
    }

    // ── ExistsActiveByCodigoAsync ───────────────────────────────────────────

    [Fact]
    public async Task ExistsActiveByCodigoAsync_ActiveCodigo_ReturnsTrue()
    {
        Assert.True(await _repository.ExistsActiveByCodigoAsync("admin"));
    }

    [Fact]
    public async Task ExistsActiveByCodigoAsync_InactiveCodigo_ReturnsFalse()
    {
        await _connection.ExecuteAsync("INSERT INTO dbo.Rol (Codigo, Nombre, Activo) VALUES ('exists_inactive', N'Test inactivo', 0);");

        Assert.False(await _repository.ExistsActiveByCodigoAsync("exists_inactive"));
    }

    [Fact]
    public async Task ExistsActiveByCodigoAsync_MissingCodigo_ReturnsFalse()
    {
        Assert.False(await _repository.ExistsActiveByCodigoAsync("missing_codigo_xyz"));
    }

    // ── AddAsync ────────────────────────────────────────────────────────────

    [Fact]
    public async Task AddAsync_NewRol_PersistsAndReturnsId()
    {
        var rol = Rol.ForCreation("addtest_new", "Add Test", "Rol de prueba add");

        var newId = await _repository.AddAsync(rol);

        Assert.True(newId > 0);

        var persisted = await _connection.QuerySingleAsync<(string Codigo, string Nombre, string? Descripcion, bool Activo)>(
            "SELECT Codigo, Nombre, Descripcion, Activo FROM dbo.Rol WHERE Id = @Id",
            new { Id = newId });

        Assert.Equal("addtest_new", persisted.Codigo);
        Assert.Equal("Add Test", persisted.Nombre);
        Assert.Equal("Rol de prueba add", persisted.Descripcion);
        Assert.True(persisted.Activo);
    }

    [Fact]
    public async Task AddAsync_WithNullDescripcion_PersistsNull()
    {
        var rol = Rol.ForCreation("addtest_nulldesc", "Null Desc", null);

        var newId = await _repository.AddAsync(rol);

        var desc = await _connection.ExecuteScalarAsync<string?>(
            "SELECT Descripcion FROM dbo.Rol WHERE Id = @Id", new { Id = newId });
        Assert.Null(desc);
    }

    // ── UpdateAsync ─────────────────────────────────────────────────────────

    [Fact]
    public async Task UpdateAsync_ExistingCodigo_UpdatesMutableFieldsAndSetsFechaModificacion()
    {
        await _connection.ExecuteAsync(
            "INSERT INTO dbo.Rol (Codigo, Nombre, Descripcion, Activo) VALUES ('updtest_one', N'Nombre Viejo', N'Desc vieja', 1);");

        var updated = await _repository.UpdateAsync("updtest_one", "Nombre Nuevo", "Desc nueva", activo: true);

        Assert.True(updated);

        var row = await _connection.QuerySingleAsync<(string Nombre, string? Descripcion, bool Activo, DateTime? FechaModificacion)>(
            "SELECT Nombre, Descripcion, Activo, FechaModificacion FROM dbo.Rol WHERE Codigo = 'updtest_one'");

        Assert.Equal("Nombre Nuevo", row.Nombre);
        Assert.Equal("Desc nueva", row.Descripcion);
        Assert.True(row.Activo);
        Assert.NotNull(row.FechaModificacion);
    }

    [Fact]
    public async Task UpdateAsync_NonExistentCodigo_ReturnsFalse()
    {
        var updated = await _repository.UpdateAsync("updtest_missing", "X", null, true);
        Assert.False(updated);
    }

    [Fact]
    public async Task UpdateAsync_DoesNotChangeCodigo()
    {
        await _connection.ExecuteAsync(
            "INSERT INTO dbo.Rol (Codigo, Nombre, Activo) VALUES ('updtest_codigo', N'Test Codigo', 1);");

        await _repository.UpdateAsync("updtest_codigo", "Nombre Cambiado", null, true);

        var stillExists = await _connection.ExecuteScalarAsync<int>(
            "SELECT COUNT(*) FROM dbo.Rol WHERE Codigo = 'updtest_codigo';");
        Assert.Equal(1, stillExists);
    }

    // ── HasActiveUsuariosAsync ──────────────────────────────────────────────

    [Fact]
    public async Task HasActiveUsuariosAsync_WithActiveUsuario_ReturnsTrue()
    {
        // 'admin' Usuario is seeded active and references Rol.admin.
        Assert.True(await _repository.HasActiveUsuariosAsync("admin"));
    }

    [Fact]
    public async Task HasActiveUsuariosAsync_NoUsuariosReferencing_ReturnsFalse()
    {
        // 'reportes' seed has no Usuario referencing it in a clean test DB.
        Assert.False(await _repository.HasActiveUsuariosAsync("reportes"));
    }

    [Fact]
    public async Task HasActiveUsuariosAsync_OnlyInactiveUsuarioReferencing_ReturnsFalse()
    {
        // Insert an INACTIVE usuario referencing 'cajero'.
        await _connection.ExecuteAsync(
            "INSERT INTO dbo.Usuario (Username, PasswordHash, Nombre, Apellido, Rol, PermisosJson, Activo) " +
            "VALUES ('inactivo1', '$2a$12$hash', 'Test', 'Inactivo', 'cajero', '[]', 0);");

        Assert.False(await _repository.HasActiveUsuariosAsync("cajero"));
    }
}
