using Microsoft.Data.Sqlite;

namespace ZB.MOM.WW.OtOpcUa.Runtime.DeploymentCache;

/// <summary>
///     DDL for the node-local deployment-artifact cache: the chunked artifact table plus the
///     per-cluster current-deployment pointer.
/// </summary>
/// <remarks>
///     <para>
///         Deliberately depends on nothing but <see cref="SqliteConnection"/> so it can be applied
///         to any connection — the host's <c>LocalDbSetup.OnReady</c> in production, and a bare
///         connection in a test — without dragging the DI graph along.
///     </para>
///     <para>
///         <b>Why the artifact is chunked base64 TEXT and not one BLOB row.</b> Two independent
///         constraints force it. <c>RegisterReplicated</c> rejects BLOB columns outright; and the
///         replication engine batches by <i>row count</i> against gRPC's 4 MB default message cap,
///         so a single multi-megabyte artifact row would simply never be deliverable — at any
///         batch size. A 128 KiB raw chunk is ≈ 171 KB once base64-encoded, which keeps a
///         worst-case batch comfortably inside the cap.
///     </para>
///     <para>
///         <b>Why no autoincrement PKs.</b> Convergence is last-writer-wins keyed on the primary
///         key. Two nodes independently allocating rowid 7 for different rows would silently
///         overwrite one another. Every key here is TEXT or a composite of caller-supplied values.
///     </para>
/// </remarks>
public static class DeploymentCacheSchema
{
    /// <summary>Table holding the artifact bytes, split into base64 chunks.</summary>
    public const string ArtifactsTable = "deployment_artifacts";

    /// <summary>Table holding one current-deployment pointer per cluster.</summary>
    public const string PointerTable = "deployment_pointer";

    /// <summary>
    ///     Creates both cache tables if they do not already exist. Idempotent.
    /// </summary>
    /// <param name="connection">
    ///     An already-open connection. <c>ILocalDb.CreateConnection()</c> hands out open,
    ///     pragma-configured connections — do not call <c>Open()</c> on one.
    /// </param>
    public static void Apply(SqliteConnection connection)
    {
        ArgumentNullException.ThrowIfNull(connection);

        using var cmd = connection.CreateCommand();
        cmd.CommandText = """
            CREATE TABLE IF NOT EXISTS deployment_artifacts (
                deployment_id   TEXT    NOT NULL,
                chunk_index     INTEGER NOT NULL,
                cluster_id      TEXT    NOT NULL,
                revision_hash   TEXT    NOT NULL,
                chunk_count     INTEGER NOT NULL,
                chunk_base64    TEXT    NOT NULL,
                cached_at_utc   TEXT    NOT NULL,
                PRIMARY KEY (deployment_id, chunk_index)
            );
            CREATE TABLE IF NOT EXISTS deployment_pointer (
                cluster_id        TEXT NOT NULL PRIMARY KEY,
                deployment_id     TEXT NOT NULL,
                revision_hash     TEXT NOT NULL,
                artifact_sha256   TEXT NOT NULL,
                applied_at_utc    TEXT NOT NULL
            );
            """;
        cmd.ExecuteNonQuery();
    }
}
