using Microsoft.Extensions.Configuration;
using ZB.MOM.WW.LocalDb;
using ZB.MOM.WW.OtOpcUa.Core.AlarmHistorian;
using ZB.MOM.WW.OtOpcUa.Runtime.DeploymentCache;

namespace ZB.MOM.WW.OtOpcUa.Host.Configuration;

/// <summary>
///     The <c>onReady</c> callback handed to <c>AddZbLocalDb</c>: creates the deployment-cache,
///     alarm store-and-forward, and scripted-alarm condition-state tables and opts them into
///     replication.
/// </summary>
/// <remarks>
///     <para>
///         Public rather than internal only so <c>LocalDbSetupTests</c> can drive the production
///         callback directly. Initialising a test database from a hand-written copy of this schema
///         would prove only that the test agrees with itself — the whole value of those tests is
///         that they exercise <b>this</b> method.
///     </para>
/// </remarks>
public static class LocalDbSetup
{
    /// <summary>
    ///     Initialises the local database: DDL first, then replication registration.
    /// </summary>
    /// <remarks>
    ///     <para>
    ///         <b>THE ORDER IS LOAD-BEARING: DDL → RegisterReplicated → writes.</b>
    ///         <c>RegisterReplicated</c> is what installs the three AFTER triggers that capture
    ///         changes into the oplog. Any row written before that call is never captured, so it
    ///         never reaches the peer — silently, and permanently, because nothing ever revisits
    ///         history. <see cref="AlarmSfLegacyMigrator"/> therefore runs <b>last</b>, after every
    ///         registration — it writes rows, and they must be captured like any other write.
    ///     </para>
    ///     <para>
    ///         The alarm buffer's tables are created unconditionally, regardless of whether this
    ///         node has <c>AlarmHistorian:Enabled</c> set. An empty registered table costs three
    ///         triggers and nothing else, whereas creating it lazily when the sink first appears
    ///         would mean a node that enables the historian later writes rows before its triggers
    ///         exist — which is precisely the silent-loss shape above.
    ///     </para>
    /// </remarks>
    /// <param name="db">The freshly constructed local database.</param>
    /// <param name="configuration">
    ///     Application configuration, read by the legacy migrator for the pre-consolidation queue's
    ///     path. Required rather than optional: an overload that silently skipped the migration
    ///     would be one wiring mistake away from discarding a node's undelivered alarm history.
    /// </param>
    public static void OnReady(ILocalDb db, IConfiguration configuration)
    {
        ArgumentNullException.ThrowIfNull(db);
        ArgumentNullException.ThrowIfNull(configuration);

        // CreateConnection() hands back an already-open, pragma-configured connection carrying the
        // zb_hlc_next() UDF the capture triggers need. Calling Open() on it would throw.
        using (var connection = db.CreateConnection())
        {
            DeploymentCacheSchema.Apply(connection);
            AlarmSfSchema.Apply(connection);
            AlarmConditionStateSchema.Apply(connection);
        }

        db.RegisterReplicated(DeploymentCacheSchema.ArtifactsTable);
        db.RegisterReplicated(DeploymentCacheSchema.PointerTable);
        db.RegisterReplicated(AlarmSfSchema.EventsTable);
        db.RegisterReplicated(AlarmConditionStateSchema.StateTable);

        // LAST, and only here. This is the one call in OnReady that writes rows.
        AlarmSfLegacyMigrator.Migrate(db, configuration);
    }
}
