using Microsoft.Data.Sqlite;

namespace ZB.MOM.WW.ScadaBridge.StoreAndForward.Tests;

/// <summary>
/// Pins <see cref="StoreAndForwardSchema"/> as the single owner of the
/// <c>sf_messages</c> 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 StoreAndForwardSchemaTests
{
    [Fact]
    public void Apply_IsIdempotent_AndCreatesEveryColumn()
    {
        var path = Path.Combine(Path.GetTempPath(), $"sf-schema-{Guid.NewGuid():N}.db");
        try
        {
            using var connection = new SqliteConnection($"Data Source={path}");
            connection.Open();

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

            using var cmd = connection.CreateCommand();
            cmd.CommandText = "SELECT name FROM pragma_table_info('sf_messages')";
            var columns = new List<string>();
            using var reader = cmd.ExecuteReader();
            while (reader.Read()) columns.Add(reader.GetString(0));

            // 12 original + 4 additive
            Assert.Equal(16, columns.Count);
            Assert.Contains("last_attempt_at_ms", columns);
            Assert.Contains("parent_execution_id", columns);
        }
        finally
        {
            SqliteConnection.ClearAllPools();
            File.Delete(path);
        }
    }

    /// <summary>
    /// The additive 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-upgrade shape and proves the ALTER path runs.
    /// </summary>
    [Fact]
    public void Apply_UpgradesALegacyTable_WithoutLosingRows()
    {
        var path = Path.Combine(Path.GetTempPath(), $"sf-schema-legacy-{Guid.NewGuid():N}.db");
        try
        {
            using var connection = new SqliteConnection($"Data Source={path}");
            connection.Open();

            // The 12-column shape as it existed before execution_id / source_script /
            // parent_execution_id / last_attempt_at_ms were added.
            using (var legacy = connection.CreateCommand())
            {
                legacy.CommandText = """
                    CREATE TABLE sf_messages (
                        id                TEXT PRIMARY KEY,
                        category          INTEGER NOT NULL,
                        target            TEXT    NOT NULL,
                        payload_json      TEXT    NOT NULL,
                        retry_count       INTEGER NOT NULL DEFAULT 0,
                        max_retries       INTEGER NOT NULL DEFAULT 50,
                        retry_interval_ms INTEGER NOT NULL DEFAULT 30000,
                        created_at        TEXT    NOT NULL,
                        last_attempt_at   TEXT,
                        status            INTEGER NOT NULL DEFAULT 0,
                        last_error        TEXT,
                        origin_instance   TEXT
                    );
                    INSERT INTO sf_messages (id, category, target, payload_json, created_at, last_attempt_at)
                    VALUES ('legacy-1', 0, 'target-a', '{}', '2026-01-01T00:00:00Z', '2026-01-02T03:04:05Z');
                    """;
                legacy.ExecuteNonQuery();
            }

            StoreAndForwardSchema.Apply(connection);

            using var check = connection.CreateCommand();
            check.CommandText =
                "SELECT COUNT(*) FROM pragma_table_info('sf_messages') WHERE name = 'last_attempt_at_ms'";
            Assert.Equal(1, Convert.ToInt32(check.ExecuteScalar()));

            // The pre-existing row survives and its epoch-ms sibling is backfilled from
            // the authoritative text timestamp.
            using var backfilled = connection.CreateCommand();
            backfilled.CommandText =
                "SELECT last_attempt_at_ms FROM sf_messages WHERE id = 'legacy-1'";
            var ms = Convert.ToInt64(backfilled.ExecuteScalar());
            var expected = DateTimeOffset.Parse("2026-01-02T03:04:05Z").ToUnixTimeMilliseconds();

            // Tolerance of 1 ms, not sloppiness: the backfill derives epoch-ms from
            // julianday(), whose double-precision day-fraction cannot represent every
            // millisecond exactly, so it lands 1 ms low here. That rounding is
            // pre-existing production behaviour carried over verbatim from
            // StoreAndForwardStorage — the column is a sweep-ordering hint, with the
            // ISO-8601 text column remaining authoritative for reads. Asserting exact
            // equality would pin a precision the implementation never had.
            Assert.InRange(ms, expected - 1, expected + 1);
        }
        finally
        {
            SqliteConnection.ClearAllPools();
            File.Delete(path);
        }
    }
}
