using Microsoft.Data.Sqlite;

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

/// <summary>
/// DDL for the site's nine configuration tables, extracted from
/// <see cref="SiteStorageService"/> so it can be applied by whoever owns the database
/// file.
/// <para>
/// Deliberately depends only on <c>Microsoft.Data.Sqlite</c>, not on the LocalDb library.
/// The Host applies this DDL to a LocalDb-managed connection before
/// <c>RegisterReplicated</c> installs the capture triggers; nothing about the schema
/// itself is LocalDb-specific, and the service still calls it so a directly-constructed
/// service (tests, tooling) remains self-sufficient.
/// </para>
/// <para>
/// Journal-mode and other connection pragmas are deliberately NOT set here — LocalDb owns
/// the connection's pragmas, and <see cref="SiteStorageService"/> keeps its own WAL set-up
/// for the databases it opens itself.
/// </para>
/// </summary>
public static class SiteStorageSchema
{
    /// <summary>
    /// Creates the nine site configuration tables when absent, and additively upgrades
    /// tables created by an older build. Idempotent — safe to run on every startup.
    /// </summary>
    /// <param name="connection">An open connection to the database that should hold the tables.</param>
    public static void Apply(SqliteConnection connection)
    {
        ArgumentNullException.ThrowIfNull(connection);

        using (var command = connection.CreateCommand())
        {
            command.CommandText = @"
            CREATE TABLE IF NOT EXISTS deployed_configurations (
                instance_unique_name TEXT PRIMARY KEY,
                config_json          TEXT    NOT NULL,
                deployment_id        TEXT    NOT NULL,
                revision_hash        TEXT    NOT NULL,
                is_enabled           INTEGER NOT NULL DEFAULT 1,
                deployed_at          TEXT    NOT NULL
            );

            CREATE TABLE IF NOT EXISTS static_attribute_overrides (
                instance_unique_name TEXT NOT NULL,
                attribute_name       TEXT NOT NULL,
                override_value       TEXT NOT NULL,
                updated_at           TEXT NOT NULL,
                PRIMARY KEY (instance_unique_name, attribute_name)
            );

            CREATE TABLE IF NOT EXISTS shared_scripts (
                name                 TEXT PRIMARY KEY,
                code                 TEXT NOT NULL,
                parameter_definitions TEXT,
                return_definition    TEXT,
                updated_at           TEXT NOT NULL
            );

            CREATE TABLE IF NOT EXISTS 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
            );

            CREATE TABLE IF NOT EXISTS database_connections (
                name                 TEXT PRIMARY KEY,
                connection_string    TEXT NOT NULL,
                max_retries          INTEGER NOT NULL DEFAULT 3,
                retry_delay_ms       INTEGER NOT NULL DEFAULT 1000,
                updated_at           TEXT NOT NULL
            );

            CREATE TABLE IF NOT EXISTS notification_lists (
                name                 TEXT PRIMARY KEY,
                recipient_emails     TEXT NOT NULL,
                updated_at           TEXT NOT NULL
            );

            CREATE TABLE IF NOT EXISTS data_connection_definitions (
                name                    TEXT PRIMARY KEY,
                protocol                TEXT NOT NULL,
                configuration           TEXT,
                backup_configuration    TEXT,
                failover_retry_count    INTEGER NOT NULL DEFAULT 3,
                updated_at              TEXT NOT NULL
            );

            CREATE TABLE IF NOT EXISTS smtp_configurations (
                name                 TEXT PRIMARY KEY,
                server               TEXT NOT NULL,
                port                 INTEGER NOT NULL,
                auth_mode            TEXT NOT NULL,
                from_address         TEXT NOT NULL,
                username             TEXT,
                password             TEXT,
                oauth_config         TEXT,
                updated_at           TEXT NOT NULL
            );

            CREATE TABLE IF NOT EXISTS 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,
                metadata_json        TEXT,
                PRIMARY KEY (instance_unique_name, source_canonical_name, source_reference)
            );
        ";
            command.ExecuteNonQuery();
        }

        // Schema migrations — add columns that may not exist on older databases
        MigrateSchema(connection);
    }

    private static void MigrateSchema(SqliteConnection connection)
    {
        // Add backup_configuration and failover_retry_count to data_connection_definitions
        // (added in primary/backup data connections feature)
        AddColumnIfMissing(connection, "data_connection_definitions", "backup_configuration", "TEXT");
        AddColumnIfMissing(connection, "data_connection_definitions", "failover_retry_count", "INTEGER NOT NULL DEFAULT 3");

        // Native-alarm display metadata (UA4) — restored on rehydration so a persisted condition
        // renders fully (type/category/message/values) before the first source snapshot arrives.
        AddColumnIfMissing(connection, "native_alarm_state", "metadata_json", "TEXT");

        // Per-external-system call timeout (ExternalSystemGateway Timeout) — carried through the
        // artifact pipeline so site-side calls honor it; 0 = use the site default.
        AddColumnIfMissing(connection, "external_systems", "timeout_seconds", "INTEGER NOT NULL DEFAULT 0");
    }

    /// <summary>
    /// Additively adds a column only when it is not already present. SQLite lacks
    /// <c>ADD COLUMN IF NOT EXISTS</c>, so the schema is probed via
    /// <c>PRAGMA table_info</c> first. Mirrors <c>OperationTrackingSchema</c>.
    /// <para>
    /// This replaces the previous try/catch on <c>SqliteException</c> message text
    /// containing "duplicate column": probing is the same precedent the other schema
    /// classes use, does not depend on an error string that is not part of SQLite's
    /// contract, and does not swallow unrelated failures of the same exception type.
    /// </para>
    /// </summary>
    private static void AddColumnIfMissing(
        SqliteConnection connection, string table, string columnName, string columnDefinition)
    {
        using (var probe = connection.CreateCommand())
        {
            // Table + column names are caller-controlled constants, never user input —
            // safe to interpolate (parameters are not permitted in DDL or in a
            // pragma-function argument).
            probe.CommandText = $"SELECT COUNT(*) FROM pragma_table_info('{table}') WHERE name = $name";
            probe.Parameters.AddWithValue("$name", columnName);
            if (Convert.ToInt32(probe.ExecuteScalar()) > 0)
            {
                return;
            }
        }

        using var alter = connection.CreateCommand();
        alter.CommandText = $"ALTER TABLE {table} ADD COLUMN {columnName} {columnDefinition}";
        alter.ExecuteNonQuery();
    }
}
