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

namespace SIGCM2.Application.Tests.Integration;

/// <summary>
/// Integration tests for PermisoRepository against SIGCM2_Test.
/// RED: written before the repository implementation exists.
/// </summary>
[Collection("Database")]
public class PermisoRepositoryTests : IAsyncLifetime
{
    private const string ConnectionString = TestConnectionStrings.AppTestDb;

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

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

        // Ensure the 18 canonical permisos are present — idempotent MERGE.
        // Needed because other test classes (RefreshTokenRepositoryTests, UsuarioRepositoryTests)
        // may call Respawn.ResetAsync before us, which would clear Permiso even if listed in
        // TablesToIgnore of the central SqlTestFixture (each class configures its own Respawner).
        await SeedPermisosCanonicalAsync();

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

    private async Task SeedPermisosCanonicalAsync()
    {
        const string sql = """
            SET QUOTED_IDENTIFIER ON;
            MERGE dbo.Permiso AS t
            USING (VALUES
                ('ventas:contado:crear',                N'Cargar orden contado',                NULL,                                           'ventas'),
                ('ventas:contado:modificar',            N'Modificar orden contado',             NULL,                                           'ventas'),
                ('ventas:contado:cobrar',               N'Cobrar orden contado',                NULL,                                           'ventas'),
                ('ventas:contado:facturar',             N'Facturar orden contado',              NULL,                                           'ventas'),
                ('ventas:ctacte:crear',                 N'Cargar orden cuenta corriente',       NULL,                                           'ventas'),
                ('ventas:ctacte:facturar',              N'Facturar lote cuenta corriente',      NULL,                                           'ventas'),
                ('textos:editar',                       N'Editar textos',                       NULL,                                           'textos'),
                ('textos:reclamos:ver',                 N'Ver reclamos de textos',              NULL,                                           'textos'),
                ('pauta:azanu:ver',                     N'Ver AZANU en pauta',                  NULL,                                           'pauta'),
                ('pauta:limpiar',                       N'Limpieza de pauta',                   NULL,                                           'pauta'),
                ('pauta:recursos:fueradehora',          N'Recursos fuera de hora',              NULL,                                           'pauta'),
                ('productores:deuda:ver',               N'Ver deuda propia de productores',     NULL,                                           'productores'),
                ('productores:pendientes:crear',        N'Cargar pendientes de productores',    NULL,                                           'productores'),
                ('productores:deuda:bypass',            N'Bypass de deuda de productores',      NULL,                                           'productores'),
                ('administracion:usuarios:gestionar',   N'Gestionar usuarios del sistema',      N'Crear, editar y desactivar usuarios',         'administracion'),
                ('administracion:tarifarios:gestionar', N'Gestionar tarifarios',                N'Crear y modificar tarifarios de publicidad',  'administracion'),
                ('administracion:medios:gestionar',     N'Gestionar medios publicitarios',      N'Alta y configuracion de medios',              'administracion'),
                ('administracion:auditoria:ver',        N'Ver logs de auditoria',               N'Acceso al dashboard de auditoria',            'administracion')
            ) AS s (Codigo, Nombre, Descripcion, Modulo)
            ON t.Codigo = s.Codigo
            WHEN NOT MATCHED BY TARGET THEN
                INSERT (Codigo, Nombre, Descripcion, Modulo)
                VALUES (s.Codigo, s.Nombre, s.Descripcion, s.Modulo);
            """;
        await _connection.ExecuteAsync(sql);
    }

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

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

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

        // V005 seeds 18 canonical permisos + V007 (UDT-006) adds 3 admin permisos
        // + V011 (ADM-001) adds 'administracion:secciones:gestionar'
        // + V013 (ADM-008) adds 'administracion:puntos_de_venta:gestionar'
        // + V014 (ADM-009) adds 'administracion:fiscal:gestionar'
        // + V016 (CAT-001) adds 'catalogo:rubros:gestionar'
        // + V017 (PRD-001) adds 'catalogo:tipos:gestionar'
        // + V018 (PRD-002) adds 'catalogo:productos:gestionar' = 27 total
        Assert.Equal(27, list.Count);
    }

    [Fact]
    public async Task ListAsync_ContainsExpectedCodigos()
    {
        var list = await _repository.ListAsync();
        var codigos = list.Select(p => p.Codigo).ToHashSet();

        Assert.Contains("ventas:contado:crear", codigos);
        Assert.Contains("ventas:contado:facturar", codigos);
        Assert.Contains("administracion:usuarios:gestionar", codigos);
        Assert.Contains("administracion:auditoria:ver", codigos);
    }

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

        foreach (var p in list)
        {
            Assert.False(string.IsNullOrWhiteSpace(p.Codigo));
            Assert.False(string.IsNullOrWhiteSpace(p.Nombre));
        }
    }

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

    [Fact]
    public async Task GetByCodigoAsync_ExistingCodigo_ReturnsPermiso()
    {
        var permiso = await _repository.GetByCodigoAsync("ventas:contado:crear");

        Assert.NotNull(permiso);
        Assert.Equal("ventas:contado:crear", permiso!.Codigo);
        Assert.False(string.IsNullOrWhiteSpace(permiso.Nombre));
    }

    [Fact]
    public async Task GetByCodigoAsync_AnotherExistingCodigo_ReturnsCorrectPermiso()
    {
        var permiso = await _repository.GetByCodigoAsync("administracion:usuarios:gestionar");

        Assert.NotNull(permiso);
        Assert.Equal("administracion:usuarios:gestionar", permiso!.Codigo);
    }

    [Fact]
    public async Task GetByCodigoAsync_NonExistentCodigo_ReturnsNull()
    {
        var permiso = await _repository.GetByCodigoAsync("permiso:inexistente:xyz");

        Assert.Null(permiso);
    }

    // ── GetByCodigosAsync ────────────────────────────────────────────────────

    [Fact]
    public async Task GetByCodigosAsync_ThreeValidCodigos_ReturnsThreeEntities()
    {
        var codigos = new[]
        {
            "ventas:contado:crear",
            "ventas:contado:facturar",
            "textos:editar"
        };

        var result = await _repository.GetByCodigosAsync(codigos);

        Assert.Equal(3, result.Count);
        var returnedCodigos = result.Select(p => p.Codigo).ToHashSet();
        foreach (var c in codigos)
            Assert.Contains(c, returnedCodigos);
    }

    [Fact]
    public async Task GetByCodigosAsync_MixedExistingAndNonExisting_ReturnsOnlyExisting()
    {
        var codigos = new[]
        {
            "ventas:contado:crear",
            "permiso:no:existe",
            "textos:editar"
        };

        var result = await _repository.GetByCodigosAsync(codigos);

        // Only 2 of 3 exist
        Assert.Equal(2, result.Count);
        var returnedCodigos = result.Select(p => p.Codigo).ToHashSet();
        Assert.Contains("ventas:contado:crear", returnedCodigos);
        Assert.Contains("textos:editar", returnedCodigos);
    }

    [Fact]
    public async Task GetByCodigosAsync_EmptyList_ReturnsEmpty()
    {
        var result = await _repository.GetByCodigosAsync(Array.Empty<string>());

        Assert.Empty(result);
    }

    [Fact]
    public async Task GetByCodigosAsync_AllNonExisting_ReturnsEmpty()
    {
        var codigos = new[] { "no:existe:uno", "no:existe:dos" };

        var result = await _repository.GetByCodigosAsync(codigos);

        Assert.Empty(result);
    }
}
