using System.Globalization;
using Microsoft.Data.Sqlite;
using YMhut.Box.Core.App;

namespace YMhut.Box.Core.Settings;

public static class AgreementDocument
{
    public const string CurrentVersion = "2.0.6.2";

    public const int CurrentRevision = 1;

    public static bool SettingsMatchCurrent(AppSettings settings)
        => settings.UserAgreementAccepted &&
            string.Equals(settings.UserAgreementVersion, CurrentVersion, StringComparison.OrdinalIgnoreCase);
}

public sealed record AgreementAcceptance(
    string Version,
    int Revision,
    string AppVersion,
    string Language,
    DateTimeOffset AcceptedAt);

public interface IAgreementAcceptanceStore
{
    Task<AgreementAcceptance?> GetLatestAsync(CancellationToken cancellationToken = default);

    Task RecordAcceptedAsync(
        string version,
        int revision,
        string appVersion,
        string language,
        DateTimeOffset acceptedAt,
        CancellationToken cancellationToken = default);
}

public sealed class AgreementAcceptanceStore : IAgreementAcceptanceStore
{
    private readonly SemaphoreSlim _gate = new(1, 1);
    private bool _initialized;

    public AgreementAcceptanceStore(AppPaths paths)
    {
        paths.EnsureCreated();
        DatabasePath = AppDatabasePaths.ResolveMainDatabasePath(paths);
    }

    public string DatabasePath { get; }

    public async Task<AgreementAcceptance?> GetLatestAsync(CancellationToken cancellationToken = default)
    {
        await _gate.WaitAsync(cancellationToken).ConfigureAwait(false);
        try
        {
            await EnsureInitializedAsync(cancellationToken).ConfigureAwait(false);
            await using var connection = OpenConnection();
            await using var command = connection.CreateCommand();
            command.CommandText = """
                SELECT agreement_version, agreement_revision, app_version, language, accepted_at
                FROM agreement_acceptances
                ORDER BY accepted_at DESC, id DESC
                LIMIT 1;
                """;

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

            return new AgreementAcceptance(
                reader.GetString(0),
                reader.GetInt32(1),
                reader.GetString(2),
                reader.GetString(3),
                DateTimeOffset.TryParse(reader.GetString(4), CultureInfo.InvariantCulture, DateTimeStyles.AssumeLocal, out var acceptedAt)
                    ? acceptedAt
                    : DateTimeOffset.MinValue);
        }
        finally
        {
            _gate.Release();
        }
    }

    public async Task RecordAcceptedAsync(
        string version,
        int revision,
        string appVersion,
        string language,
        DateTimeOffset acceptedAt,
        CancellationToken cancellationToken = default)
    {
        await _gate.WaitAsync(cancellationToken).ConfigureAwait(false);
        try
        {
            await EnsureInitializedAsync(cancellationToken).ConfigureAwait(false);
            await using var connection = OpenConnection();
            await using var command = connection.CreateCommand();
            command.CommandText = """
                INSERT INTO agreement_acceptances(
                    agreement_version,
                    agreement_revision,
                    app_version,
                    language,
                    accepted_at)
                VALUES (
                    $agreement_version,
                    $agreement_revision,
                    $app_version,
                    $language,
                    $accepted_at);
                """;
            command.Parameters.AddWithValue("$agreement_version", version);
            command.Parameters.AddWithValue("$agreement_revision", revision);
            command.Parameters.AddWithValue("$app_version", appVersion);
            command.Parameters.AddWithValue("$language", language);
            command.Parameters.AddWithValue("$accepted_at", acceptedAt.ToString("O", CultureInfo.InvariantCulture));
            await command.ExecuteNonQueryAsync(cancellationToken).ConfigureAwait(false);
        }
        finally
        {
            _gate.Release();
        }
    }

    private async Task EnsureInitializedAsync(CancellationToken cancellationToken)
    {
        if (_initialized)
        {
            return;
        }

        Directory.CreateDirectory(Path.GetDirectoryName(DatabasePath)!);
        await using var connection = OpenConnection();
        await using var command = connection.CreateCommand();
        command.CommandText = """
            PRAGMA journal_mode = WAL;
            PRAGMA synchronous = NORMAL;
            PRAGMA busy_timeout = 5000;
            CREATE TABLE IF NOT EXISTS agreement_acceptances (
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                agreement_version TEXT NOT NULL,
                agreement_revision INTEGER NOT NULL,
                app_version TEXT NOT NULL,
                language TEXT NOT NULL,
                accepted_at TEXT NOT NULL
            );
            CREATE INDEX IF NOT EXISTS idx_agreement_acceptances_latest
                ON agreement_acceptances(accepted_at DESC, id DESC);
            """;
        await command.ExecuteNonQueryAsync(cancellationToken).ConfigureAwait(false);
        _initialized = true;
    }

    private SqliteConnection OpenConnection()
    {
        var connection = new SqliteConnection($"Data Source={DatabasePath};Pooling=True");
        connection.Open();
        using var command = connection.CreateCommand();
        command.CommandText = """
            PRAGMA busy_timeout = 5000;
            PRAGMA synchronous = NORMAL;
            """;
        command.ExecuteNonQuery();
        return connection;
    }
}
