namespace DodoSSH.Contracts;

/// <summary>
/// Argon2id parameters as stored and transmitted.
/// </summary>
/// <remarks>
/// Stored in plaintext per wrap row. Salts are not secrets, and keeping the parameters with the
/// wrap makes raising them later a per-user, unlock-time migration rather than a breaking
/// change — an older client can still open its own wrap.
/// <para>
/// <see cref="MemoryKibibytes"/> is kibibytes, matching both the storage column and libsodium.
/// Client code should go through <c>Argon2Profile</c> rather than handling the number directly;
/// see docs/crypto.md §2 for why that unit needs guarding.
/// </para>
/// </remarks>
/// <param name="Algorithm">KDF identifier. Currently always <c>argon2id</c>.</param>
/// <param name="Salt">16-byte salt.</param>
/// <param name="MemoryKibibytes">Memory cost in KiB.</param>
/// <param name="Passes">Number of passes.</param>
/// <param name="Parallelism">Lanes. Always 1; libsodium supports no other value.</param>
public sealed record KdfParameters(
    string Algorithm,
    byte[] Salt,
    int MemoryKibibytes,
    int Passes,
    int Parallelism);

/// <summary>
/// The self-describing, signed statement binding a user to their public keys.
/// </summary>
/// <remarks>
/// Its SHA-256 is used as the <c>nonce</c> of a fresh OIDC authorization, so the resulting ID
/// token is signed by the identity provider over exactly these keys. That is what stops the
/// DodoSSH server fabricating a key for a user who never enrolled. See ADR 0001.
/// </remarks>
/// <param name="Version">Statement format version.</param>
/// <param name="Issuer">OIDC issuer.</param>
/// <param name="Subject">OIDC subject.</param>
/// <param name="Email">Email at enrollment time, for display only.</param>
/// <param name="EncryptionPublicKey">X25519 public key, 32 bytes.</param>
/// <param name="SigningPublicKey">Ed25519 public key, 32 bytes.</param>
/// <param name="KeyGeneration">Generation of this key pair, starting at 1.</param>
/// <param name="CreatedAt">When the client generated the keys.</param>
/// <param name="DeviceName">Human-readable name of the enrolling device.</param>
public sealed record KeyStatement(
    int Version,
    string Issuer,
    string Subject,
    string? Email,
    byte[] EncryptionPublicKey,
    byte[] SigningPublicKey,
    int KeyGeneration,
    DateTimeOffset CreatedAt,
    string DeviceName);

/// <summary>
/// The personal vault a client creates as part of enrolling.
/// </summary>
/// <remarks>
/// <para>
/// The vault key is generated on the client and sealed to the user's own X25519 key, so the
/// server cannot produce this and cannot verify that <see cref="WrappedVaultKey"/> contains
/// anything in particular. It stores the bytes and the signature that attributes them.
/// </para>
/// <para>
/// <see cref="VaultId"/> is chosen by the client, not the server. That is what makes enrollment
/// safely retryable: a client whose request timed out re-sends the identical body and gets the
/// identical result, instead of accumulating a second vault it never learns about. It is also
/// required by the grant signature, which covers the vault id — see docs/crypto.md §7.
/// </para>
/// </remarks>
/// <param name="VaultId">Client-generated UUIDv7 for the new vault.</param>
/// <param name="Name">Display name. Plaintext, as all vault names are.</param>
/// <param name="WrappedVaultKey">
/// The vault key sealed to the enrolling user's own encryption key. Opaque to the server.
/// </param>
/// <param name="GrantSignature">
/// Ed25519 signature over the canonical grant tuple, by the key being enrolled. A self-grant
/// carries no key log head, because there is no third party whose key could have been
/// substituted.
/// </param>
/// <param name="GrantedAt">Signing timestamp, part of the signed tuple.</param>
public sealed record PersonalVaultRequest(
    Guid VaultId,
    string Name,
    byte[] WrappedVaultKey,
    byte[] GrantSignature,
    DateTimeOffset GrantedAt);

/// <summary>A request to enroll a user's first identity key pair.</summary>
/// <param name="Statement">The key statement.</param>
/// <param name="StatementSignature">Ed25519 self-signature over the statement.</param>
/// <param name="IdentityProviderToken">
/// An ID token whose <c>nonce</c> equals the SHA-256 of the statement. The server verifies it
/// against the provider's JWKS.
/// </param>
/// <param name="WrappedPrivateKey">
/// The user's secret bundle sealed under the passphrase-derived key. Opaque to the server.
/// </param>
/// <param name="KdfParameters">Parameters needed to re-derive the wrapping key.</param>
/// <param name="DevicePublicKey">
/// X25519 public key of this device, so the bundle can also be wrapped to the device and
/// unlocked without re-entering the passphrase.
/// </param>
/// <param name="DeviceWrappedPrivateKey">
/// The same bundle sealed to <paramref name="DevicePublicKey"/>. Required whenever a device key
/// is supplied: only the holder of the bundle can produce this, so a device key without its wrap
/// registers a device that can never unlock anything.
/// </param>
/// <param name="RecoveryWrappedPrivateKey">
/// The same bundle sealed under a recovery-code-derived key.
/// </param>
/// <param name="RecoveryKdfParameters">Parameters for the recovery wrap.</param>
/// <param name="PersonalVault">
/// The personal vault to create, with its key already wrapped to the enrolling user.
/// </param>
public sealed record EnrollmentRequest(
    KeyStatement Statement,
    byte[] StatementSignature,
    string IdentityProviderToken,
    byte[] WrappedPrivateKey,
    KdfParameters KdfParameters,
    byte[]? DevicePublicKey,
    byte[]? DeviceWrappedPrivateKey,
    byte[]? RecoveryWrappedPrivateKey,
    KdfParameters? RecoveryKdfParameters,
    PersonalVaultRequest PersonalVault);

/// <summary>Result of a successful enrollment.</summary>
/// <param name="UserId">The user's identifier.</param>
/// <param name="KeyGeneration">The generation now current.</param>
/// <param name="Fingerprint">Identity fingerprint over both public keys.</param>
/// <param name="PersonalVaultId">The automatically created personal vault.</param>
/// <param name="DeviceId">The enrolled device, when a device key was supplied.</param>
/// <param name="KeyLogSequence">
/// Position of this statement in the append-only key log. Clients cache the log head and
/// include it in signed operations, which is what makes a forked view detectable.
/// </param>
public sealed record EnrollmentResponse(
    Guid UserId,
    int KeyGeneration,
    byte[] Fingerprint,
    Guid PersonalVaultId,
    Guid? DeviceId,
    long KeyLogSequence);

/// <summary>A public-key directory entry.</summary>
/// <remarks>
/// Before wrapping a vault key to one of these, a client must verify it: check the identity
/// provider binding, compare against its pinned fingerprint, and confirm the key log head is
/// consistent. Wrapping to an unverified key is the one mistake that undoes end-to-end
/// encryption entirely.
/// </remarks>
/// <param name="UserId">The user.</param>
/// <param name="Email">Email, for display.</param>
/// <param name="DisplayName">Display name.</param>
/// <param name="EncryptionPublicKey">X25519 public key.</param>
/// <param name="SigningPublicKey">Ed25519 public key.</param>
/// <param name="Fingerprint">Identity fingerprint.</param>
/// <param name="KeyGeneration">Generation of this key pair.</param>
/// <param name="KeyLogSequence">Key log position of the statement that introduced it.</param>
public sealed record DirectoryEntry(
    Guid UserId,
    string? Email,
    string? DisplayName,
    byte[] EncryptionPublicKey,
    byte[] SigningPublicKey,
    byte[] Fingerprint,
    int KeyGeneration,
    long KeyLogSequence);

/// <summary>The caller's own profile and unlock state.</summary>
/// <param name="UserId">The user.</param>
/// <param name="Issuer">OIDC issuer.</param>
/// <param name="Subject">OIDC subject.</param>
/// <param name="Email">Email.</param>
/// <param name="DisplayName">Display name.</param>
/// <param name="EnrollmentRequired">
/// True when no identity key exists yet, so the client must run enrollment before anything else.
/// </param>
/// <param name="KeyGeneration">Current key generation, when enrolled.</param>
/// <param name="WrappedPrivateKey">
/// The passphrase wrap of the secret bundle, for unlocking on this device.
/// </param>
/// <param name="KdfParameters">Parameters to re-derive the wrapping key.</param>
/// <param name="Vaults">Vaults the caller can reach.</param>
public sealed record MeResponse(
    Guid UserId,
    string Issuer,
    string Subject,
    string? Email,
    string? DisplayName,
    bool EnrollmentRequired,
    int? KeyGeneration,
    byte[]? WrappedPrivateKey,
    KdfParameters? KdfParameters,
    IReadOnlyList<VaultSummary> Vaults);

/// <summary>A vault the caller can reach, with the wrapped key needed to open it.</summary>
/// <param name="VaultId">The vault.</param>
/// <param name="Name">Display name. Plaintext, and only for vaults, not for items.</param>
/// <param name="IsPersonal">Whether this is the caller's personal vault.</param>
/// <param name="TeamId">Owning team, for a team vault.</param>
/// <param name="KeyGeneration">Current key generation.</param>
/// <param name="Permissions">The caller's effective permissions, as a flags value.</param>
/// <param name="WrappedVaultKey">
/// The vault key sealed to the caller's X25519 key. Absent when a grant is awaiting re-wrap
/// after a rekey, in which case the vault is temporarily unreadable and a member holding Share
/// must complete it.
/// </param>
/// <param name="RekeyRequired">Whether a membership change has left this vault needing a rekey.</param>
/// <param name="PriorKeyWraps">
/// Generations before <paramref name="KeyGeneration"/> that this caller still holds a grant for.
/// <para>
/// Empty for a vault that has never been rotated, which is nearly all of them. It is not empty after
/// one, and it has to be served: an item is sealed under the generation in force when it was written,
/// so a client that held only the current key would find every item older than the rotation
/// undecryptable. See <c>VaultGrantService.RekeyAsync</c> for why old grants are kept rather than
/// revoked.
/// </para>
/// </param>
public sealed record VaultSummary(
    Guid VaultId,
    string Name,
    bool IsPersonal,
    Guid? TeamId,
    uint KeyGeneration,
    int Permissions,
    byte[]? WrappedVaultKey,
    bool RekeyRequired,
    IReadOnlyList<VaultKeyWrap>? PriorKeyWraps = null);

/// <summary>A vault key sealed to one recipient, at one generation.</summary>
/// <remarks>
/// Only ever the caller's own. <c>VaultGrantSummary</c> deliberately carries no wrap: serving every
/// member's sealed key to every member would widen what a stolen access token yields for nothing.
/// </remarks>
/// <param name="KeyGeneration">The generation this wrap opens.</param>
/// <param name="WrappedKey">The vault key sealed to the caller's X25519 key. Opaque.</param>
public sealed record VaultKeyWrap(uint KeyGeneration, byte[] WrappedKey);
