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

public sealed record ApiKeyOptions
{
    public string TokenPrefix { get; init; } = "mxgw";
    public string PepperSecretName { get; init; } = "";
    public string SqlitePath { get; init; } = "";
    public bool RunMigrationsOnStartup { get; init; } = true;
}

public enum ApiKeyFailure { MissingOrMalformed, KeyNotFound, KeyRevoked, PepperUnavailable, SecretMismatch, KeyExpired }

public sealed record ApiKeyIdentity(string KeyId, string DisplayName, IReadOnlySet<string> Scopes, object? Constraints);

public sealed record ApiKeyVerification(bool Succeeded, ApiKeyIdentity? Identity, ApiKeyFailure? Failure);

public interface IApiKeyVerifier
{
    Task<ApiKeyVerification> VerifyAsync(string authorizationHeader, CancellationToken ct);
}

/// <remarks>
/// As a positional record, <see cref="SecretHash"/> (<c>byte[]</c>) participates in equality
/// BY REFERENCE. Two records whose <c>SecretHash</c> arrays contain identical bytes are NOT
/// considered equal by <see cref="object.Equals(object?)"/>. Callers must not rely on value
/// equality for <see cref="SecretHash"/>; use <see cref="System.MemoryExtensions.SequenceEqual{T}"/>
/// or similar for content comparison.
/// </remarks>
public sealed record ApiKeyRecord(
    string KeyId, string KeyPrefix, byte[] SecretHash, string DisplayName,
    IReadOnlySet<string> Scopes, string? ConstraintsJson,
    DateTimeOffset CreatedUtc, DateTimeOffset? LastUsedUtc, DateTimeOffset? RevokedUtc,
    // Optional absolute expiry. NULL = never expires (the default for every key minted before this
    // field existed, so upgrading a store leaves existing keys valid). Enforced in ApiKeyVerifier.
    DateTimeOffset? ExpiresUtc = null);

public interface IApiKeyStore
{
    Task<ApiKeyRecord?> FindByKeyIdAsync(string keyId, CancellationToken ct);
    Task<ApiKeyRecord?> FindActiveByKeyIdAsync(string keyId, CancellationToken ct);
    Task MarkUsedAsync(string keyId, DateTimeOffset whenUtc, CancellationToken ct);
}

public sealed record ApiKeyAuditEntry(string? KeyId, string EventType, string? RemoteAddress, DateTimeOffset CreatedUtc, string? Details);

/// <summary>
/// Hash-free projection of an API-key record, safe to enumerate and surface to admins.
/// Deliberately omits <c>SecretHash</c> so that listing keys can never leak secret material.
/// </summary>
public sealed record ApiKeyListItem(
    string KeyId, string KeyPrefix, string DisplayName, IReadOnlySet<string> Scopes,
    string? ConstraintsJson, DateTimeOffset CreatedUtc, DateTimeOffset? LastUsedUtc, DateTimeOffset? RevokedUtc,
    // Optional absolute expiry (NULL = never expires); surfaced so admins can see/alert on expiry.
    DateTimeOffset? ExpiresUtc = null);

public interface IApiKeyAdminStore
{
    Task CreateAsync(ApiKeyRecord record, CancellationToken ct);
    Task<bool> RevokeAsync(string keyId, DateTimeOffset whenUtc, CancellationToken ct);
    Task<bool> RotateAsync(string keyId, byte[] newSecretHash, CancellationToken ct);
    Task<bool> DeleteAsync(string keyId, CancellationToken ct);

    /// <summary>Replaces the scope set on an existing key. Does not touch the secret. Returns false if the key does not exist.</summary>
    Task<bool> SetScopesAsync(string keyId, IReadOnlySet<string> scopes, CancellationToken ct);

    /// <summary>Enables (clears revoked_utc) or disables (sets revoked_utc) a key WITHOUT changing its secret. Returns false if the key does not exist.</summary>
    Task<bool> SetEnabledAsync(string keyId, bool enabled, DateTimeOffset whenUtc, CancellationToken ct);

    /// <summary>
    /// Enumerates all API keys as hash-free <see cref="ApiKeyListItem"/> projections, newest first.
    /// The secret hash is never selected, so callers cannot use this to recover secret material.
    /// </summary>
    Task<IReadOnlyList<ApiKeyListItem>> ListAsync(CancellationToken ct);
}

public interface IApiKeyAuditStore
{
    Task AppendAsync(ApiKeyAuditEntry entry, CancellationToken ct);
    Task<IReadOnlyList<ApiKeyAuditEntry>> ListRecentAsync(int limit, CancellationToken ct);
}
