/**
 * InboxStore — server-side storage interface for the async store-and-forward
 * relay (V3.6).
 *
 * The relay stores ciphertext blobs only. It never sees plaintext, never
 * holds private keys, and never decrypts anything. The address-owner table
 * binds an address to a recipient signing key (Ed25519) so GET/DELETE
 * authentication can verify that the caller actually owns the address.
 *
 * Per-blob row layout (mandated by V3.6 spec):
 *   address || msgId || ciphertext-bytes || expires_at
 *
 * `received_at` is also stored per blob to support cursor-based GET. The
 * cursor is opaque to the caller — it is just the highest `received_at`
 * the client has seen, encoded as a string.
 */
export interface InboxStore {
  // ─── Address ownership (TOFU on first register) ───────────

  /**
   * Register or update the signing key that owns `address`. First call
   * "claims" the address (TOFU). Subsequent calls with the same key are
   * no-ops. A different key trying to claim the same address is rejected
   * by the route layer (the store itself just upserts).
   */
  saveAddressOwner(address: string, signingKey: Uint8Array): Promise<void>;

  /** Look up the owner signing key for `address`, or null if unregistered. */
  getAddressOwner(address: string): Promise<Uint8Array | null>;

  // ─── Blob CRUD ───────────────────────────────────────────

  /**
   * Store an inbox blob.
   *
   * **Idempotent**: if a row already exists for `(address, msgId)` the
   * implementation MUST return `{ created: false }` and leave the existing
   * row untouched. A fresh insert returns `{ created: true, receivedAt }`.
   *
   * `senderFp` (V4.8+) is the 8-byte hex of SHA-256(senderSigningKey)
   * — captured at PUT time when the relay verified the sender's
   * signature. Optional so legacy 4.7 callers compile, but populated by
   * `createInboxRoutes` from 4.8 onward and surfaced on bridge push +
   * inbox-fetch responses to bootstrap unknown-sender first-contact
   * (X3DH pair handshake).
   */
  putBlob(args: {
    address: string;
    msgId: string;
    ciphertext: Uint8Array;
    expiresAt: number;
    senderFp?: string;
  }): Promise<{ created: boolean; receivedAt: number }>;

  /**
   * Fetch all non-expired blobs for `address` whose `received_at > sinceCursor`,
   * ordered ascending by `received_at`. The caller passes 0 (or `null`-equiv
   * "")  to fetch from the beginning.
   *
   * Implementations must filter out expired rows so a slow consumer never
   * sees a payload past TTL. Pruning of expired rows happens out-of-band.
   */
  fetchBlobs(args: {
    address: string;
    sinceCursor: number;
    now: number;
    limit: number;
  }): Promise<
    Array<{
      msgId: string;
      ciphertext: Uint8Array;
      receivedAt: number;
      expiresAt: number;
      /**
       * Sender fingerprint — 8-byte hex of SHA-256(senderSigningKey)
       * captured at PUT time. Empty/undefined for blobs persisted by a
       * pre-4.8 relay that didn't track sender provenance.
       */
      senderFp?: string;
    }>
  >;

  /**
   * Delete a single blob by `(address, msgId)`. Returns true if a row was
   * removed. Used by clients to ack a fetched message for early prune.
   */
  deleteBlob(address: string, msgId: string): Promise<boolean>;

  /**
   * Count non-expired blobs for `address`. Used by quota enforcement and
   * the inbox-fanout fingerprint gate.
   */
  countBlobs(address: string, now: number): Promise<number>;

  // ─── Maintenance ─────────────────────────────────────────

  /**
   * Purge every blob whose `expires_at <= now`. Returns count removed.
   * Called periodically by a cron task.
   */
  purgeExpired(now: number): Promise<number>;

  /**
   * Drop the address owner record and any remaining blobs for `address`.
   * Used by the unregister route.
   */
  deleteAddress(address: string): Promise<void>;
}
