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

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

/// <summary>
/// SQLite-backed administration store for API keys (create, revoke, rotate, delete,
/// set-scopes, enable/disable).
/// </summary>
public sealed class SqliteApiKeyAdminStore(AuthSqliteConnectionFactory connectionFactory) : IApiKeyAdminStore
{
    /// <inheritdoc />
    public async Task CreateAsync(ApiKeyRecord record, CancellationToken ct)
    {
        ArgumentNullException.ThrowIfNull(record);

        await using SqliteConnection connection =
            await connectionFactory.OpenConnectionAsync(ct).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, expires_utc)
            VALUES (
                $key_id, $key_prefix, $secret_hash, $display_name, $scopes,
                $constraints, $created_utc, $last_used_utc, $revoked_utc, $expires_utc);
            """;
        command.Parameters.AddWithValue("$key_id", record.KeyId);
        command.Parameters.AddWithValue("$key_prefix", record.KeyPrefix);
        command.Parameters.Add("$secret_hash", SqliteType.Blob).Value = record.SecretHash;
        command.Parameters.AddWithValue("$display_name", record.DisplayName);
        command.Parameters.AddWithValue("$scopes", ScopeSerializer.Serialize(record.Scopes));
        command.Parameters.AddWithValue("$constraints", (object?)record.ConstraintsJson ?? DBNull.Value);
        command.Parameters.AddWithValue("$created_utc", record.CreatedUtc.ToString("O"));
        command.Parameters.AddWithValue("$last_used_utc", (object?)record.LastUsedUtc?.ToString("O") ?? DBNull.Value);
        command.Parameters.AddWithValue("$revoked_utc", (object?)record.RevokedUtc?.ToString("O") ?? DBNull.Value);
        command.Parameters.AddWithValue("$expires_utc", (object?)record.ExpiresUtc?.ToString("O") ?? DBNull.Value);

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

    /// <inheritdoc />
    public async Task<bool> RevokeAsync(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 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", whenUtc.ToString("O"));

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

        return rows > 0;
    }

    /// <inheritdoc />
    public async Task<bool> RotateAsync(string keyId, byte[] newSecretHash, CancellationToken ct)
    {
        ArgumentException.ThrowIfNullOrWhiteSpace(keyId);
        ArgumentNullException.ThrowIfNull(newSecretHash);

        await using SqliteConnection connection =
            await connectionFactory.OpenConnectionAsync(ct).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 = newSecretHash;

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

        return rows > 0;
    }

    /// <inheritdoc />
    public async Task<bool> SetScopesAsync(string keyId, IReadOnlySet<string> scopes, CancellationToken ct)
    {
        ArgumentException.ThrowIfNullOrWhiteSpace(keyId);
        ArgumentNullException.ThrowIfNull(scopes);

        await using SqliteConnection connection =
            await connectionFactory.OpenConnectionAsync(ct).ConfigureAwait(false);

        await using SqliteCommand command = connection.CreateCommand();
        command.CommandText = """
            UPDATE api_keys
            SET scopes = $scopes
            WHERE key_id = $key_id;
            """;
        command.Parameters.AddWithValue("$key_id", keyId);
        command.Parameters.AddWithValue("$scopes", ScopeSerializer.Serialize(scopes));

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

        return rows > 0;
    }

    /// <inheritdoc />
    public async Task<bool> SetEnabledAsync(string keyId, bool enabled, DateTimeOffset whenUtc, CancellationToken ct)
    {
        ArgumentException.ThrowIfNullOrWhiteSpace(keyId);

        await using SqliteConnection connection =
            await connectionFactory.OpenConnectionAsync(ct).ConfigureAwait(false);

        await using SqliteCommand command = connection.CreateCommand();

        // Reversible toggle: NO `revoked_utc IS NULL` guard (unlike RevokeAsync), so it works
        // regardless of current state. Deliberately leaves secret_hash and last_used_utc untouched
        // — that is what distinguishes re-enable from RotateAsync.
        if (enabled)
        {
            command.CommandText = """
                UPDATE api_keys
                SET revoked_utc = NULL
                WHERE key_id = $key_id;
                """;
            command.Parameters.AddWithValue("$key_id", keyId);
        }
        else
        {
            command.CommandText = """
                UPDATE api_keys
                SET revoked_utc = $revoked_utc
                WHERE key_id = $key_id;
                """;
            command.Parameters.AddWithValue("$key_id", keyId);
            command.Parameters.AddWithValue("$revoked_utc", whenUtc.ToString("O"));
        }

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

        return rows > 0;
    }

    /// <inheritdoc />
    public async Task<bool> DeleteAsync(string keyId, CancellationToken ct)
    {
        ArgumentException.ThrowIfNullOrWhiteSpace(keyId);

        await using SqliteConnection connection =
            await connectionFactory.OpenConnectionAsync(ct).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(ct).ConfigureAwait(false);

        return rows > 0;
    }

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

        await using SqliteCommand command = connection.CreateCommand();

        // Deliberately omits secret_hash so listing can never leak secret material.
        command.CommandText = """
            SELECT key_id, key_prefix, display_name, scopes, constraints,
                   created_utc, last_used_utc, revoked_utc, expires_utc
            FROM api_keys
            ORDER BY created_utc DESC, key_id DESC;
            """;

        List<ApiKeyListItem> items = [];

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

        while (await reader.ReadAsync(ct).ConfigureAwait(false))
        {
            items.Add(new ApiKeyListItem(
                KeyId: reader.GetString(0),
                KeyPrefix: reader.GetString(1),
                DisplayName: reader.GetString(2),
                Scopes: ScopeSerializer.Deserialize(reader.GetString(3)),
                ConstraintsJson: reader.IsDBNull(4) ? null : reader.GetString(4),
                CreatedUtc: SqliteValueParsing.ParseUtc(reader.GetString(5)),
                LastUsedUtc: SqliteValueParsing.ReadNullableUtc(reader, 6),
                RevokedUtc: SqliteValueParsing.ReadNullableUtc(reader, 7),
                ExpiresUtc: SqliteValueParsing.ReadNullableUtc(reader, 8)));
        }

        return items;
    }
}
