using Microsoft.Data.Sqlite;
using Microsoft.Extensions.Logging;

namespace ScadaLink.SiteRuntime.Persistence;

/// <summary>
/// Direct SQLite persistence for site-local deployment state.
/// Stores deployed instance configurations (as JSON) and static attribute overrides.
/// This is NOT EF Core — uses Microsoft.Data.Sqlite directly for lightweight site storage.
/// </summary>
public class SiteStorageService
{
    private readonly string _connectionString;
    private readonly ILogger<SiteStorageService> _logger;

    public SiteStorageService(string connectionString, ILogger<SiteStorageService> logger)
    {
        _connectionString = connectionString;
        _logger = logger;
    }

    /// <summary>
    /// Creates the SQLite tables if they do not exist.
    /// Called once on site startup.
    /// </summary>
    public async Task InitializeAsync()
    {
        await using var connection = new SqliteConnection(_connectionString);
        await connection.OpenAsync();

        await 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
            );
        ";
        await command.ExecuteNonQueryAsync();

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

        _logger.LogInformation("Site SQLite storage initialized at {ConnectionString}", _connectionString);
    }

    private async Task MigrateSchemaAsync(SqliteConnection connection)
    {
        // Add backup_configuration and failover_retry_count to data_connection_definitions
        // (added in primary/backup data connections feature)
        await TryAddColumnAsync(connection, "data_connection_definitions", "backup_configuration", "TEXT");
        await TryAddColumnAsync(connection, "data_connection_definitions", "failover_retry_count", "INTEGER NOT NULL DEFAULT 3");
    }

    private async Task TryAddColumnAsync(SqliteConnection connection, string table, string column, string type)
    {
        try
        {
            await using var cmd = connection.CreateCommand();
            cmd.CommandText = $"ALTER TABLE {table} ADD COLUMN {column} {type}";
            await cmd.ExecuteNonQueryAsync();
            _logger.LogInformation("Migrated: added column {Column} to {Table}", column, table);
        }
        catch (SqliteException ex) when (ex.Message.Contains("duplicate column"))
        {
            // Column already exists — no action needed
        }
    }

    // ── Deployed Configuration CRUD ──

    /// <summary>
    /// Returns all deployed instance configurations from SQLite.
    /// </summary>
    public async Task<List<DeployedInstance>> GetAllDeployedConfigsAsync()
    {
        await using var connection = new SqliteConnection(_connectionString);
        await connection.OpenAsync();

        await using var command = connection.CreateCommand();
        command.CommandText = @"
            SELECT instance_unique_name, config_json, deployment_id, revision_hash, is_enabled, deployed_at
            FROM deployed_configurations";

        var results = new List<DeployedInstance>();
        await using var reader = await command.ExecuteReaderAsync();
        while (await reader.ReadAsync())
        {
            results.Add(new DeployedInstance
            {
                InstanceUniqueName = reader.GetString(0),
                ConfigJson = reader.GetString(1),
                DeploymentId = reader.GetString(2),
                RevisionHash = reader.GetString(3),
                IsEnabled = reader.GetInt64(4) != 0,
                DeployedAt = reader.GetString(5)
            });
        }

        return results;
    }

    /// <summary>
    /// Stores or updates a deployed instance configuration. Uses UPSERT semantics.
    /// </summary>
    public async Task StoreDeployedConfigAsync(
        string instanceName,
        string configJson,
        string deploymentId,
        string revisionHash,
        bool isEnabled)
    {
        await using var connection = new SqliteConnection(_connectionString);
        await connection.OpenAsync();

        await using var command = connection.CreateCommand();
        command.CommandText = @"
            INSERT INTO deployed_configurations (instance_unique_name, config_json, deployment_id, revision_hash, is_enabled, deployed_at)
            VALUES (@name, @json, @depId, @hash, @enabled, @deployedAt)
            ON CONFLICT(instance_unique_name) DO UPDATE SET
                config_json   = excluded.config_json,
                deployment_id = excluded.deployment_id,
                revision_hash = excluded.revision_hash,
                is_enabled    = excluded.is_enabled,
                deployed_at   = excluded.deployed_at";

        command.Parameters.AddWithValue("@name", instanceName);
        command.Parameters.AddWithValue("@json", configJson);
        command.Parameters.AddWithValue("@depId", deploymentId);
        command.Parameters.AddWithValue("@hash", revisionHash);
        command.Parameters.AddWithValue("@enabled", isEnabled ? 1 : 0);
        command.Parameters.AddWithValue("@deployedAt", DateTimeOffset.UtcNow.ToString("O"));

        await command.ExecuteNonQueryAsync();
        _logger.LogDebug("Stored deployed config for {Instance}, deploymentId={DeploymentId}", instanceName, deploymentId);
    }

    /// <summary>
    /// Removes a deployed instance configuration and its static overrides.
    /// </summary>
    public async Task RemoveDeployedConfigAsync(string instanceName)
    {
        await using var connection = new SqliteConnection(_connectionString);
        await connection.OpenAsync();

        await using var transaction = await connection.BeginTransactionAsync();

        await using (var cmd = connection.CreateCommand())
        {
            cmd.Transaction = (SqliteTransaction)transaction;
            cmd.CommandText = "DELETE FROM static_attribute_overrides WHERE instance_unique_name = @name";
            cmd.Parameters.AddWithValue("@name", instanceName);
            await cmd.ExecuteNonQueryAsync();
        }

        await using (var cmd = connection.CreateCommand())
        {
            cmd.Transaction = (SqliteTransaction)transaction;
            cmd.CommandText = "DELETE FROM deployed_configurations WHERE instance_unique_name = @name";
            cmd.Parameters.AddWithValue("@name", instanceName);
            await cmd.ExecuteNonQueryAsync();
        }

        await transaction.CommitAsync();
        _logger.LogInformation("Removed deployed config and overrides for {Instance}", instanceName);
    }

    /// <summary>
    /// Sets the enabled/disabled state of a deployed instance.
    /// </summary>
    public async Task SetInstanceEnabledAsync(string instanceName, bool isEnabled)
    {
        await using var connection = new SqliteConnection(_connectionString);
        await connection.OpenAsync();

        await using var command = connection.CreateCommand();
        command.CommandText = @"
            UPDATE deployed_configurations
            SET is_enabled = @enabled
            WHERE instance_unique_name = @name";

        command.Parameters.AddWithValue("@enabled", isEnabled ? 1 : 0);
        command.Parameters.AddWithValue("@name", instanceName);

        var rows = await command.ExecuteNonQueryAsync();
        if (rows == 0)
        {
            _logger.LogWarning("SetInstanceEnabled: instance {Instance} not found", instanceName);
        }
    }

    // ── Static Attribute Override CRUD ──

    /// <summary>
    /// Returns all static attribute overrides for an instance.
    /// </summary>
    public async Task<Dictionary<string, string>> GetStaticOverridesAsync(string instanceName)
    {
        await using var connection = new SqliteConnection(_connectionString);
        await connection.OpenAsync();

        await using var command = connection.CreateCommand();
        command.CommandText = @"
            SELECT attribute_name, override_value
            FROM static_attribute_overrides
            WHERE instance_unique_name = @name";
        command.Parameters.AddWithValue("@name", instanceName);

        var results = new Dictionary<string, string>();
        await using var reader = await command.ExecuteReaderAsync();
        while (await reader.ReadAsync())
        {
            results[reader.GetString(0)] = reader.GetString(1);
        }

        return results;
    }

    /// <summary>
    /// Sets or updates a single static attribute override for an instance.
    /// </summary>
    public async Task SetStaticOverrideAsync(string instanceName, string attributeName, string value)
    {
        await using var connection = new SqliteConnection(_connectionString);
        await connection.OpenAsync();

        await using var command = connection.CreateCommand();
        command.CommandText = @"
            INSERT INTO static_attribute_overrides (instance_unique_name, attribute_name, override_value, updated_at)
            VALUES (@name, @attr, @val, @updatedAt)
            ON CONFLICT(instance_unique_name, attribute_name) DO UPDATE SET
                override_value = excluded.override_value,
                updated_at     = excluded.updated_at";

        command.Parameters.AddWithValue("@name", instanceName);
        command.Parameters.AddWithValue("@attr", attributeName);
        command.Parameters.AddWithValue("@val", value);
        command.Parameters.AddWithValue("@updatedAt", DateTimeOffset.UtcNow.ToString("O"));

        await command.ExecuteNonQueryAsync();
    }

    /// <summary>
    /// Clears all static attribute overrides for an instance.
    /// Called on redeployment to reset overrides.
    /// </summary>
    public async Task ClearStaticOverridesAsync(string instanceName)
    {
        await using var connection = new SqliteConnection(_connectionString);
        await connection.OpenAsync();

        await using var command = connection.CreateCommand();
        command.CommandText = "DELETE FROM static_attribute_overrides WHERE instance_unique_name = @name";
        command.Parameters.AddWithValue("@name", instanceName);

        await command.ExecuteNonQueryAsync();
        _logger.LogDebug("Cleared static overrides for {Instance}", instanceName);
    }

    // ── WP-33: Shared Script CRUD ──

    /// <summary>
    /// Stores or updates a shared script. Uses UPSERT semantics.
    /// </summary>
    public async Task StoreSharedScriptAsync(string name, string code, string? parameterDefs, string? returnDef)
    {
        await using var connection = new SqliteConnection(_connectionString);
        await connection.OpenAsync();

        await using var command = connection.CreateCommand();
        command.CommandText = @"
            INSERT INTO shared_scripts (name, code, parameter_definitions, return_definition, updated_at)
            VALUES (@name, @code, @paramDefs, @returnDef, @updatedAt)
            ON CONFLICT(name) DO UPDATE SET
                code = excluded.code,
                parameter_definitions = excluded.parameter_definitions,
                return_definition = excluded.return_definition,
                updated_at = excluded.updated_at";

        command.Parameters.AddWithValue("@name", name);
        command.Parameters.AddWithValue("@code", code);
        command.Parameters.AddWithValue("@paramDefs", (object?)parameterDefs ?? DBNull.Value);
        command.Parameters.AddWithValue("@returnDef", (object?)returnDef ?? DBNull.Value);
        command.Parameters.AddWithValue("@updatedAt", DateTimeOffset.UtcNow.ToString("O"));

        await command.ExecuteNonQueryAsync();
        _logger.LogDebug("Stored shared script '{Name}'", name);
    }

    /// <summary>
    /// Returns all stored shared scripts.
    /// </summary>
    public async Task<List<StoredSharedScript>> GetAllSharedScriptsAsync()
    {
        await using var connection = new SqliteConnection(_connectionString);
        await connection.OpenAsync();

        await using var command = connection.CreateCommand();
        command.CommandText = "SELECT name, code, parameter_definitions, return_definition FROM shared_scripts";

        var results = new List<StoredSharedScript>();
        await using var reader = await command.ExecuteReaderAsync();
        while (await reader.ReadAsync())
        {
            results.Add(new StoredSharedScript
            {
                Name = reader.GetString(0),
                Code = reader.GetString(1),
                ParameterDefinitions = reader.IsDBNull(2) ? null : reader.GetString(2),
                ReturnDefinition = reader.IsDBNull(3) ? null : reader.GetString(3)
            });
        }

        return results;
    }

    // ── WP-33: External System CRUD ──

    /// <summary>
    /// Stores or updates an external system definition.
    /// </summary>
    public async Task StoreExternalSystemAsync(
        string name, string endpointUrl, string authType, string? authConfig, string? methodDefs)
    {
        await using var connection = new SqliteConnection(_connectionString);
        await connection.OpenAsync();

        await using var command = connection.CreateCommand();
        command.CommandText = @"
            INSERT INTO external_systems (name, endpoint_url, auth_type, auth_configuration, method_definitions, updated_at)
            VALUES (@name, @url, @authType, @authConfig, @methodDefs, @updatedAt)
            ON CONFLICT(name) DO UPDATE SET
                endpoint_url = excluded.endpoint_url,
                auth_type = excluded.auth_type,
                auth_configuration = excluded.auth_configuration,
                method_definitions = excluded.method_definitions,
                updated_at = excluded.updated_at";

        command.Parameters.AddWithValue("@name", name);
        command.Parameters.AddWithValue("@url", endpointUrl);
        command.Parameters.AddWithValue("@authType", authType);
        command.Parameters.AddWithValue("@authConfig", (object?)authConfig ?? DBNull.Value);
        command.Parameters.AddWithValue("@methodDefs", (object?)methodDefs ?? DBNull.Value);
        command.Parameters.AddWithValue("@updatedAt", DateTimeOffset.UtcNow.ToString("O"));

        await command.ExecuteNonQueryAsync();
    }

    // ── WP-33: Database Connection CRUD ──

    /// <summary>
    /// Stores or updates a database connection definition.
    /// </summary>
    public async Task StoreDatabaseConnectionAsync(
        string name, string connectionString, int maxRetries, TimeSpan retryDelay)
    {
        await using var connection = new SqliteConnection(_connectionString);
        await connection.OpenAsync();

        await using var command = connection.CreateCommand();
        command.CommandText = @"
            INSERT INTO database_connections (name, connection_string, max_retries, retry_delay_ms, updated_at)
            VALUES (@name, @connStr, @maxRetries, @retryDelayMs, @updatedAt)
            ON CONFLICT(name) DO UPDATE SET
                connection_string = excluded.connection_string,
                max_retries = excluded.max_retries,
                retry_delay_ms = excluded.retry_delay_ms,
                updated_at = excluded.updated_at";

        command.Parameters.AddWithValue("@name", name);
        command.Parameters.AddWithValue("@connStr", connectionString);
        command.Parameters.AddWithValue("@maxRetries", maxRetries);
        command.Parameters.AddWithValue("@retryDelayMs", (long)retryDelay.TotalMilliseconds);
        command.Parameters.AddWithValue("@updatedAt", DateTimeOffset.UtcNow.ToString("O"));

        await command.ExecuteNonQueryAsync();
    }

    // ── WP-33: Notification List CRUD ──

    /// <summary>
    /// Stores or updates a notification list.
    /// </summary>
    public async Task StoreNotificationListAsync(string name, IReadOnlyList<string> recipientEmails)
    {
        await using var connection = new SqliteConnection(_connectionString);
        await connection.OpenAsync();

        await using var command = connection.CreateCommand();
        command.CommandText = @"
            INSERT INTO notification_lists (name, recipient_emails, updated_at)
            VALUES (@name, @emails, @updatedAt)
            ON CONFLICT(name) DO UPDATE SET
                recipient_emails = excluded.recipient_emails,
                updated_at = excluded.updated_at";

        command.Parameters.AddWithValue("@name", name);
        command.Parameters.AddWithValue("@emails", System.Text.Json.JsonSerializer.Serialize(recipientEmails));
        command.Parameters.AddWithValue("@updatedAt", DateTimeOffset.UtcNow.ToString("O"));

        await command.ExecuteNonQueryAsync();
    }

    // ── WP-33: SMTP Configuration CRUD ──

    /// <summary>
    /// Stores or updates an SMTP configuration.
    /// </summary>
    public async Task StoreSmtpConfigurationAsync(
        string name, string server, int port, string authMode, string fromAddress,
        string? username, string? password, string? oauthConfig)
    {
        await using var connection = new SqliteConnection(_connectionString);
        await connection.OpenAsync();

        await using var command = connection.CreateCommand();
        command.CommandText = @"
            INSERT INTO smtp_configurations (name, server, port, auth_mode, from_address, username, password, oauth_config, updated_at)
            VALUES (@name, @server, @port, @authMode, @fromAddress, @username, @password, @oauthConfig, @updatedAt)
            ON CONFLICT(name) DO UPDATE SET
                server = excluded.server,
                port = excluded.port,
                auth_mode = excluded.auth_mode,
                from_address = excluded.from_address,
                username = excluded.username,
                password = excluded.password,
                oauth_config = excluded.oauth_config,
                updated_at = excluded.updated_at";

        command.Parameters.AddWithValue("@name", name);
        command.Parameters.AddWithValue("@server", server);
        command.Parameters.AddWithValue("@port", port);
        command.Parameters.AddWithValue("@authMode", authMode);
        command.Parameters.AddWithValue("@fromAddress", fromAddress);
        command.Parameters.AddWithValue("@username", (object?)username ?? DBNull.Value);
        command.Parameters.AddWithValue("@password", (object?)password ?? DBNull.Value);
        command.Parameters.AddWithValue("@oauthConfig", (object?)oauthConfig ?? DBNull.Value);
        command.Parameters.AddWithValue("@updatedAt", DateTimeOffset.UtcNow.ToString("O"));

        await command.ExecuteNonQueryAsync();
    }

    // ── Data Connection Definition CRUD ──

    /// <summary>
    /// Stores or updates a data connection definition (OPC UA endpoint, etc.).
    /// </summary>
    public async Task StoreDataConnectionDefinitionAsync(
        string name, string protocol, string? configJson, string? backupConfigJson = null, int failoverRetryCount = 3)
    {
        await using var connection = new SqliteConnection(_connectionString);
        await connection.OpenAsync();

        await using var command = connection.CreateCommand();
        command.CommandText = @"
            INSERT INTO data_connection_definitions (name, protocol, configuration, backup_configuration, failover_retry_count, updated_at)
            VALUES (@name, @protocol, @config, @backupConfig, @failoverRetryCount, @updatedAt)
            ON CONFLICT(name) DO UPDATE SET
                protocol = excluded.protocol,
                configuration = excluded.configuration,
                backup_configuration = excluded.backup_configuration,
                failover_retry_count = excluded.failover_retry_count,
                updated_at = excluded.updated_at";

        command.Parameters.AddWithValue("@name", name);
        command.Parameters.AddWithValue("@protocol", protocol);
        command.Parameters.AddWithValue("@config", (object?)configJson ?? DBNull.Value);
        command.Parameters.AddWithValue("@backupConfig", (object?)backupConfigJson ?? DBNull.Value);
        command.Parameters.AddWithValue("@failoverRetryCount", failoverRetryCount);
        command.Parameters.AddWithValue("@updatedAt", DateTimeOffset.UtcNow.ToString("O"));

        await command.ExecuteNonQueryAsync();
        _logger.LogDebug("Stored data connection definition '{Name}' (protocol={Protocol})", name, protocol);
    }

    /// <summary>
    /// Returns all stored data connection definitions.
    /// </summary>
    public async Task<List<StoredDataConnectionDefinition>> GetAllDataConnectionDefinitionsAsync()
    {
        await using var connection = new SqliteConnection(_connectionString);
        await connection.OpenAsync();

        await using var command = connection.CreateCommand();
        command.CommandText = "SELECT name, protocol, configuration, backup_configuration, failover_retry_count FROM data_connection_definitions";

        var results = new List<StoredDataConnectionDefinition>();
        await using var reader = await command.ExecuteReaderAsync();
        while (await reader.ReadAsync())
        {
            results.Add(new StoredDataConnectionDefinition
            {
                Name = reader.GetString(0),
                Protocol = reader.GetString(1),
                ConfigurationJson = reader.IsDBNull(2) ? null : reader.GetString(2),
                BackupConfigurationJson = reader.IsDBNull(3) ? null : reader.GetString(3),
                FailoverRetryCount = reader.GetInt32(4)
            });
        }

        return results;
    }
}

/// <summary>
/// Represents a deployed instance configuration as stored in SQLite.
/// </summary>
public class DeployedInstance
{
    public string InstanceUniqueName { get; init; } = string.Empty;
    public string ConfigJson { get; init; } = string.Empty;
    public string DeploymentId { get; init; } = string.Empty;
    public string RevisionHash { get; init; } = string.Empty;
    public bool IsEnabled { get; init; }
    public string DeployedAt { get; init; } = string.Empty;
}

/// <summary>
/// Represents a shared script stored locally in SQLite (WP-33).
/// </summary>
public class StoredSharedScript
{
    public string Name { get; init; } = string.Empty;
    public string Code { get; init; } = string.Empty;
    public string? ParameterDefinitions { get; init; }
    public string? ReturnDefinition { get; init; }
}

/// <summary>
/// Represents a data connection definition stored locally in SQLite.
/// </summary>
public class StoredDataConnectionDefinition
{
    public string Name { get; init; } = string.Empty;
    public string Protocol { get; init; } = string.Empty;
    public string? ConfigurationJson { get; init; }
    public string? BackupConfigurationJson { get; init; }
    public int FailoverRetryCount { get; init; } = 3;
}
