using Dapper;
using FluentAssertions;
using Microsoft.Data.SqlClient;
using Xunit;

namespace SIGCM2.Api.Tests.Admin;

/// <summary>
/// ADM-009 Batch 1 — V014 migration integration tests.
/// Validates:
///   REQ-SEED-001 : TipoDeIva seed genera exactamente 4 filas canónicas (EXENTO, NO_GRAVADO, IVA_105, IVA_21).
///   REQ-SEED-002 : IngresosBrutos seed genera exactamente 25 filas (24 provincias INDEC + CABA).
///   REQ-SEED-003 : Re-ejecución de V014 NO duplica filas (idempotencia MERGE).
///   REQ-TEMPORAL-001 : SYSTEM_VERSIONING ON en TipoDeIva e IngresosBrutos (temporal_type = 2).
///   REQ-FISCAL-AUTH-002 : Permiso 'administracion:fiscal:gestionar' existe y está asignado al rol admin.
///
/// NOTA: Esta suite opera directamente sobre SIGCM2_Test con Dapper.
///       NO usa WebApplicationFactory (es test de migración pura, no API).
///       La migración debe haberse aplicado previamente a SIGCM2_Test via sqlcmd o SqlTestFixture.
/// </summary>
[Collection("ApiIntegration")]
public sealed class V014MigrationTests : IClassFixture<SIGCM2.TestSupport.TestWebAppFactory>
{
    private const string ConnectionString = TestConnectionStrings.ApiTestDb;

    public V014MigrationTests(SIGCM2.TestSupport.TestWebAppFactory _)
    {
        // Depend on the factory so SqlTestFixture.InitializeAsync runs
        // (ensures V014 schema is present via EnsureV014SchemaAsync).
    }

    // ── REQ-TEMPORAL-001 ──────────────────────────────────────────────────────

    [Fact]
    public async Task TipoDeIva_SystemVersioning_IsActive()
    {
        await using var conn = new SqlConnection(ConnectionString);
        await conn.OpenAsync();

        var temporalType = await conn.ExecuteScalarAsync<int>("""
            SELECT temporal_type
            FROM sys.tables
            WHERE object_id = OBJECT_ID('dbo.TipoDeIva')
            """);

        temporalType.Should().Be(2, "TipoDeIva debe tener SYSTEM_VERSIONING = ON (temporal_type = 2)");
    }

    [Fact]
    public async Task TipoDeIva_History_Exists()
    {
        await using var conn = new SqlConnection(ConnectionString);
        await conn.OpenAsync();

        var exists = await conn.ExecuteScalarAsync<int>("""
            SELECT COUNT(1) FROM sys.tables
            WHERE name = 'TipoDeIva_History'
              AND schema_id = SCHEMA_ID('dbo')
            """);

        exists.Should().Be(1, "dbo.TipoDeIva_History debe existir como tabla de historial temporal");
    }

    [Fact]
    public async Task IngresosBrutos_SystemVersioning_IsActive()
    {
        await using var conn = new SqlConnection(ConnectionString);
        await conn.OpenAsync();

        var temporalType = await conn.ExecuteScalarAsync<int>("""
            SELECT temporal_type
            FROM sys.tables
            WHERE object_id = OBJECT_ID('dbo.IngresosBrutos')
            """);

        temporalType.Should().Be(2, "IngresosBrutos debe tener SYSTEM_VERSIONING = ON (temporal_type = 2)");
    }

    [Fact]
    public async Task IngresosBrutos_History_Exists()
    {
        await using var conn = new SqlConnection(ConnectionString);
        await conn.OpenAsync();

        var exists = await conn.ExecuteScalarAsync<int>("""
            SELECT COUNT(1) FROM sys.tables
            WHERE name = 'IngresosBrutos_History'
              AND schema_id = SCHEMA_ID('dbo')
            """);

        exists.Should().Be(1, "dbo.IngresosBrutos_History debe existir como tabla de historial temporal");
    }

    // ── REQ-SEED-001 ──────────────────────────────────────────────────────────
    // NOTE: Filters use Codigo IN (...) + PredecesorId IS NULL + VigenciaDesde='2020-01-01'
    // to isolate ONLY the 4 canonical seed rows, ignoring rows inserted by repo integration tests.

    [Fact]
    public async Task TipoDeIva_Seed_HasExactly4Rows()
    {
        await using var conn = new SqlConnection(ConnectionString);
        await conn.OpenAsync();

        var count = await conn.ExecuteScalarAsync<int>("""
            SELECT COUNT(1) FROM dbo.TipoDeIva
            WHERE Codigo IN ('EXENTO', 'NO_GRAVADO', 'IVA_105', 'IVA_21')
              AND PredecesorId IS NULL
              AND VigenciaDesde = CAST('2020-01-01' AS DATE)
            """);

        count.Should().Be(4, "El seed de V014 debe generar exactamente 4 TipoDeIva canónicos");
    }

    [Fact]
    public async Task TipoDeIva_Seed_HasCorrectCodigos()
    {
        await using var conn = new SqlConnection(ConnectionString);
        await conn.OpenAsync();

        var codigos = (await conn.QueryAsync<string>("""
            SELECT Codigo FROM dbo.TipoDeIva
            WHERE Codigo IN ('EXENTO', 'NO_GRAVADO', 'IVA_105', 'IVA_21')
              AND PredecesorId IS NULL
              AND VigenciaDesde = CAST('2020-01-01' AS DATE)
            ORDER BY Codigo
            """)).ToList();

        codigos.Should().BeEquivalentTo(
            new[] { "EXENTO", "IVA_105", "IVA_21", "NO_GRAVADO" },
            "Los 4 códigos canónicos deben existir en TipoDeIva");
    }

    [Fact]
    public async Task TipoDeIva_Seed_Porcentajes_AreCorrect()
    {
        await using var conn = new SqlConnection(ConnectionString);
        await conn.OpenAsync();

        var rows = (await conn.QueryAsync<(string Codigo, decimal Porcentaje)>("""
            SELECT Codigo, Porcentaje FROM dbo.TipoDeIva
            WHERE Codigo IN ('EXENTO', 'NO_GRAVADO', 'IVA_105', 'IVA_21')
              AND PredecesorId IS NULL
              AND VigenciaDesde = CAST('2020-01-01' AS DATE)
            ORDER BY Codigo
            """)).ToList();

        rows.Should().ContainSingle(r => r.Codigo == "EXENTO" && r.Porcentaje == 0m);
        rows.Should().ContainSingle(r => r.Codigo == "NO_GRAVADO" && r.Porcentaje == 0m);
        rows.Should().ContainSingle(r => r.Codigo == "IVA_105" && r.Porcentaje == 10.5m);
        rows.Should().ContainSingle(r => r.Codigo == "IVA_21" && r.Porcentaje == 21m);
    }

    [Fact]
    public async Task TipoDeIva_Seed_AllRowsActive_PredecesorNull_VigenciaHastaNull()
    {
        await using var conn = new SqlConnection(ConnectionString);
        await conn.OpenAsync();

        var invalidRows = await conn.ExecuteScalarAsync<int>("""
            SELECT COUNT(1) FROM dbo.TipoDeIva
            WHERE Codigo IN ('EXENTO', 'NO_GRAVADO', 'IVA_105', 'IVA_21')
              AND VigenciaDesde = CAST('2020-01-01' AS DATE)
              AND PredecesorId IS NULL
              AND (Activo = 0 OR VigenciaHasta IS NOT NULL)
            """);

        invalidRows.Should().Be(0,
            "Las 4 filas seed canónicas deben tener Activo=1, PredecesorId=NULL, VigenciaHasta=NULL");
    }

    // ── REQ-SEED-002 ──────────────────────────────────────────────────────────
    // NOTE: Filters use Alicuota=0 + PredecesorId IS NULL + VigenciaDesde='2020-01-01'
    // to isolate ONLY the 24 canonical seed rows, ignoring rows inserted by repo integration tests.
    // Seed provinces are stored as PascalCase matching enum ProvinciaArgentina.ToString() (T700 cleanup).

    [Fact]
    public async Task IngresosBrutos_Seed_HasExactly24Rows()
    {
        await using var conn = new SqlConnection(ConnectionString);
        await conn.OpenAsync();

        // Design canónico: 23 provincias INDEC + CABA = 24 jurisdicciones.
        // La lista del design incluye CABA (CiudadAutonomaDeBuenosAires) como elemento propio.
        // REQ-SEED-002 especifica "25" pero la lista canónica del design tiene 24 entradas únicas.
        // DISCOVERY: posible discrepancia spec vs. design — anotado en apply-progress.
        // Implementamos lo que la lista del design establece explícitamente: 24 filas.
        var count = await conn.ExecuteScalarAsync<int>("""
            SELECT COUNT(1) FROM dbo.IngresosBrutos
            WHERE Alicuota = 0
              AND PredecesorId IS NULL
              AND VigenciaDesde = CAST('2020-01-01' AS DATE)
            """);

        count.Should().Be(24, "El seed de V014 debe generar 24 IngresosBrutos (23 provincias INDEC + CABA)");
    }

    [Fact]
    public async Task IngresosBrutos_Seed_HasAllProvincias()
    {
        await using var conn = new SqlConnection(ConnectionString);
        await conn.OpenAsync();

        var provincias = (await conn.QueryAsync<string>("""
            SELECT Provincia FROM dbo.IngresosBrutos
            WHERE Alicuota = 0
              AND PredecesorId IS NULL
              AND VigenciaDesde = CAST('2020-01-01' AS DATE)
            ORDER BY Provincia
            """)).ToList();

        // Lista canónica del design ADM-009: 23 provincias argentinas INDEC + CABA = 24
        // Stored as PascalCase matching ProvinciaArgentina enum values (T700 cleanup).
        var expectedCanonical = new[]
        {
            "BuenosAires", "CiudadAutonomaDeBuenosAires", "Catamarca", "Chaco", "Chubut",
            "Cordoba", "Corrientes", "EntreRios", "Formosa", "Jujuy",
            "LaPampa", "LaRioja", "Mendoza", "Misiones", "Neuquen",
            "RioNegro", "Salta", "SanJuan", "SanLuis", "SantaCruz",
            "SantaFe", "SantiagoDelEstero", "TierraDelFuego", "Tucuman"
        };

        provincias.Should().Contain("CiudadAutonomaDeBuenosAires",
            "CABA debe estar almacenada como CiudadAutonomaDeBuenosAires (PascalCase enum)");
        provincias.Should().Contain("BuenosAires",
            "Buenos Aires (provincia) debe estar como BuenosAires (PascalCase enum)");
        foreach (var prov in expectedCanonical)
            provincias.Should().Contain(prov, $"Provincia {prov} debe estar en el seed (PascalCase)");
    }

    [Fact]
    public async Task IngresosBrutos_Seed_AlicuotaZero_AllRows()
    {
        await using var conn = new SqlConnection(ConnectionString);
        await conn.OpenAsync();

        // Verify all 24 seed rows (VigenciaDesde='2020-01-01', PredecesorId IS NULL) have Alicuota=0.
        var nonZero = await conn.ExecuteScalarAsync<int>("""
            SELECT COUNT(1) FROM dbo.IngresosBrutos
            WHERE PredecesorId IS NULL
              AND VigenciaDesde = CAST('2020-01-01' AS DATE)
              AND Alicuota <> 0
            """);

        nonZero.Should().Be(0, "Las 24 filas seed de IngresosBrutos deben tener Alicuota=0 (placeholder)");
    }

    [Fact]
    public async Task IngresosBrutos_Seed_AllRowsActive_PredecesorNull_VigenciaHastaNull()
    {
        await using var conn = new SqlConnection(ConnectionString);
        await conn.OpenAsync();

        var invalidRows = await conn.ExecuteScalarAsync<int>("""
            SELECT COUNT(1) FROM dbo.IngresosBrutos
            WHERE VigenciaDesde = CAST('2020-01-01' AS DATE)
              AND PredecesorId IS NULL
              AND Alicuota = 0
              AND (Activo = 0 OR VigenciaHasta IS NOT NULL)
            """);

        invalidRows.Should().Be(0,
            "Las 24 filas seed deben tener Activo=1, PredecesorId=NULL, VigenciaHasta=NULL");
    }

    // ── REQ-FISCAL-AUTH-002 ───────────────────────────────────────────────────

    [Fact]
    public async Task Permiso_AdministracionFiscalGestionar_Exists()
    {
        await using var conn = new SqlConnection(ConnectionString);
        await conn.OpenAsync();

        var count = await conn.ExecuteScalarAsync<int>("""
            SELECT COUNT(1) FROM dbo.Permiso
            WHERE Codigo = 'administracion:fiscal:gestionar'
            """);

        count.Should().Be(1, "El permiso 'administracion:fiscal:gestionar' debe existir en dbo.Permiso");
    }

    [Fact]
    public async Task Permiso_AdministracionFiscalGestionar_AsignadoARolAdmin()
    {
        await using var conn = new SqlConnection(ConnectionString);
        await conn.OpenAsync();

        var count = await conn.ExecuteScalarAsync<int>("""
            SELECT COUNT(1)
            FROM dbo.RolPermiso rp
            JOIN dbo.Rol r ON r.Id = rp.RolId
            JOIN dbo.Permiso p ON p.Id = rp.PermisoId
            WHERE r.Codigo = 'admin'
              AND p.Codigo = 'administracion:fiscal:gestionar'
            """);

        count.Should().Be(1,
            "El permiso 'administracion:fiscal:gestionar' debe estar asignado al rol 'admin'");
    }

    // ── REQ-SEED-003 — Idempotencia ───────────────────────────────────────────

    [Fact]
    public async Task V014_Idempotencia_TipoDeIva_NoSeDuplica()
    {
        await using var conn = new SqlConnection(ConnectionString);
        await conn.OpenAsync();

        // Aplicar el MERGE seed manualmente una segunda vez (simula re-ejecución de V014)
        await conn.ExecuteAsync("""
            MERGE dbo.TipoDeIva AS t
            USING (VALUES
                ('EXENTO',     N'Exento de IVA',               CAST(0    AS DECIMAL(5,2)), CAST(0 AS BIT), '2020-01-01'),
                ('NO_GRAVADO', N'No gravado',                   CAST(0    AS DECIMAL(5,2)), CAST(0 AS BIT), '2020-01-01'),
                ('IVA_105',    N'IVA 10.5%',                   CAST(10.5 AS DECIMAL(5,2)), CAST(1 AS BIT), '2020-01-01'),
                ('IVA_21',     N'IVA 21%',                     CAST(21   AS DECIMAL(5,2)), CAST(1 AS BIT), '2020-01-01')
            ) AS s (Codigo, Descripcion, Porcentaje, AplicaIVA, VigenciaDesde)
            ON t.Codigo = s.Codigo AND t.PredecesorId IS NULL
            WHEN NOT MATCHED BY TARGET THEN
                INSERT (Codigo, Descripcion, Porcentaje, AplicaIVA, Activo, VigenciaDesde, VigenciaHasta, PredecesorId)
                VALUES (s.Codigo, s.Descripcion, s.Porcentaje, s.AplicaIVA, 1, s.VigenciaDesde, NULL, NULL);
            """);

        // Count only the 4 canonical seed rows — not test-inserted rows.
        var count = await conn.ExecuteScalarAsync<int>("""
            SELECT COUNT(1) FROM dbo.TipoDeIva
            WHERE Codigo IN ('EXENTO', 'NO_GRAVADO', 'IVA_105', 'IVA_21')
              AND PredecesorId IS NULL
              AND VigenciaDesde = CAST('2020-01-01' AS DATE)
            """);

        count.Should().Be(4, "Re-ejecutar el seed MERGE no debe duplicar filas en TipoDeIva");
    }

    [Fact]
    public async Task V014_Idempotencia_IngresosBrutos_NoSeDuplica()
    {
        await using var conn = new SqlConnection(ConnectionString);
        await conn.OpenAsync();

        // Re-aplicar el MERGE de provincias (simula re-ejecución de V014) — ahora con PascalCase.
        await conn.ExecuteAsync("""
            MERGE dbo.IngresosBrutos AS t
            USING (VALUES
                ('BuenosAires'),('CiudadAutonomaDeBuenosAires'),('Catamarca'),('Chaco'),('Chubut'),
                ('Cordoba'),('Corrientes'),('EntreRios'),('Formosa'),('Jujuy'),
                ('LaPampa'),('LaRioja'),('Mendoza'),('Misiones'),('Neuquen'),
                ('RioNegro'),('Salta'),('SanJuan'),('SanLuis'),('SantaCruz'),
                ('SantaFe'),('SantiagoDelEstero'),('TierraDelFuego'),('Tucuman')
            ) AS s (Provincia)
            ON t.Provincia = s.Provincia AND t.PredecesorId IS NULL
            WHEN NOT MATCHED BY TARGET THEN
                INSERT (Provincia, Descripcion, Alicuota, Activo, VigenciaDesde, VigenciaHasta, PredecesorId)
                VALUES (s.Provincia, N'Ingresos Brutos ' + s.Provincia, CAST(0 AS DECIMAL(5,2)), 1, '2020-01-01', NULL, NULL);
            """);

        // Count only the 24 canonical seed rows — not test-inserted rows.
        var count = await conn.ExecuteScalarAsync<int>("""
            SELECT COUNT(1) FROM dbo.IngresosBrutos
            WHERE Alicuota = 0
              AND PredecesorId IS NULL
              AND VigenciaDesde = CAST('2020-01-01' AS DATE)
            """);

        count.Should().Be(24, "Re-ejecutar el seed MERGE no debe duplicar filas en IngresosBrutos");
    }

    [Fact]
    public async Task V014_Idempotencia_Permiso_NoSeDuplica()
    {
        await using var conn = new SqlConnection(ConnectionString);
        await conn.OpenAsync();

        // Re-aplicar MERGE permiso (simula re-ejecución de V014)
        await conn.ExecuteAsync("""
            MERGE dbo.Permiso AS t
            USING (VALUES ('administracion:fiscal:gestionar', N'Gestionar tablas fiscales (IVA, IIBB)', 'administracion'))
                AS s (Codigo, Descripcion, Modulo)
            ON t.Codigo = s.Codigo
            WHEN NOT MATCHED BY TARGET THEN
                INSERT (Codigo, Nombre, Descripcion, Modulo)
                VALUES (s.Codigo, N'Gestionar tablas fiscales', s.Descripcion, s.Modulo);
            """);

        var count = await conn.ExecuteScalarAsync<int>("""
            SELECT COUNT(1) FROM dbo.Permiso
            WHERE Codigo = 'administracion:fiscal:gestionar'
            """);

        count.Should().Be(1, "Re-ejecutar el MERGE de Permiso no debe crear duplicados");
    }
}
