namespace DodoSSH.Crypto;

/// <summary>
/// Named constructors for every AAD descriptor the specification defines. See docs/crypto.md §4.
/// </summary>
/// <remarks>
/// <para>
/// The AAD binding is the most valuable structural property in the design: it is what stops a
/// server that holds every ciphertext from pasting one row's bytes onto another, rolling a row back
/// to a superseded key generation, replaying a revoked grant, or substituting a metadata blob for a
/// payload. None of that follows from access control.
/// </para>
/// <para>
/// All of it also depends on callers getting the purpose, resource type and ids right at every call
/// site. Hand-constructing <see cref="AadDescriptor"/> makes that a matter of care; going through
/// these factories makes it a matter of picking the right method name, which is the difference
/// between a property that holds and one that mostly holds.
/// </para>
/// </remarks>
public static class DshAad
{
    /// <summary>
    /// Binds a wrap of the user's secret bundle to that user and key generation.
    /// </summary>
    /// <remarks>
    /// <para>
    /// Every wrap kind — passphrase, device, recovery, escrow — protects the same bundle and uses
    /// this same descriptor. In particular a device wrap is <em>not</em> bound to its device row:
    /// the client cannot be, since the server assigns the device id after the wrap is built, and it
    /// need not be, because the wrap is sealed to that device's public key. Relocating the row to
    /// another device gains an attacker nothing they could open.
    /// </para>
    /// <para>
    /// Including the generation means a server cannot serve back a superseded bundle after a key
    /// rotation. The client learns the current generation from <c>/me</c>, so a lie there produces
    /// a tag failure rather than a silent downgrade.
    /// </para>
    /// </remarks>
    /// <param name="userId">
    /// The user's identifier, as assigned by the server. A client must therefore read
    /// <c>/api/v1/me</c> — which provisions the account and returns its id even before enrollment —
    /// before it can build a wrap.
    /// </param>
    /// <param name="keyGeneration">Generation of the identity key pair inside the bundle.</param>
    public static AadDescriptor UserSecretBundle(Guid userId, uint keyGeneration = 1) =>
        AadDescriptor.Create(
            CryptoSpec.AadPurpose.UserSecretBundle,
            CryptoSpec.AadResourceType.User,
            userId,
            keyGeneration: keyGeneration);

    /// <summary>
    /// Binds a vault key grant to its vault and generation.
    /// </summary>
    /// <remarks>
    /// The generation is what makes revocation stick against a malicious server: after a rekey it
    /// cannot re-serve a previous generation's grant to a removed member, because the AAD no longer
    /// matches. That is a bound on future reads only — anything already downloaded is already gone,
    /// which is why offboarding means rotating the SSH credentials themselves. See ADR 0001.
    /// </remarks>
    public static AadDescriptor VaultKeyGrant(Guid vaultId, uint keyGeneration) =>
        AadDescriptor.Create(
            CryptoSpec.AadPurpose.VaultKeyGrant,
            CryptoSpec.AadResourceType.Vault,
            vaultId,
            keyGeneration: keyGeneration);

    /// <summary>Binds an item's data key, wrapped under the vault key, to that item and version.</summary>
    public static AadDescriptor ItemDataKey(
        CryptoSpec.AadResourceType resourceType,
        Guid itemId,
        uint keyGeneration,
        uint itemVersion) =>
        AadDescriptor.Create(
            CryptoSpec.AadPurpose.ItemDataKey,
            resourceType,
            itemId,
            keyGeneration: keyGeneration,
            itemVersion: itemVersion);

    /// <summary>Binds an item's payload to the item, its data key, generation and version.</summary>
    /// <param name="resourceType">What kind of item this is.</param>
    /// <param name="itemId">The item.</param>
    /// <param name="dataKeyId">
    /// The data key the payload is under — the <c>content_key_id</c> column. Reserved so that
    /// per-item grants can make item-level access cryptographic in M5 without a migration.
    /// </param>
    /// <param name="keyGeneration">Vault key generation in force.</param>
    /// <param name="itemVersion">Item version, so an earlier version cannot be replayed.</param>
    public static AadDescriptor ItemPayload(
        CryptoSpec.AadResourceType resourceType,
        Guid itemId,
        Guid dataKeyId,
        uint keyGeneration,
        uint itemVersion) =>
        AadDescriptor.Create(
            CryptoSpec.AadPurpose.ItemPayload,
            resourceType,
            itemId,
            dataKeyId,
            keyGeneration,
            itemVersion);

    /// <summary>
    /// Binds an item's encrypted metadata.
    /// </summary>
    /// <remarks>
    /// A distinct purpose from <see cref="ItemPayload"/> on the same item, which is what stops a
    /// server swapping one blob for the other — a substitution that would otherwise leave a client
    /// decrypting a password where it expected a display name.
    /// </remarks>
    public static AadDescriptor ItemMetadata(
        CryptoSpec.AadResourceType resourceType,
        Guid itemId,
        Guid dataKeyId,
        uint keyGeneration,
        uint itemVersion) =>
        AadDescriptor.Create(
            CryptoSpec.AadPurpose.ItemMetadata,
            resourceType,
            itemId,
            dataKeyId,
            keyGeneration,
            itemVersion);

    /// <summary>
    /// Binds a record in the client's own on-disk cache to the row that holds it.
    /// </summary>
    /// <remarks>
    /// <para>
    /// Separate from every server-side purpose so a cache record can never be accepted as vault
    /// content, nor the reverse. The threat model differs too: the cache is local, so the adversary
    /// is a process or a backup with access to the file rather than the server.
    /// </para>
    /// <para>
    /// <b>Changed 2026-07-29</b> from taking the user id to taking the record's identity. The user
    /// was already bound by the key — <c>LocalCacheKey</c> is derived from that user's master key, so
    /// another user's record cannot decrypt at all — which left the AAD binding nothing, and a cache
    /// record could be moved to a different row of the same user's cache. For plaintext columns like
    /// a relay address that is not academic: swapping two rows would point one host's connection at
    /// another host's address. No cache has ever been written, so there is nothing to migrate.
    /// </para>
    /// </remarks>
    /// <param name="resourceType">What kind of item the record belongs to.</param>
    /// <param name="recordId">The row it belongs to — an item id, or a conflict entry's own id.</param>
    public static AadDescriptor LocalCache(
        CryptoSpec.AadResourceType resourceType,
        Guid recordId) =>
        AadDescriptor.Create(
            CryptoSpec.AadPurpose.LocalCache,
            resourceType,
            recordId);
}
