namespace ZB.MOM.WW.Secrets.Abstractions;

/// <summary>
/// Persists encrypted secret rows and exposes the read, list, and replication operations the
/// core services build on. The store only ever handles the encrypted <see cref="StoredSecret"/>
/// representation and its safe <see cref="SecretMetadata"/> projection — it never sees plaintext.
/// Deletes are soft (tombstones) so removals can propagate across a cluster.
/// </summary>
public interface ISecretStore
{
    /// <summary>
    /// Returns the row for <paramref name="name"/>, including a tombstoned row, or <c>null</c>
    /// if no row exists.
    /// </summary>
    /// <param name="name">The secret to fetch.</param>
    /// <param name="ct">A token to cancel the operation.</param>
    /// <returns>The stored row, or <c>null</c> if absent.</returns>
    Task<StoredSecret?> GetAsync(SecretName name, CancellationToken ct);

    /// <summary>
    /// Inserts the row, or overwrites an existing row in place, bumping its revision and
    /// updated timestamp.
    /// </summary>
    /// <param name="row">The encrypted row to persist.</param>
    /// <param name="ct">A token to cancel the operation.</param>
    /// <returns>A task that completes when the row is persisted.</returns>
    Task UpsertAsync(StoredSecret row, CancellationToken ct);

    /// <summary>
    /// Soft-deletes (tombstones) the row for <paramref name="name"/> so the deletion can
    /// propagate to peers.
    /// </summary>
    /// <param name="name">The secret to tombstone.</param>
    /// <param name="actor">The principal to record as <c>updated_by</c>, if known.</param>
    /// <param name="ct">A token to cancel the operation.</param>
    /// <returns><c>true</c> if a row was tombstoned; <c>false</c> if the row was absent.</returns>
    Task<bool> DeleteAsync(SecretName name, string? actor, CancellationToken ct);

    /// <summary>
    /// Lists the safe metadata projection for all secrets — never ciphertext.
    /// </summary>
    /// <param name="includeDeleted">
    /// When <c>true</c>, tombstoned rows are included; otherwise they are omitted.
    /// </param>
    /// <param name="ct">A token to cancel the operation.</param>
    /// <returns>The metadata for the matching secrets.</returns>
    Task<IReadOnlyList<SecretMetadata>> ListAsync(bool includeDeleted, CancellationToken ct);

    /// <summary>
    /// Returns a compact manifest — name, revision, updated timestamp, and tombstone flag per
    /// secret — used for cluster anti-entropy reconciliation without exchanging ciphertext.
    /// </summary>
    /// <param name="ct">A token to cancel the operation.</param>
    /// <returns>The manifest entries for all secrets.</returns>
    Task<IReadOnlyList<SecretManifestEntry>> GetManifestAsync(CancellationToken ct);

    /// <summary>
    /// Applies a row received from a peer using last-writer-wins on
    /// (<see cref="StoredSecret.UpdatedUtc"/>, <see cref="StoredSecret.Revision"/>). A row that
    /// is not newer than the local row is ignored.
    /// </summary>
    /// <param name="row">The replicated encrypted row to apply.</param>
    /// <param name="ct">A token to cancel the operation.</param>
    /// <returns>A task that completes when the row has been applied or ignored.</returns>
    Task ApplyReplicatedAsync(StoredSecret row, CancellationToken ct);

    /// <summary>
    /// Applies a KEK re-wrap in place: overwrites <b>only</b> the KEK-wrap envelope columns
    /// (<see cref="StoredSecret.WrappedDek"/>/<see cref="StoredSecret.WrapNonce"/>/<see cref="StoredSecret.WrapTag"/>)
    /// and <see cref="StoredSecret.KekId"/> for the row named by <paramref name="rewrappedRow"/>,
    /// leaving the sealed body, revision, timestamps, tombstone flag, and audit stamps untouched.
    /// </summary>
    /// <remarks>
    /// <para>
    /// This is the persistence half of KEK rotation. It deliberately does <b>not</b> bump the
    /// revision or refresh <c>updated_utc</c>: a re-wrap does not change the secret's plaintext, so
    /// it must stay invisible to the (updated_utc, revision) last-writer-wins ordering used by
    /// cluster anti-entropy — otherwise every node's independent re-wrap would churn replication.
    /// </para>
    /// <para>
    /// The update is a <b>compare-and-swap</b> on <paramref name="expectedCurrentWrappedDek"/>: the
    /// row is updated only if its current <see cref="StoredSecret.WrappedDek"/> still equals the DEK
    /// wrap that was unwrapped to produce <paramref name="rewrappedRow"/>. A concurrent write to the
    /// same secret (a <c>set</c>/<c>rotate</c> that generated a new DEK) changes that wrap, so the
    /// CAS matches 0 rows and the row is left untouched — rather than corrupting it by pairing a
    /// stale re-wrap with a newer body. The caller re-processes a 0-row result (fetch → re-wrap).
    /// </para>
    /// </remarks>
    /// <param name="rewrappedRow">
    /// The row carrying the new wrap envelope + <see cref="StoredSecret.KekId"/>; matched by
    /// <see cref="StoredSecret.Name"/>. Only the four wrap-related columns are read from it.
    /// </param>
    /// <param name="expectedCurrentWrappedDek">
    /// The <see cref="StoredSecret.WrappedDek"/> the row must still carry for the swap to apply — the
    /// wrap that was unwrapped to produce <paramref name="rewrappedRow"/>.
    /// </param>
    /// <param name="ct">A token to cancel the operation.</param>
    /// <returns>
    /// <c>true</c> if the row was updated; <c>false</c> if no row matched (absent, or its wrapped DEK
    /// changed under a concurrent write).
    /// </returns>
    Task<bool> ApplyRewrapAsync(
        StoredSecret rewrappedRow, byte[] expectedCurrentWrappedDek, CancellationToken ct);
}
