using Microsoft.Data.Sqlite;
using Microsoft.Extensions.Options;
using ZB.MOM.WW.MxGateway.Server.Configuration;

namespace ZB.MOM.WW.MxGateway.Server.Security.Authentication;

/// <summary>
/// Factory for creating SQLite connections to the authentication store.
/// </summary>
public sealed class AuthSqliteConnectionFactory(IOptions<GatewayOptions> options)
{
    /// <summary>
    /// Busy timeout applied to every auth-store connection. SQLite retries a busy
    /// database for this long before surfacing <c>SQLITE_BUSY</c>, so the concurrent
    /// <c>MarkKeyUsedAsync</c> / audit-append writers degrade gracefully under load
    /// instead of failing the request path.
    /// </summary>
    private static readonly TimeSpan BusyTimeout = TimeSpan.FromSeconds(5);

    /// <summary>
    /// Creates an unopened SQLite connection to the auth database. Prefer
    /// <see cref="OpenConnectionAsync"/>, which also applies WAL journaling and the
    /// busy timeout.
    /// </summary>
    public SqliteConnection CreateConnection()
    {
        string sqlitePath = options.Value.Authentication.SqlitePath;
        string? directory = Path.GetDirectoryName(sqlitePath);

        if (!string.IsNullOrWhiteSpace(directory))
        {
            Directory.CreateDirectory(directory);
        }

        SqliteConnectionStringBuilder builder = new()
        {
            DataSource = sqlitePath,
            Mode = SqliteOpenMode.ReadWriteCreate,
            Pooling = true,
            DefaultTimeout = (int)BusyTimeout.TotalSeconds,
        };

        return new SqliteConnection(builder.ToString());
    }

    /// <summary>
    /// Creates a SQLite connection, opens it, and configures WAL journaling and a
    /// non-zero busy timeout so concurrent readers and writers degrade gracefully
    /// rather than surfacing <c>SQLITE_BUSY</c> as a hard failure.
    /// </summary>
    public async Task<SqliteConnection> OpenConnectionAsync(CancellationToken cancellationToken)
    {
        SqliteConnection connection = CreateConnection();
        try
        {
            await connection.OpenAsync(cancellationToken).ConfigureAwait(false);
            await ConfigureConnectionAsync(connection, cancellationToken).ConfigureAwait(false);
            return connection;
        }
        catch
        {
            await connection.DisposeAsync().ConfigureAwait(false);
            throw;
        }
    }

    private static async Task ConfigureConnectionAsync(
        SqliteConnection connection,
        CancellationToken cancellationToken)
    {
        // WAL is a persistent, database-level setting; re-applying it per connection
        // is cheap and a no-op once set. busy_timeout is per-connection state.
        await using SqliteCommand command = connection.CreateCommand();
        command.CommandText =
            $"PRAGMA journal_mode=WAL; PRAGMA busy_timeout={(int)BusyTimeout.TotalMilliseconds};";
        await command.ExecuteNonQueryAsync(cancellationToken).ConfigureAwait(false);
    }
}
