using Microsoft.Data.Sqlite;
using ZB.MOM.WW.ScadaBridge.SiteRuntime.Persistence;

namespace ZB.MOM.WW.ScadaBridge.SiteRuntime.Tests.Persistence;

/// <summary>
/// Pins <see cref="SiteStorageSchema"/> as the single owner of the site config DDL.
/// The Host applies this to a LocalDb-managed connection before
/// <c>RegisterReplicated</c> installs the capture triggers, so the shape it produces
/// is the shape that replicates.
/// </summary>
public class SiteStorageSchemaTests
{
    [Fact]
    public void Apply_IsIdempotent_AndCreatesEveryTable()
    {
        var path = Path.Combine(Path.GetTempPath(), $"site-schema-{Guid.NewGuid():N}.db");
        try
        {
            using var connection = new SqliteConnection($"Data Source={path}");
            connection.Open();

            SiteStorageSchema.Apply(connection);
            SiteStorageSchema.Apply(connection);   // second run must not throw

            // Every table is named explicitly on purpose — a loop asserting "9 tables"
            // would still pass if one were renamed.
            foreach (var table in new[]
                     {
                         "deployed_configurations", "static_attribute_overrides", "shared_scripts",
                         "external_systems", "database_connections", "notification_lists",
                         "data_connection_definitions", "smtp_configurations", "native_alarm_state",
                     })
            {
                Assert.True(TableExists(connection, table), $"missing table: {table}");
            }
        }
        finally
        {
            SqliteConnection.ClearAllPools();
            File.Delete(path);
        }
    }

    /// <summary>
    /// The migration columns exist to upgrade a database created by an older build in
    /// place. Asserting only against a freshly-created table would pass even if every
    /// <c>ALTER</c> were deleted, because <c>CREATE TABLE</c> already lists them — so
    /// this starts from the pre-migration shape and proves the migration path runs.
    /// </summary>
    [Fact]
    public void Apply_AddsMigrationColumns_ToALegacyDatabase()
    {
        var path = Path.Combine(Path.GetTempPath(), $"site-schema-legacy-{Guid.NewGuid():N}.db");
        try
        {
            using var connection = new SqliteConnection($"Data Source={path}");
            connection.Open();

            // The shapes as they existed before backup_configuration /
            // failover_retry_count / metadata_json / timeout_seconds were added.
            using (var legacy = connection.CreateCommand())
            {
                legacy.CommandText = """
                    CREATE TABLE data_connection_definitions (
                        name          TEXT PRIMARY KEY,
                        protocol      TEXT NOT NULL,
                        configuration TEXT,
                        updated_at    TEXT NOT NULL
                    );
                    CREATE TABLE native_alarm_state (
                        instance_unique_name  TEXT NOT NULL,
                        source_canonical_name TEXT NOT NULL,
                        source_reference      TEXT NOT NULL,
                        condition_json        TEXT NOT NULL,
                        last_transition_at    TEXT NOT NULL,
                        PRIMARY KEY (instance_unique_name, source_canonical_name, source_reference)
                    );
                    CREATE TABLE external_systems (
                        name               TEXT PRIMARY KEY,
                        endpoint_url       TEXT NOT NULL,
                        auth_type          TEXT NOT NULL,
                        auth_configuration TEXT,
                        method_definitions TEXT,
                        updated_at         TEXT NOT NULL
                    );
                    INSERT INTO external_systems (name, endpoint_url, auth_type, updated_at)
                    VALUES ('legacy-system', 'http://example.invalid', 'None', '2026-01-01T00:00:00Z');
                    """;
                legacy.ExecuteNonQuery();
            }

            SiteStorageSchema.Apply(connection);

            Assert.True(ColumnExists(connection, "data_connection_definitions", "backup_configuration"));
            Assert.True(ColumnExists(connection, "data_connection_definitions", "failover_retry_count"));
            Assert.True(ColumnExists(connection, "native_alarm_state", "metadata_json"));
            Assert.True(ColumnExists(connection, "external_systems", "timeout_seconds"));

            // The pre-existing row survives the upgrade and reads back the NOT NULL
            // column's default rather than failing.
            using var check = connection.CreateCommand();
            check.CommandText = "SELECT timeout_seconds FROM external_systems WHERE name = 'legacy-system'";
            Assert.Equal(0, Convert.ToInt32(check.ExecuteScalar()));
        }
        finally
        {
            SqliteConnection.ClearAllPools();
            File.Delete(path);
        }
    }

    private static bool TableExists(SqliteConnection connection, string table)
    {
        using var cmd = connection.CreateCommand();
        cmd.CommandText = "SELECT COUNT(*) FROM sqlite_master WHERE type = 'table' AND name = $name";
        cmd.Parameters.AddWithValue("$name", table);
        return Convert.ToInt32(cmd.ExecuteScalar()) > 0;
    }

    private static bool ColumnExists(SqliteConnection connection, string table, string column)
    {
        using var cmd = connection.CreateCommand();
        cmd.CommandText = $"SELECT COUNT(*) FROM pragma_table_info('{table}') WHERE name = $name";
        cmd.Parameters.AddWithValue("$name", column);
        return Convert.ToInt32(cmd.ExecuteScalar()) > 0;
    }
}
