namespace DodoSSH.Crypto;

/// <summary>
/// Constants of the DodoSSH cryptographic specification, version 1.
/// </summary>
/// <remarks>
/// docs/crypto.md is normative; this type must agree with it exactly. The values here are
/// written into stored data, so changing one reinterprets or orphans existing ciphertext.
/// Only clients can re-encrypt, so the server cannot migrate a change here.
/// </remarks>
public static class CryptoSpec
{
    /// <summary>Magic prefix identifying a DSH1 envelope.</summary>
    public static ReadOnlySpan<byte> EnvelopeMagic => "DSH1"u8;

    /// <summary>Magic prefix of the canonical AAD encoding.</summary>
    public static ReadOnlySpan<byte> AadMagic => "dsh1\n"u8;

    /// <summary>Length of the canonical AAD encoding, before hashing.</summary>
    public const int AadEncodedLength = 64;

    /// <summary>Version of the AAD derivation rule that payloads are bound to.</summary>
    /// <remarks>
    /// Stored per row as <c>payload_aad_version</c> so a future change can be applied lazily,
    /// re-encrypting on next write rather than in a migration.
    /// </remarks>
    public const byte CurrentAadVersion = 1;

    /// <summary>Version of the relational schema that AAD is bound to.</summary>
    public const ushort CurrentSchemaVersion = 1;

    /// <summary>Size of a symmetric content or wrapping key.</summary>
    public const int SymmetricKeySize = 32;

    /// <summary>
    /// Size of the passphrase-derived master key, which subkeys are expanded from.
    /// </summary>
    /// <remarks>
    /// 64 bytes, not 32, because it is the PRK of an HKDF-SHA512 expansion and .NET's
    /// <c>HKDF.Expand</c> rejects a PRK shorter than the hash output. See docs/crypto.md §3.
    /// </remarks>
    public const int MasterKeySize = 64;

    /// <summary>Size of an X25519 or Ed25519 public key.</summary>
    public const int PublicKeySize = 32;

    /// <summary>Size of an Ed25519 signature.</summary>
    public const int SignatureSize = 64;

    /// <summary>Size of a SHA-256 output, used for AAD, fingerprints and digests.</summary>
    public const int DigestSize = 32;

    /// <summary>Size of an AEAD authentication tag.</summary>
    public const int TagSize = 16;

    /// <summary>Recommended salt length for password-based derivation.</summary>
    public const int SaltSize = 16;

    /// <summary>Identifies the construction used by a DSH1 envelope.</summary>
    public enum AlgorithmId : byte
    {
        /// <summary>Reserved; never written, and rejected on read.</summary>
        Unspecified = 0,

        /// <summary>Symmetric content encryption under a known key.</summary>
        XChaCha20Poly1305 = 1,

        /// <summary>
        /// Symmetric fallback for environments without XChaCha20-Poly1305. Accepted on read,
        /// not currently emitted.
        /// </summary>
        Aes256Gcm = 2,

        /// <summary>Anonymous-sender seal to an X25519 public key. See docs/crypto.md §6.</summary>
        SealToX25519 = 3,

        // 4 is reserved for a hybrid X25519 + ML-KEM-768 seal. Claimed now so it cannot be
        // reused: store-now-decrypt-later is a real threat for long-lived SSH keys.
    }

    /// <summary>
    /// What a given ciphertext is, so that one kind of blob can never be substituted for
    /// another. Part of the AAD; see docs/crypto.md §4.2.
    /// </summary>
    public enum AadPurpose : byte
    {
        /// <summary>Not a legal value.</summary>
        Unspecified = 0,

        /// <summary>A wrap of the user's secret bundle.</summary>
        UserSecretBundle = 1,

        /// <summary>A vault key sealed to a member's public key.</summary>
        VaultKeyGrant = 2,

        /// <summary>An item data key wrapped under a vault key.</summary>
        ItemDataKey = 3,

        /// <summary>Item plaintext under its data key.</summary>
        ItemPayload = 4,

        /// <summary>Encrypted item metadata under its data key.</summary>
        ItemMetadata = 5,

        /// <summary>A record in a client's on-disk cache.</summary>
        LocalCache = 6,
    }

    /// <summary>
    /// The kind of entity a ciphertext belongs to. Part of the AAD; see docs/crypto.md §4.3.
    /// Append only.
    /// </summary>
    public enum AadResourceType : byte
    {
        /// <summary>No resource. Legal only where the purpose implies none.</summary>
        None = 0,

        /// <summary>A user account.</summary>
        User = 1,

        /// <summary>An enrolled device.</summary>
        Device = 2,

        /// <summary>A vault.</summary>
        Vault = 3,

        /// <summary>An SSH host.</summary>
        Host = 4,

        /// <summary>A credential.</summary>
        Credential = 5,

        /// <summary>An SSH key pair.</summary>
        SshKey = 6,

        /// <summary>A host group.</summary>
        HostGroup = 7,

        /// <summary>A tag.</summary>
        Tag = 8,

        /// <summary>A snippet.</summary>
        Snippet = 9,

        /// <summary>A port forward.</summary>
        PortForward = 10,

        /// <summary>A known SSH host key.</summary>
        KnownHostKey = 11,

        // 12 and 13 close a hole rather than adding a feature. Contracts.SyncEntityType has carried
        // HostTag and HostCredential since it was frozen, so those items are syncable — but with no
        // resource type here, their payloads had nothing to bind an AAD to. Added 2026-07-29, while
        // the enum is still append-only and no such item has been stored.

        /// <summary>A host-to-tag association.</summary>
        HostTag = 12,

        /// <summary>A host-to-credential association.</summary>
        HostCredential = 13,

        /// <summary>One connection that was made, and how long it lasted.</summary>
        /// <remarks>
        /// 14 and 15 are the first members here that are not one-behind their <c>SyncEntityType</c>
        /// counterparts by the same constant offset — the two enums drifted apart when 12 and 13 closed a
        /// hole. That is why nothing casts between them and why <c>AadResourceTypeTests</c> pins each pairing
        /// by name.
        /// </remarks>
        ConnectionLogEntry = 14,

        /// <summary>One create, edit or delete of a vault item.</summary>
        ActivityLogEntry = 15,

        /// <summary>An S3-compatible bucket, and the credentials that reach it.</summary>
        ObjectStore = 16,
    }

    /// <summary>HKDF info labels. Domain-separated so one subkey cannot stand in for another.</summary>
    public static class DerivationLabels
    {
        /// <summary>Derives the key-encryption key that wraps the secret bundle.</summary>
        public static ReadOnlySpan<byte> PassphraseKek => "dsh1/kek/passphrase/v1"u8;

        /// <summary>
        /// Derives the key that encrypts the client's on-disk cache.
        /// </summary>
        /// <remarks>
        /// <b>v2 derives from the secret bundle, where v1 derived from the passphrase master key.</b> The
        /// cache key has to be reachable through every door that opens the vault — passphrase, device,
        /// recovery code, escrow — and only the bundle is common to all of them. Under v1 a device unlock
        /// could open the identity and still not read the cache it had written, and a recovery-code unlock
        /// would silently derive a different key and orphan the whole cache. Bumping the label rather than
        /// reusing it is what makes an old cache fail to open instead of decrypting to nonsense.
        /// </remarks>
        public static ReadOnlySpan<byte> LocalCache => "dsh1/localcache/v2"u8;

        /// <summary>Prefix of the SealTo key-derivation info, concatenated with the AAD.</summary>
        public static ReadOnlySpan<byte> SealTo => "dsh1/sealto/v1|"u8;

        /// <summary>Prefix of the identity key fingerprint input.</summary>
        public static ReadOnlySpan<byte> Fingerprint => "dsh1/fp/v1"u8;
    }

    /// <summary>Ed25519 signing contexts. Prevents a signature being replayed in another role.</summary>
    public static class SigningContexts
    {
        /// <summary>Signs an enrollment key statement.</summary>
        public static ReadOnlySpan<byte> KeyStatement => "dsh1/sig/keystatement/v1"u8;

        /// <summary>Signs a vault key grant tuple.</summary>
        public static ReadOnlySpan<byte> Grant => "dsh1/sig/grant/v1"u8;

        /// <summary>Signs an admin attestation of another user's key statement.</summary>
        public static ReadOnlySpan<byte> Attestation => "dsh1/sig/attestation/v1"u8;
    }
}
