using Microsoft.Data.Sqlite;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using ZB.MOM.WW.ScadaBridge.ConfigurationDatabase.Migrations;
using Xunit;

namespace ZB.MOM.WW.ScadaBridge.ConfigurationDatabase.Tests.Migrations;

/// <summary>
/// Idempotency tests for the <c>ResyncLdapGroupMappingSeed</c> migration (#70).
///
/// The original migration did an UNCONDITIONAL <c>InsertData</c> of the
/// Id=5 (SCADA-Viewers → Viewer) seed row. On any pre-existing DB that already
/// carried Id=5 that threw a PK violation and crash-looped startup (dev
/// auto-applies migrations). The fix makes the insert provider-aware and
/// idempotent (SQLite <c>INSERT OR IGNORE</c> / SQL Server guarded
/// <c>IF NOT EXISTS … SET IDENTITY_INSERT … INSERT</c>).
///
/// These tests drive the migration's <c>Up()</c> through the EF migrations SQL
/// pipeline configured for SQLite, so they exercise the exact
/// <c>migrationBuilder.Sql(...)</c> text the migration emits on the SQLite
/// branch — the branch a CI box without an MSSQL container can actually run.
/// The SQL Server branch is the same <c>migrationBuilder.Sql</c> text shape and
/// is exercised by the full-history MSSQL migration fixture
/// (<see cref="MsSqlMigrationFixture"/>) used by the other migration tests.
/// </summary>
public class ResyncLdapGroupMappingSeedMigrationTests
{
    private const string SqliteProvider = "Microsoft.EntityFrameworkCore.Sqlite";

    [Fact]
    public void Up_OnSqlite_InsertsRow_WhenIdFiveMissing()
    {
        using var connection = OpenSeededConnection(seedIdFive: false);

        ApplyMigrationUp(connection);

        Assert.Equal(1, CountId5(connection));
        Assert.Equal(("SCADA-Viewers", "Viewer"), ReadId5(connection));
    }

    [Fact]
    public void Up_OnSqlite_IsIdempotent_WhenIdFiveAlreadyExists()
    {
        // Pre-existing DB already carries Id=5 — the #70 crash scenario.
        using var connection = OpenSeededConnection(seedIdFive: true);

        // Applying the migration MUST NOT throw on the PK clash...
        var ex = Record.Exception(() => ApplyMigrationUp(connection));
        Assert.Null(ex);

        // ...and MUST NOT duplicate the row.
        Assert.Equal(1, CountId5(connection));
        Assert.Equal(("SCADA-Viewers", "Viewer"), ReadId5(connection));
    }

    [Fact]
    public void Up_OnSqlite_ReplayingTwice_IsStillIdempotent()
    {
        using var connection = OpenSeededConnection(seedIdFive: false);

        ApplyMigrationUp(connection);
        var ex = Record.Exception(() => ApplyMigrationUp(connection));

        Assert.Null(ex);
        Assert.Equal(1, CountId5(connection));
    }

    // --- harness -----------------------------------------------------------

    /// <summary>
    /// Opens a shared in-memory SQLite connection holding only the columns this
    /// migration touches and (optionally) a pre-existing Id=5 row.
    /// </summary>
    private static SqliteConnection OpenSeededConnection(bool seedIdFive)
    {
        var connection = new SqliteConnection("DataSource=:memory:");
        connection.Open();

        using (var create = connection.CreateCommand())
        {
            // Mirror the production PK shape: Id is the primary key. Re-inserting
            // an existing Id raises a UNIQUE/PK violation unless guarded.
            create.CommandText =
                "CREATE TABLE \"LdapGroupMappings\" (" +
                "  \"Id\" INTEGER NOT NULL CONSTRAINT \"PK_LdapGroupMappings\" PRIMARY KEY," +
                "  \"LdapGroupName\" TEXT NOT NULL," +
                "  \"Role\" TEXT NOT NULL);";
            create.ExecuteNonQuery();
        }

        if (seedIdFive)
        {
            using var seed = connection.CreateCommand();
            seed.CommandText =
                "INSERT INTO \"LdapGroupMappings\" (\"Id\", \"LdapGroupName\", \"Role\") " +
                "VALUES (5, 'SCADA-Viewers', 'Viewer');";
            seed.ExecuteNonQuery();
        }

        return connection;
    }

    /// <summary>
    /// Generates the real migration's <c>Up()</c> SQL via the EF migrations
    /// pipeline configured for SQLite (so <c>migrationBuilder.ActiveProvider</c>
    /// is the SQLite provider and the SQLite branch is taken), then runs every
    /// emitted command against the supplied connection.
    /// </summary>
    private static void ApplyMigrationUp(SqliteConnection connection)
    {
        var options = new DbContextOptionsBuilder<ScadaBridgeDbContext>()
            .UseSqlite(connection)
            .Options;

        using var context = new ScadaBridgeDbContext(options);

        var migration = new ResyncLdapGroupMappingSeed { ActiveProvider = SqliteProvider };
        var sqlGenerator = context.GetService<IMigrationsSqlGenerator>();
        var commands = sqlGenerator.Generate(migration.UpOperations);

        foreach (var command in commands)
        {
            using var cmd = connection.CreateCommand();
            cmd.CommandText = command.CommandText;
            cmd.ExecuteNonQuery();
        }
    }

    private static int CountId5(SqliteConnection connection)
    {
        using var cmd = connection.CreateCommand();
        cmd.CommandText = "SELECT COUNT(*) FROM \"LdapGroupMappings\" WHERE \"Id\" = 5;";
        return Convert.ToInt32(cmd.ExecuteScalar());
    }

    private static (string LdapGroupName, string Role) ReadId5(SqliteConnection connection)
    {
        using var cmd = connection.CreateCommand();
        cmd.CommandText = "SELECT \"LdapGroupName\", \"Role\" FROM \"LdapGroupMappings\" WHERE \"Id\" = 5;";
        using var reader = cmd.ExecuteReader();
        Assert.True(reader.Read());
        return (reader.GetString(0), reader.GetString(1));
    }
}
