using System.Globalization;
using GestionaDenuncias.Shared.Models;
using MySqlConnector;

namespace ApiDenuncias.Services;

public sealed class AppConfigurationService
{
    private const string ExternalUpdateCutoffDateKey = "external_update_cutoff_date";

    private readonly MySqlConnectionStringProvider _connectionStringProvider;

    public AppConfigurationService(MySqlConnectionStringProvider connectionStringProvider)
    {
        _connectionStringProvider = connectionStringProvider;
    }

    public async Task<AppConfigurationDto> GetAsync(CancellationToken cancellationToken = default)
    {
        await using var connection = await OpenConnectionAsync(cancellationToken);
        await EnsureTableAsync(connection, cancellationToken);

        await using var command = new MySqlCommand(
            """
            SELECT setting_value
            FROM app_settings
            WHERE setting_key = @settingKey
            LIMIT 1;
            """,
            connection);
        command.Parameters.AddWithValue("@settingKey", ExternalUpdateCutoffDateKey);

        var value = await command.ExecuteScalarAsync(cancellationToken);
        var dateText = value is null or DBNull
            ? null
            : Convert.ToString(value, CultureInfo.InvariantCulture);

        var latestEncryptionKey = await GetLatestEncryptionKeyAsync(connection, cancellationToken);

        return new AppConfigurationDto(
            string.IsNullOrWhiteSpace(dateText) ? null : dateText,
            latestEncryptionKey?.KeyDate.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture),
            latestEncryptionKey?.Status);
    }

    public async Task<AppConfigurationDto> SetExternalUpdateCutoffDateAsync(
        DateOnly? date,
        CancellationToken cancellationToken = default)
    {
        await using var connection = await OpenConnectionAsync(cancellationToken);
        await EnsureTableAsync(connection, cancellationToken);

        var dateText = date?.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture);
        await using var command = new MySqlCommand(
            """
            INSERT INTO app_settings (setting_key, setting_value, updated_at_utc)
            VALUES (@settingKey, @settingValue, UTC_TIMESTAMP(6))
            ON DUPLICATE KEY UPDATE
                setting_value = VALUES(setting_value),
                updated_at_utc = UTC_TIMESTAMP(6);
            """,
            connection);
        command.Parameters.AddWithValue("@settingKey", ExternalUpdateCutoffDateKey);
        command.Parameters.AddWithValue("@settingValue", string.IsNullOrWhiteSpace(dateText) ? DBNull.Value : dateText);

        await command.ExecuteNonQueryAsync(cancellationToken);
        var latestEncryptionKey = await GetLatestEncryptionKeyAsync(connection, cancellationToken);
        return new AppConfigurationDto(
            dateText,
            latestEncryptionKey?.KeyDate.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture),
            latestEncryptionKey?.Status);
    }

    private static async Task<LatestEncryptionKey?> GetLatestEncryptionKeyAsync(
        MySqlConnection connection,
        CancellationToken cancellationToken)
    {
        try
        {
            await using var command = new MySqlCommand(
                """
                SELECT key_date, status
                FROM encryption_keys
                ORDER BY key_date DESC
                LIMIT 1;
                """,
                connection);

            await using var reader = await command.ExecuteReaderAsync(cancellationToken);
            if (!await reader.ReadAsync(cancellationToken))
            {
                return null;
            }

            return new LatestEncryptionKey(
                DateOnly.FromDateTime(reader.GetDateTime("key_date")),
                reader.GetString("status"));
        }
        catch (MySqlException ex) when (ex.Number == 1146)
        {
            return null;
        }
    }

    private async Task<MySqlConnection> OpenConnectionAsync(CancellationToken cancellationToken)
    {
        var connectionString = await _connectionStringProvider.GetConnectionStringAsync(cancellationToken);
        var connection = new MySqlConnection(connectionString);
        await connection.OpenAsync(cancellationToken);
        return connection;
    }

    private static async Task EnsureTableAsync(MySqlConnection connection, CancellationToken cancellationToken)
    {
        await using var command = new MySqlCommand(
            """
            CREATE TABLE IF NOT EXISTS app_settings (
                setting_key VARCHAR(128) NOT NULL,
                setting_value TEXT NULL,
                updated_at_utc DATETIME(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
                PRIMARY KEY (setting_key)
            ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
            """,
            connection);
        await command.ExecuteNonQueryAsync(cancellationToken);
    }

    private sealed record LatestEncryptionKey(DateOnly KeyDate, string Status);
}
