using Microsoft.Data.Sqlite;

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

/// <summary>
/// SQLite-backed storage for API key administration (create, list, revoke, rotate).
/// </summary>
public sealed class SqliteApiKeyAdminStore(AuthSqliteConnectionFactory connectionFactory) : IApiKeyAdminStore
{
    /// <inheritdoc />
    public async Task CreateAsync(ApiKeyCreateRequest request, CancellationToken cancellationToken)
    {
        await using SqliteConnection connection = await connectionFactory.OpenConnectionAsync(cancellationToken).ConfigureAwait(false);

        await using SqliteCommand command = connection.CreateCommand();
        command.CommandText = """
            INSERT INTO api_keys (
                key_id,
                key_prefix,
                secret_hash,
                display_name,
                scopes,
                constraints,
                created_utc,
                last_used_utc,
                revoked_utc)
            VALUES (
                $key_id,
                $key_prefix,
                $secret_hash,
                $display_name,
                $scopes,
                $constraints,
                $created_utc,
                NULL,
                NULL);
            """;
        AddCreateParameters(command, request);

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

    /// <inheritdoc />
    public async Task<IReadOnlyList<ApiKeyRecord>> ListAsync(CancellationToken cancellationToken)
    {
        await using SqliteConnection connection = await connectionFactory.OpenConnectionAsync(cancellationToken).ConfigureAwait(false);

        await using SqliteCommand command = connection.CreateCommand();
        command.CommandText = """
            SELECT key_id, key_prefix, secret_hash, display_name, scopes, constraints, created_utc, last_used_utc, revoked_utc
            FROM api_keys
            ORDER BY key_id;
            """;

        List<ApiKeyRecord> records = [];

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

        while (await reader.ReadAsync(cancellationToken).ConfigureAwait(false))
        {
            records.Add(ApiKeyRecordReader.Read(reader));
        }

        return records;
    }

    /// <inheritdoc />
    public async Task<bool> RevokeAsync(string keyId, DateTimeOffset revokedUtc, CancellationToken cancellationToken)
    {
        await using SqliteConnection connection = await connectionFactory.OpenConnectionAsync(cancellationToken).ConfigureAwait(false);

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

        int rows = await command.ExecuteNonQueryAsync(cancellationToken).ConfigureAwait(false);

        return rows > 0;
    }

    /// <inheritdoc />
    public async Task<bool> RotateAsync(
        string keyId,
        byte[] secretHash,
        DateTimeOffset rotatedUtc,
        CancellationToken cancellationToken)
    {
        await using SqliteConnection connection = await connectionFactory.OpenConnectionAsync(cancellationToken).ConfigureAwait(false);

        await using SqliteCommand command = connection.CreateCommand();
        command.CommandText = """
            UPDATE api_keys
            SET secret_hash = $secret_hash,
                last_used_utc = NULL,
                revoked_utc = NULL
            WHERE key_id = $key_id;
            """;
        command.Parameters.AddWithValue("$key_id", keyId);
        command.Parameters.Add("$secret_hash", SqliteType.Blob).Value = secretHash;

        int rows = await command.ExecuteNonQueryAsync(cancellationToken).ConfigureAwait(false);

        return rows > 0;
    }

    /// <inheritdoc />
    public async Task<bool> DeleteAsync(string keyId, CancellationToken cancellationToken)
    {
        await using SqliteConnection connection = await connectionFactory.OpenConnectionAsync(cancellationToken).ConfigureAwait(false);

        await using SqliteCommand command = connection.CreateCommand();
        command.CommandText = """
            DELETE FROM api_keys
            WHERE key_id = $key_id AND revoked_utc IS NOT NULL;
            """;
        command.Parameters.AddWithValue("$key_id", keyId);

        int rows = await command.ExecuteNonQueryAsync(cancellationToken).ConfigureAwait(false);

        return rows > 0;
    }

    private static void AddCreateParameters(SqliteCommand command, ApiKeyCreateRequest request)
    {
        command.Parameters.AddWithValue("$key_id", request.KeyId);
        command.Parameters.AddWithValue("$key_prefix", request.KeyPrefix);
        command.Parameters.Add("$secret_hash", SqliteType.Blob).Value = request.SecretHash;
        command.Parameters.AddWithValue("$display_name", request.DisplayName);
        command.Parameters.AddWithValue("$scopes", ApiKeyScopeSerializer.Serialize(request.Scopes));
        command.Parameters.AddWithValue(
            "$constraints",
            (object?)ApiKeyConstraintSerializer.Serialize(request.Constraints) ?? DBNull.Value);
        command.Parameters.AddWithValue("$created_utc", request.CreatedUtc.ToString("O"));
    }
}
