using Microsoft.Data.Sqlite;

namespace ZB.MOM.WW.LocalDb.Internal;

/// <summary>
/// Creates/migrates the library's own bookkeeping tables. Idempotent; runs in one transaction.
/// </summary>
internal static class LocalDbSchema
{
    private const string Ddl = """
        CREATE TABLE IF NOT EXISTS __localdb_meta (
          id INTEGER PRIMARY KEY CHECK (id = 1),
          node_id TEXT NOT NULL,
          hlc_high_water INTEGER NOT NULL DEFAULT 0,
          schema_version INTEGER NOT NULL
        );
        -- AUTOINCREMENT: seq must never be reused after row pruning, or peer watermarks/acks would
        -- silently skip/misattribute rows sharing a reused seq value.
        CREATE TABLE IF NOT EXISTS __localdb_oplog (
          seq INTEGER PRIMARY KEY AUTOINCREMENT,
          table_name TEXT NOT NULL,
          pk_json TEXT NOT NULL,
          row_json TEXT NULL,
          hlc INTEGER NOT NULL,
          node_id TEXT NOT NULL,
          is_tombstone INTEGER NOT NULL DEFAULT 0
        );
        CREATE INDEX IF NOT EXISTS __localdb_oplog_hlc ON __localdb_oplog(table_name, pk_json, hlc);
        CREATE TABLE IF NOT EXISTS __localdb_peer_state (
          id INTEGER PRIMARY KEY CHECK (id = 1),
          peer_node_id TEXT NULL,
          last_acked_seq INTEGER NOT NULL DEFAULT 0,
          last_applied_remote_seq INTEGER NOT NULL DEFAULT 0,
          last_seen_hlc INTEGER NOT NULL DEFAULT 0,
          needs_snapshot INTEGER NOT NULL DEFAULT 0,
          last_sync_utc TEXT NULL
        );
        -- WITHOUT ROWID: the (table_name, pk_json) key IS the lookup path for every LWW conflict compare.
        CREATE TABLE IF NOT EXISTS __localdb_row_version (
          table_name TEXT NOT NULL,
          pk_json TEXT NOT NULL,
          hlc INTEGER NOT NULL,
          node_id TEXT NOT NULL,
          is_tombstone INTEGER NOT NULL DEFAULT 0,
          tombstone_utc TEXT NULL,
          PRIMARY KEY (table_name, pk_json)
        ) WITHOUT ROWID;
        -- Single-row DB-scoped guard the applier toggles inside its txn so capture triggers skip replicated applies.
        CREATE TABLE IF NOT EXISTS __localdb_applying (
          id INTEGER PRIMARY KEY CHECK (id = 1),
          applying INTEGER NOT NULL DEFAULT 0
        );
        CREATE TABLE IF NOT EXISTS __localdb_dead_letter (
          id INTEGER PRIMARY KEY AUTOINCREMENT,
          received_utc TEXT NOT NULL,
          table_name TEXT NOT NULL,
          pk_json TEXT NOT NULL,
          row_json TEXT NULL,
          hlc INTEGER NOT NULL,
          node_id TEXT NOT NULL,
          error TEXT NOT NULL
        );
        INSERT INTO __localdb_applying (id, applying) SELECT 1, 0 WHERE NOT EXISTS (SELECT 1 FROM __localdb_applying);
        INSERT INTO __localdb_peer_state (id) SELECT 1 WHERE NOT EXISTS (SELECT 1 FROM __localdb_peer_state);
        """;

    public static void EnsureCreated(SqliteConnection connection)
    {
        using var transaction = connection.BeginTransaction();

        using (var ddl = connection.CreateCommand())
        {
            ddl.Transaction = transaction;
            ddl.CommandText = Ddl;
            ddl.ExecuteNonQuery();
        }

        using (var meta = connection.CreateCommand())
        {
            meta.Transaction = transaction;
            meta.CommandText =
                "INSERT INTO __localdb_meta (id, node_id, schema_version) " +
                "SELECT 1, $nodeId, 1 WHERE NOT EXISTS (SELECT 1 FROM __localdb_meta)";
            meta.Parameters.AddWithValue("$nodeId", Guid.NewGuid().ToString("D"));
            meta.ExecuteNonQuery();
        }

        transaction.Commit();
    }

    internal static (string NodeId, long HlcHighWater) ReadMeta(SqliteConnection connection)
    {
        using var cmd = connection.CreateCommand();
        cmd.CommandText = "SELECT node_id, hlc_high_water FROM __localdb_meta WHERE id = 1";
        using var reader = cmd.ExecuteReader();
        if (!reader.Read())
            throw new InvalidOperationException(
                "LocalDb schema not initialized: __localdb_meta is empty. Call EnsureCreated first.");
        return (reader.GetString(0), reader.GetInt64(1));
    }

    internal static void FlushHlcHighWater(SqliteConnection connection, long value)
    {
        using var cmd = connection.CreateCommand();
        cmd.CommandText = "UPDATE __localdb_meta SET hlc_high_water = $value WHERE id = 1";
        cmd.Parameters.AddWithValue("$value", value);
        cmd.ExecuteNonQuery();
    }
}
