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

namespace SIGCM2.Api.Tests.Admin;

/// <summary>
/// UDT-011 Batch 2 — V015 migration integration tests.
/// Validates:
///   REQ-DB-VIEWS-001 : Vista dbo.v_AuditEvent_Local existe tras aplicar V015.
///   REQ-DB-VIEWS-002 : Vista dbo.v_SecurityEvent_Local existe tras aplicar V015.
///   REQ-DB-VIEWS-003 : OccurredAtLocal retorna offset -03:00 (Argentina Standard Time).
///   REQ-DB-VIEWS-004 : V015 es idempotente (re-ejecución no falla ni duplica vistas).
///   REQ-DB-VIEWS-005 : V015_ROLLBACK elimina ambas vistas.
///
/// NOTA: Esta suite opera directamente sobre SIGCM2_Test con Dapper.
///       NO usa WebApplicationFactory (es test de migración pura, no API).
///       La migración se aplica via SqlTestFixture.EnsureV015SchemaAsync().
/// </summary>
[Collection("ApiIntegration")]
public sealed class V015MigrationTests : IClassFixture<SIGCM2.TestSupport.TestWebAppFactory>
{
    private const string ConnectionString = TestConnectionStrings.ApiTestDb;

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

    // ── REQ-DB-VIEWS-001 ─────────────────────────────────────────────────────

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

        var objectId = await conn.ExecuteScalarAsync<int?>("""
            SELECT OBJECT_ID('dbo.v_AuditEvent_Local', 'V')
            """);

        objectId.Should().NotBeNull("dbo.v_AuditEvent_Local debe existir tras aplicar V015");
    }

    // ── REQ-DB-VIEWS-002 ─────────────────────────────────────────────────────

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

        var objectId = await conn.ExecuteScalarAsync<int?>("""
            SELECT OBJECT_ID('dbo.v_SecurityEvent_Local', 'V')
            """);

        objectId.Should().NotBeNull("dbo.v_SecurityEvent_Local debe existir tras aplicar V015");
    }

    // ── REQ-DB-VIEWS-003 ─────────────────────────────────────────────────────
    // Inserta 1 fila con OccurredAt = 2026-05-01 01:30:00 UTC
    // Espera OccurredAtLocal con offset -03:00 → 2026-04-30 22:30:00-03:00

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

        // Insert a test row with known OccurredAt UTC value.
        // OccurredAt = 2026-05-01 01:30:00 UTC → Argentina (UTC-3) = 2026-04-30 22:30:00
        var occurredAt = new DateTime(2026, 5, 1, 1, 30, 0, DateTimeKind.Utc);

        long insertedId;
        try
        {
            insertedId = await conn.ExecuteScalarAsync<long>("""
                INSERT INTO dbo.AuditEvent
                    (OccurredAt, ActorUserId, ActorRoleId, Action, TargetType, TargetId, CorrelationId, IpAddress, UserAgent, Metadata)
                VALUES
                    (@OccurredAt, NULL, NULL, 'test.v015', 'Test', '0', NULL, '127.0.0.1', NULL, NULL);
                SELECT SCOPE_IDENTITY();
                """, new { OccurredAt = occurredAt });
        }
        catch
        {
            // If insert fails (e.g. CHECK constraint on Action), skip — vista test only
            throw;
        }

        var result = await conn.QuerySingleOrDefaultAsync<(DateTimeOffset OccurredAtLocal, DateTime OccurredAt)>("""
            SELECT TOP 1 OccurredAtLocal, OccurredAt
            FROM dbo.v_AuditEvent_Local
            WHERE Action = 'test.v015'
              AND OccurredAt = @OccurredAt
            """, new { OccurredAt = occurredAt });

        result.OccurredAt.Should().Be(occurredAt, "OccurredAt debe ser el UTC insertado");

        // Argentina Standard Time = UTC-3 (sin DST desde 2009). Offset fijo = -03:00.
        result.OccurredAtLocal.Offset.TotalHours.Should().Be(-3,
            "OccurredAtLocal debe tener offset -03:00 (Argentina Standard Time)");

        // 2026-05-01 01:30 UTC → 2026-04-30 22:30 ART
        result.OccurredAtLocal.DateTime.Date.Should().Be(new DateTime(2026, 4, 30),
            "La fecha local Argentina debe ser 2026-04-30 (día anterior al UTC)");
        result.OccurredAtLocal.Hour.Should().Be(22, "La hora local Argentina debe ser 22");
        result.OccurredAtLocal.Minute.Should().Be(30, "Los minutos deben ser 30");
    }

    // ── REQ-DB-VIEWS-004 — Idempotencia ──────────────────────────────────────

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

        // Re-ejecutar el DDL idempotente de V015 por segunda vez — NO debe fallar.
        // La vista ya existe (creada en EnsureV015SchemaAsync), el guard IF OBJECT_ID IS NULL
        // debe cortocircuitar sin error.
        await conn.ExecuteAsync("""
            IF OBJECT_ID('dbo.v_AuditEvent_Local', 'V') IS NULL
            BEGIN
                EXEC('
                    CREATE VIEW dbo.v_AuditEvent_Local AS
                    SELECT
                        Id,
                        OccurredAt,
                        OccurredAt AT TIME ZONE ''UTC'' AT TIME ZONE ''Argentina Standard Time'' AS OccurredAtLocal,
                        ActorUserId,
                        ActorRoleId,
                        Action,
                        TargetType,
                        TargetId,
                        CorrelationId,
                        IpAddress,
                        UserAgent,
                        Metadata
                    FROM dbo.AuditEvent;
                ');
            END
            """);

        await conn.ExecuteAsync("""
            IF OBJECT_ID('dbo.v_SecurityEvent_Local', 'V') IS NULL
            BEGIN
                EXEC('
                    CREATE VIEW dbo.v_SecurityEvent_Local AS
                    SELECT
                        Id,
                        OccurredAt,
                        OccurredAt AT TIME ZONE ''UTC'' AT TIME ZONE ''Argentina Standard Time'' AS OccurredAtLocal,
                        ActorUserId,
                        AttemptedUsername,
                        SessionId,
                        Action,
                        Result,
                        FailureReason,
                        IpAddress,
                        UserAgent,
                        Metadata
                    FROM dbo.SecurityEvent;
                ');
            END
            """);

        // Ambas vistas deben seguir existiendo.
        var auditExists = await conn.ExecuteScalarAsync<int?>("""
            SELECT OBJECT_ID('dbo.v_AuditEvent_Local', 'V')
            """);

        var secExists = await conn.ExecuteScalarAsync<int?>("""
            SELECT OBJECT_ID('dbo.v_SecurityEvent_Local', 'V')
            """);

        auditExists.Should().NotBeNull("v_AuditEvent_Local debe seguir existiendo tras re-ejecución idempotente");
        secExists.Should().NotBeNull("v_SecurityEvent_Local debe seguir existiendo tras re-ejecución idempotente");
    }

    // ── REQ-DB-VIEWS-005 — Rollback ───────────────────────────────────────────

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

        // Apply rollback DDL (simula V015_ROLLBACK.sql).
        await conn.ExecuteAsync("""
            IF OBJECT_ID('dbo.v_AuditEvent_Local', 'V') IS NOT NULL
                DROP VIEW dbo.v_AuditEvent_Local;
            """);

        await conn.ExecuteAsync("""
            IF OBJECT_ID('dbo.v_SecurityEvent_Local', 'V') IS NOT NULL
                DROP VIEW dbo.v_SecurityEvent_Local;
            """);

        var auditExists = await conn.ExecuteScalarAsync<int?>("""
            SELECT OBJECT_ID('dbo.v_AuditEvent_Local', 'V')
            """);

        var secExists = await conn.ExecuteScalarAsync<int?>("""
            SELECT OBJECT_ID('dbo.v_SecurityEvent_Local', 'V')
            """);

        auditExists.Should().BeNull("v_AuditEvent_Local debe ser NULL tras el rollback");
        secExists.Should().BeNull("v_SecurityEvent_Local debe ser NULL tras el rollback");

        // IMPORTANT: Re-create views after rollback test so remaining tests still pass.
        // (This test tears down — re-apply V015 DDL to restore the fixture state.)
        await conn.ExecuteAsync("""
            IF OBJECT_ID('dbo.v_AuditEvent_Local', 'V') IS NULL
            BEGIN
                EXEC('
                    CREATE VIEW dbo.v_AuditEvent_Local AS
                    SELECT
                        Id,
                        OccurredAt,
                        OccurredAt AT TIME ZONE ''UTC'' AT TIME ZONE ''Argentina Standard Time'' AS OccurredAtLocal,
                        ActorUserId,
                        ActorRoleId,
                        Action,
                        TargetType,
                        TargetId,
                        CorrelationId,
                        IpAddress,
                        UserAgent,
                        Metadata
                    FROM dbo.AuditEvent;
                ');
            END
            """);

        await conn.ExecuteAsync("""
            IF OBJECT_ID('dbo.v_SecurityEvent_Local', 'V') IS NULL
            BEGIN
                EXEC('
                    CREATE VIEW dbo.v_SecurityEvent_Local AS
                    SELECT
                        Id,
                        OccurredAt,
                        OccurredAt AT TIME ZONE ''UTC'' AT TIME ZONE ''Argentina Standard Time'' AS OccurredAtLocal,
                        ActorUserId,
                        AttemptedUsername,
                        SessionId,
                        Action,
                        Result,
                        FailureReason,
                        IpAddress,
                        UserAgent,
                        Metadata
                    FROM dbo.SecurityEvent;
                ');
            END
            """);
    }
}
