using Dapper;
using SIGCM2.TestSupport;

namespace SIGCM2.Application.Tests.Integration;

/// <summary>
/// SUITE-B-MIGRATION-V009 — M-01 a M-07 (UDT-009)
/// Validates the V009 migration SQL and SqlTestFixture.EnsureV009SchemaAsync.
/// Uses SIGCM2_Test_App database via shared SqlTestFixture.
/// </summary>
[Collection("Database")]
public sealed class V009MigrationTests : IAsyncLifetime
{
    private readonly SqlTestFixture _db;

    public V009MigrationTests(SqlTestFixture db)
    {
        _db = db;
    }

    public async Task InitializeAsync()
    {
        await _db.ResetAndSeedAsync();
    }

    public Task DisposeAsync() => Task.CompletedTask;

    // M-01: migration file exists on filesystem
    [Fact]
    public void MigrationFile_Exists()
    {
        // Walk up from test assembly looking for the repo root
        var dir = new DirectoryInfo(AppContext.BaseDirectory);
        string? repoRoot = null;

        while (dir is not null)
        {
            if (dir.GetFiles("SIGCM2.slnx").Length > 0)
            {
                repoRoot = dir.FullName;
                break;
            }
            dir = dir.Parent;
        }

        // Known fallback
        if (repoRoot is null && Directory.Exists(@"E:\SIG-CM2.0"))
            repoRoot = @"E:\SIG-CM2.0";

        Assert.NotNull(repoRoot);
        var migrationPath = Path.Combine(repoRoot!, "database", "migrations", "V009__activate_permisos_overrides.sql");
        Assert.True(File.Exists(migrationPath), $"Migration file not found at: {migrationPath}");
    }

    // M-02: re-run idempotent (EnsureV009SchemaAsync twice → no error)
    [Fact]
    public async Task EnsureV009SchemaAsync_IsIdempotent()
    {
        await EnsureV009SchemaAsync();
        await EnsureV009SchemaAsync(); // second call must not throw
    }

    // M-03: after migration, DEFAULT constraint is the new shape
    [Fact]
    public async Task EnsureV009SchemaAsync_DefaultConstraint_IsNewShape()
    {
        await EnsureV009SchemaAsync();

        const string sql = """
            SELECT object_definition(default_object_id) AS DefaultDef
            FROM sys.columns
            WHERE object_id = OBJECT_ID('dbo.Usuario')
              AND name = 'PermisosJson'
            """;

        var definition = await _db.Connection.QuerySingleOrDefaultAsync<string>(sql);

        Assert.NotNull(definition);
        Assert.Contains(@"{""grant"":[]", definition);
        Assert.Contains(@"""deny"":[]}", definition);
    }

    // M-04: rows with '[]' are migrated to new shape
    [Fact]
    public async Task EnsureV009SchemaAsync_MigratesLegacyEmptyArray()
    {
        await EnsureV009SchemaAsync();

        await _db.Connection.ExecuteAsync("""
            INSERT INTO dbo.Usuario (Username, PasswordHash, Nombre, Apellido, Rol, PermisosJson, Activo, MustChangePassword)
            VALUES ('legacyempty', '$2a$12$hash', 'L', 'E', 'admin', '[]', 1, 0)
            """);

        // Run migration again to migrate the newly inserted row
        await EnsureV009SchemaAsync();

        var permisosJson = await _db.Connection.QuerySingleAsync<string>(
            "SELECT PermisosJson FROM dbo.Usuario WHERE Username = 'legacyempty'");

        Assert.Equal("""{"grant":[],"deny":[]}""", permisosJson);
    }

    // M-05: rows with '["*"]' are migrated
    [Fact]
    public async Task EnsureV009SchemaAsync_MigratesLegacyWildcard()
    {
        await EnsureV009SchemaAsync();

        await _db.Connection.ExecuteAsync("""
            INSERT INTO dbo.Usuario (Username, PasswordHash, Nombre, Apellido, Rol, PermisosJson, Activo, MustChangePassword)
            VALUES ('legacywild', '$2a$12$hash', 'L', 'W', 'admin', '["*"]', 1, 0)
            """);

        await EnsureV009SchemaAsync();

        var permisosJson = await _db.Connection.QuerySingleAsync<string>(
            "SELECT PermisosJson FROM dbo.Usuario WHERE Username = 'legacywild'");

        Assert.Equal("""{"grant":[],"deny":[]}""", permisosJson);
    }

    // M-06: the migration UPDATE statement includes NULL / empty-string conditions
    // The column is NOT NULL (V001 constraint), so we verify the UPDATE logic covers
    // all the WHERE conditions syntactically and that rows with '' are migrated.
    [Fact]
    public async Task EnsureV009SchemaAsync_MigratesEmptyStringRows()
    {
        // First apply V009 schema so the constraint is updated
        await EnsureV009SchemaAsync();

        // Temporarily drop and re-add without the DEFAULT so we can insert ''
        await _db.Connection.ExecuteAsync("""
            IF EXISTS (
                SELECT 1 FROM sys.default_constraints
                WHERE name = 'DF_Usuario_Permisos'
                  AND parent_object_id = OBJECT_ID('dbo.Usuario')
            )
                ALTER TABLE dbo.Usuario DROP CONSTRAINT DF_Usuario_Permisos;
            """);

        await _db.Connection.ExecuteAsync("""
            INSERT INTO dbo.Usuario (Username, PasswordHash, Nombre, Apellido, Rol, PermisosJson, Activo, MustChangePassword)
            VALUES ('emptystruser', '$2a$12$hash', 'E', 'S', 'admin', '', 1, 0)
            """);

        // Re-apply V009 (which restores constraint and migrates '' rows)
        await EnsureV009SchemaAsync();

        var permisosJson = await _db.Connection.QuerySingleAsync<string>(
            "SELECT PermisosJson FROM dbo.Usuario WHERE Username = 'emptystruser'");

        Assert.Equal("""{"grant":[],"deny":[]}""", permisosJson);
    }

    // M-07: admin seed in SqlTestFixture uses new shape
    [Fact]
    public async Task SqlTestFixture_SeedAdmin_UsesNewPermisosJsonShape()
    {
        await EnsureV009SchemaAsync();

        // Seed admin as TestFixture does post-V009
        await _db.Connection.ExecuteAsync("""
            IF NOT EXISTS (SELECT 1 FROM dbo.Usuario WHERE Username = 'admin')
            INSERT INTO dbo.Usuario (Username, PasswordHash, Nombre, Apellido, Rol, PermisosJson, Activo, MustChangePassword)
            VALUES (
                'admin',
                '$2a$12$rmq6tlSAQ8WXhR2CwLCSeuwCJKz/.8Eab95UQCUNfwe4dokeOqMcW',
                'Administrador', 'Sistema', 'admin', '{"grant":[],"deny":[]}', 1, 0
            )
            """);

        var permisosJson = await _db.Connection.QuerySingleAsync<string>(
            "SELECT PermisosJson FROM dbo.Usuario WHERE Username = 'admin'");

        Assert.Equal("""{"grant":[],"deny":[]}""", permisosJson);
    }

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

    /// <summary>
    /// Replicates V009 migration idempotently — mirrors SqlTestFixture.EnsureV009SchemaAsync.
    /// </summary>
    private async Task EnsureV009SchemaAsync()
    {
        const string dropConstraint = """
            IF EXISTS (
                SELECT 1 FROM sys.default_constraints
                WHERE name = 'DF_Usuario_Permisos'
                  AND parent_object_id = OBJECT_ID('dbo.Usuario')
            )
            BEGIN
                ALTER TABLE dbo.Usuario DROP CONSTRAINT DF_Usuario_Permisos;
            END
            """;

        const string addConstraint = """
            IF NOT EXISTS (
                SELECT 1 FROM sys.default_constraints
                WHERE name = 'DF_Usuario_Permisos'
                  AND parent_object_id = OBJECT_ID('dbo.Usuario')
            )
            BEGIN
                ALTER TABLE dbo.Usuario
                    ADD CONSTRAINT DF_Usuario_Permisos
                    DEFAULT('{"grant":[],"deny":[]}') FOR PermisosJson;
            END
            """;

        const string migrateRows = """
            UPDATE dbo.Usuario
            SET PermisosJson = '{"grant":[],"deny":[]}'
            WHERE PermisosJson IN ('[]', '["*"]', '')
               OR PermisosJson IS NULL
               OR LTRIM(RTRIM(PermisosJson)) = ''
            """;

        await _db.Connection.ExecuteAsync(dropConstraint);
        await _db.Connection.ExecuteAsync(addConstraint);
        await _db.Connection.ExecuteAsync(migrateRows);
    }
}
