using Microsoft.Data.Sqlite;
using ZB.MOM.WW.Auth.Abstractions.ApiKeys;

namespace ZB.MOM.WW.Auth.ApiKeys.Sqlite;

/// <summary>SQLite-backed read store for API-key records.</summary>
public sealed class SqliteApiKeyStore(AuthSqliteConnectionFactory connectionFactory) : IApiKeyStore
{
    private const string SelectColumns =
        "key_id, key_prefix, secret_hash, display_name, scopes, constraints, created_utc, last_used_utc, revoked_utc, expires_utc";

    /// <inheritdoc />
    public Task<ApiKeyRecord?> FindByKeyIdAsync(string keyId, CancellationToken ct)
    {
        ArgumentException.ThrowIfNullOrWhiteSpace(keyId);
        return FindAsync(keyId, requireActive: false, ct);
    }

    /// <inheritdoc />
    public Task<ApiKeyRecord?> FindActiveByKeyIdAsync(string keyId, CancellationToken ct)
    {
        ArgumentException.ThrowIfNullOrWhiteSpace(keyId);
        return FindAsync(keyId, requireActive: true, ct);
    }

    /// <inheritdoc />
    public async Task MarkUsedAsync(string keyId, DateTimeOffset whenUtc, CancellationToken ct)
    {
        ArgumentException.ThrowIfNullOrWhiteSpace(keyId);
        await using SqliteConnection connection =
            await connectionFactory.OpenConnectionAsync(ct).ConfigureAwait(false);

        await using SqliteCommand command = connection.CreateCommand();
        command.CommandText = """
            UPDATE api_keys
            SET last_used_utc = $last_used_utc
            WHERE key_id = $key_id AND revoked_utc IS NULL;
            """;
        command.Parameters.AddWithValue("$key_id", keyId);
        command.Parameters.AddWithValue("$last_used_utc", whenUtc.ToString("O"));

        await command.ExecuteNonQueryAsync(ct).ConfigureAwait(false);
    }

    private async Task<ApiKeyRecord?> FindAsync(string keyId, bool requireActive, CancellationToken ct)
    {
        await using SqliteConnection connection =
            await connectionFactory.OpenConnectionAsync(ct).ConfigureAwait(false);

        await using SqliteCommand command = connection.CreateCommand();
        command.CommandText = requireActive
            ? $"SELECT {SelectColumns} FROM api_keys WHERE key_id = $key_id AND revoked_utc IS NULL;"
            : $"SELECT {SelectColumns} FROM api_keys WHERE key_id = $key_id;";
        command.Parameters.AddWithValue("$key_id", keyId);

        await using SqliteDataReader reader = await command.ExecuteReaderAsync(ct).ConfigureAwait(false);

        if (!await reader.ReadAsync(ct).ConfigureAwait(false))
        {
            return null;
        }

        return ReadRecord(reader);
    }

    internal static ApiKeyRecord ReadRecord(SqliteDataReader reader) => new(
        KeyId: reader.GetString(0),
        KeyPrefix: reader.GetString(1),
        SecretHash: reader.GetFieldValue<byte[]>(2),
        DisplayName: reader.GetString(3),
        Scopes: ScopeSerializer.Deserialize(reader.GetString(4)),
        ConstraintsJson: reader.IsDBNull(5) ? null : reader.GetString(5),
        CreatedUtc: SqliteValueParsing.ParseUtc(reader.GetString(6)),
        LastUsedUtc: SqliteValueParsing.ReadNullableUtc(reader, 7),
        RevokedUtc: SqliteValueParsing.ReadNullableUtc(reader, 8),
        ExpiresUtc: SqliteValueParsing.ReadNullableUtc(reader, 9));
}
