namespace DodoSSH.Domain;

/// <summary>What a change did to an entity.</summary>
public enum ChangeOperation
{
    /// <summary>Not a legal value.</summary>
    Unspecified = 0,

    /// <summary>Created or modified.</summary>
    Upsert = 1,

    /// <summary>Soft-deleted, leaving a tombstone.</summary>
    Delete = 2,
}

/// <summary>The kind of item a change refers to. Mirrors the contract enum; append only.</summary>
public enum ChangeEntityType
{
    /// <summary>Not a legal value.</summary>
    Unspecified = 0,

    /// <summary>An SSH host.</summary>
    SshHost = 1,

    /// <summary>A credential. M2.</summary>
    Credential = 2,

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

    /// <summary>A host group. M2.</summary>
    HostGroup = 4,

    /// <summary>A tag. M2.</summary>
    Tag = 5,

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

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

    /// <summary>A snippet. M2.</summary>
    Snippet = 8,

    /// <summary>A port forward. M2.</summary>
    PortForward = 9,

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

    /// <summary>One connection that was made, and how long it lasted. M2.</summary>
    ConnectionLogEntry = 11,

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

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

/// <summary>
/// One entry in a vault's change log, which is what delta sync reads.
/// </summary>
/// <remarks>
/// <para>
/// <see cref="Sequence"/> comes from a <c>bigserial</c>, and that carries a trap worth stating
/// where the code lives: sequence values are handed out <em>before</em> commit. If transaction A
/// takes 5 and B takes 6 but B commits first, a reader that advances its cursor to 6 permanently
/// misses 5 — silent sync corruption that only appears under concurrent writes to one vault.
/// </para>
/// <para>
/// Every push therefore takes a per-vault transaction-scoped advisory lock as its first
/// statement, so sequence order equals commit order. See ADR 0003.
/// </para>
/// </remarks>
public sealed class VaultChange
{
    /// <summary>Monotonic sequence. Database-generated.</summary>
    public long Sequence { get; set; }

    /// <summary>Owning vault. Cursors are scoped per vault.</summary>
    public Guid VaultId { get; set; }

    /// <summary>Kind of item.</summary>
    public ChangeEntityType EntityType { get; set; }

    /// <summary>The item.</summary>
    public Guid EntityId { get; set; }

    /// <summary>What happened.</summary>
    public ChangeOperation Operation { get; set; }

    /// <summary>The item's version after the change.</summary>
    public int Revision { get; set; }

    /// <summary>Who made the change.</summary>
    public Guid ActorUserId { get; set; }

    /// <summary>When it was recorded.</summary>
    public DateTimeOffset OccurredAtUtc { get; set; }
}

/// <summary>
/// Records that a client operation was applied, so a retry is a no-op.
/// </summary>
/// <remarks>
/// Keyed on the client-generated operation id, which makes retries exactly-once at
/// <em>operation</em> granularity. A batch-level idempotency key alone would not: a client that
/// times out mid-push and retries with a partially overlapping batch would otherwise double-apply
/// the operations that did land.
/// </remarks>
public sealed class SyncOperationReceipt
{
    /// <summary>The client-generated operation id. Primary key.</summary>
    public Guid OperationId { get; set; }

    /// <summary>Vault the operation targeted.</summary>
    public Guid VaultId { get; set; }

    /// <summary>Change-log sequence produced, when the operation was applied.</summary>
    public long? AppliedChangeSequence { get; set; }

    /// <summary>The item's version after the operation.</summary>
    public int? ResultVersion { get; set; }

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