namespace DodoSSH.Domain;

/// <summary>
/// A user, keyed on their identity-provider subject.
/// </summary>
/// <remarks>
/// Provisioned just-in-time on first authenticated request. Matching an existing account by
/// email is an account-takeover vector and is therefore opt-in configuration, never the default.
/// </remarks>
public sealed class UserAccount
{
    /// <summary>Primary key. UUIDv7, generated by the application.</summary>
    public Guid Id { get; set; }

    /// <summary>OIDC issuer. Part of the natural key, so multiple providers can coexist.</summary>
    public string Issuer { get; set; } = string.Empty;

    /// <summary>OIDC subject.</summary>
    public string Subject { get; set; } = string.Empty;

    /// <summary>Email, for display and invitations. Case-insensitive.</summary>
    public string? Email { get; set; }

    /// <summary>Display name.</summary>
    public string? DisplayName { get; set; }

    /// <summary>Lifecycle state.</summary>
    public UserStatus Status { get; set; } = UserStatus.Active;

    /// <summary>When the identity key was first enrolled; null until then.</summary>
    public DateTimeOffset? EnrolledAtUtc { get; set; }

    /// <summary>Creation timestamp.</summary>
    public DateTimeOffset CreatedAtUtc { get; set; }

    /// <summary>Last modification timestamp.</summary>
    public DateTimeOffset UpdatedAtUtc { get; set; }

    /// <summary>Last authenticated request.</summary>
    public DateTimeOffset? LastSeenAtUtc { get; set; }

    /// <summary>Soft-delete marker.</summary>
    public DateTimeOffset? DeletedAtUtc { get; set; }

    /// <summary>Identity key generations, current and historic.</summary>
    public ICollection<UserKey> Keys { get; } = [];

    /// <summary>Wraps of this user's secret bundle.</summary>
    public ICollection<UserKeyWrap> KeyWraps { get; } = [];

    /// <summary>Enrolled devices.</summary>
    public ICollection<Device> Devices { get; } = [];
}

/// <summary>
/// One generation of a user's identity key pair. Public halves only.
/// </summary>
/// <remarks>
/// <para>
/// A separate table from the first migration, because retrofitting key rotation onto columns
/// hanging off the user row is painful.
/// </para>
/// <para>
/// Encryption and signing keys are distinct: reusing one key for both agreement and signatures
/// is a standing cryptographic mistake, and the signing key is what gives grants attribution
/// that the server cannot forge.
/// </para>
/// </remarks>
public sealed class UserKey
{
    /// <summary>Primary key.</summary>
    public Guid Id { get; set; }

    /// <summary>Owning user.</summary>
    public Guid UserId { get; set; }

    /// <summary>Owning user.</summary>
    public UserAccount? User { get; set; }

    /// <summary>Generation number, starting at 1.</summary>
    public int Generation { get; set; }

    /// <summary>X25519 public key, 32 bytes. Used for wrapping.</summary>
    public byte[] EncryptionPublicKey { get; set; } = [];

    /// <summary>Ed25519 public key, 32 bytes. Used for signatures.</summary>
    public byte[] SigningPublicKey { get; set; } = [];

    /// <summary>SHA-256 fingerprint over both public keys. See docs/crypto.md §8.</summary>
    public byte[] FingerprintSha256 { get; set; } = [];

    /// <summary>The signed key statement, verbatim, as JSON.</summary>
    public string Statement { get; set; } = string.Empty;

    /// <summary>Ed25519 self-signature over the statement.</summary>
    public byte[] StatementSignature { get; set; } = [];

    /// <summary>
    /// Evidence that the identity provider signed over this statement's hash: the verified
    /// claims of the binding ID token, as JSON. Retained so a client can audit the binding
    /// rather than trusting our word for it.
    /// </summary>
    public string? IdentityProviderBinding { get; set; }

    /// <summary>Whether this is the user's current generation.</summary>
    public bool IsCurrent { get; set; }

    /// <summary>Creation timestamp.</summary>
    public DateTimeOffset CreatedAtUtc { get; set; }

    /// <summary>When this generation was superseded or revoked.</summary>
    public DateTimeOffset? RevokedAtUtc { get; set; }
}

/// <summary>
/// One wrap of a user's secret bundle.
/// </summary>
/// <remarks>
/// KDF parameters are stored in plaintext beside the wrap. 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.
/// </remarks>
public sealed class UserKeyWrap
{
    /// <summary>Primary key.</summary>
    public Guid Id { get; set; }

    /// <summary>Owning user.</summary>
    public Guid UserId { get; set; }

    /// <summary>Owning user.</summary>
    public UserAccount? User { get; set; }

    /// <summary>Which key this wrap is under.</summary>
    public UserKeyWrapKind Kind { get; set; }

    /// <summary>The device, for <see cref="UserKeyWrapKind.Device"/> wraps.</summary>
    public Guid? DeviceId { get; set; }

    /// <summary>The device, for <see cref="UserKeyWrapKind.Device"/> wraps.</summary>
    public Device? Device { get; set; }

    /// <summary>The wrapped bundle: an opaque DSH1 envelope.</summary>
    public byte[] Wrap { get; set; } = [];

    /// <summary>Optimistic concurrency guard against two devices racing a passphrase change.</summary>
    public int WrapVersion { get; set; }

    /// <summary>KDF identifier, for password-derived wraps.</summary>
    public string? KdfAlgorithm { get; set; }

    /// <summary>KDF salt. Not a secret.</summary>
    public byte[]? KdfSalt { get; set; }

    /// <summary>Argon2id memory cost, in kibibytes.</summary>
    public int? KdfMemoryKibibytes { get; set; }

    /// <summary>Argon2id pass count.</summary>
    public int? KdfPasses { get; set; }

    /// <summary>Argon2id lanes. Always 1; libsodium supports no other value.</summary>
    public int? KdfParallelism { get; set; }

    /// <summary>Creation timestamp.</summary>
    public DateTimeOffset CreatedAtUtc { get; set; }

    /// <summary>Last successful unlock through this wrap.</summary>
    public DateTimeOffset? LastUsedAtUtc { get; set; }
}

/// <summary>
/// A device enrolled for unlock without re-entering the passphrase.
/// </summary>
public sealed class Device
{
    /// <summary>Primary key.</summary>
    public Guid Id { get; set; }

    /// <summary>Owning user.</summary>
    public Guid UserId { get; set; }

    /// <summary>Owning user.</summary>
    public UserAccount? User { get; set; }

    /// <summary>Human-readable name.</summary>
    public string Name { get; set; } = string.Empty;

    /// <summary>Operating system family.</summary>
    public DevicePlatform Platform { get; set; }

    /// <summary>The device's X25519 public key. Its private half lives in the OS keystore.</summary>
    public byte[] PublicKey { get; set; } = [];

    /// <summary>Enrollment timestamp.</summary>
    public DateTimeOffset EnrolledAtUtc { get; set; }

    /// <summary>Last activity.</summary>
    public DateTimeOffset? LastSeenAtUtc { get; set; }

    /// <summary>Revocation timestamp.</summary>
    public DateTimeOffset? RevokedAtUtc { get; set; }
}

/// <summary>
/// An append-only log of every identity key statement ever published.
/// </summary>
/// <remarks>
/// Cheap key transparency. Every signed grant records the log head its signer observed, so for
/// the server to show two clients divergent views of a user's keys it must keep both forks
/// consistent across every subsequent shared operation. Any two clients touching the same vault
/// will then surface the mismatch. This converts an otherwise undetectable key-substitution
/// attack into a detectable one; it does not prevent it. See ADR 0001.
/// </remarks>
public sealed class KeyLogEntry
{
    /// <summary>Monotonic sequence. Database-generated.</summary>
    public long Sequence { get; set; }

    /// <summary>The user whose key this is.</summary>
    public Guid UserId { get; set; }

    /// <summary>Generation published.</summary>
    public int Generation { get; set; }

    /// <summary>X25519 public key.</summary>
    public byte[] EncryptionPublicKey { get; set; } = [];

    /// <summary>Ed25519 public key.</summary>
    public byte[] SigningPublicKey { get; set; } = [];

    /// <summary>Ed25519 self-signature over the key statement.</summary>
    public byte[] StatementSignature { get; set; } = [];

    /// <summary>Hash of the preceding entry, forming the chain.</summary>
    public byte[] PreviousHash { get; set; } = [];

    /// <summary>Hash of this entry, over the previous hash and this entry's contents.</summary>
    public byte[] Hash { get; set; } = [];

    /// <summary>Creation timestamp.</summary>
    public DateTimeOffset CreatedAtUtc { get; set; }
}
