/**
 * Repository ports (Repository Pattern, patterns.md). Registries/services depend on these
 * abstractions, NOT on a concrete storage engine. Two adapters implement them:
 *   - `store/memory.ts` — in-memory, the tested + v0.9-MVP-shipped default. Immutable/versioned
 *     discipline (INV8) is enforced here in synchronous critical sections (JS is single-threaded,
 *     so a mutation with no `await` inside is atomic — no torn read).
 *   - `store/pg.ts` — parameterized Postgres SQL over `db/pool.ts` (integration seam, typechecked).
 *
 * The INV8 versioning logic lives in the store so both adapters agree on the contract; the
 * registries orchestrate and add authz/ownership predicates.
 */
import type {
  AccountRecord,
  AccountStatus,
  AccountStatusVersionRow,
  HostRecord,
  HostStatus,
  HostStatusVersionRow,
  MeteringSampleRow,
  PairingCodeRecord,
  RouteEntry,
  SessionRecord,
} from '../model/records.js'
// A4 add-only (FIX C-native-1): the device data-path identity records own their types in the
// registry module (model/records.ts is frozen for this task); import them type-only here.
import type { DeviceRecord, DeviceStatus, DeviceStatusVersionRow } from '../registry/devices.js'

export interface AccountStore {
  insert(rec: AccountRecord): Promise<void>
  get(accountId: string): Promise<AccountRecord | null>
  /** Atomic: append a status version then swap the single-row pointer. Returns the NEW record. */
  swapStatus(accountId: string, status: AccountStatus, changedBy: string): Promise<AccountRecord>
  versions(accountId: string): Promise<readonly AccountStatusVersionRow[]>
}

export interface HostStore {
  insert(rec: HostRecord): Promise<void>
  get(hostId: string): Promise<HostRecord | null>
  getBySubdomain(subdomain: string): Promise<HostRecord | null>
  listByAccount(accountId: string): Promise<readonly HostRecord[]>
  /** Atomic status version + pointer swap. `revokedAt` set only on 'revoked'. Returns NEW record. */
  swapStatus(
    hostId: string,
    status: HostStatus,
    revokedAt: string | null,
    changedBy: string,
  ): Promise<HostRecord>
  /** Advisory liveness cache update — NOT versioned (INV7; live truth is Redis). */
  touchLastSeen(hostId: string, ts: string): Promise<void>
  versions(hostId: string): Promise<readonly HostStatusVersionRow[]>
}

export interface SessionStore {
  insert(rec: SessionRecord): Promise<void>
  get(sessionId: string): Promise<SessionRecord | null>
}

/**
 * A4 (add-only) — device data-path identity store (FIX C-native-1). Mirrors `HostStore`: versioned
 * status snapshots (INV8) + an ACTIVE-count read for the per-account device cap. The in-memory
 * adapter lives in `registry/devices.ts` (`createMemoryDeviceStore`), not `store/memory.ts`, so the
 * shared `Stores` wiring is untouched.
 */
export interface DeviceStore {
  insert(rec: DeviceRecord): Promise<void>
  get(deviceId: string): Promise<DeviceRecord | null>
  listByAccount(accountId: string): Promise<readonly DeviceRecord[]>
  /** Count of ACTIVE (non-revoked) devices for the per-account cap. */
  countActiveByAccount(accountId: string): Promise<number>
  /** Atomic status version + pointer swap. `revokedAt` set only on 'revoked'. Returns NEW record. */
  swapStatus(
    deviceId: string,
    status: DeviceStatus,
    revokedAt: string | null,
    changedBy: string,
  ): Promise<DeviceRecord>
  /**
   * Persist a fresh leaf expiry on RENEWAL (mirrors the host `now()+ttl` fresh-expiry pattern).
   * Atomic single-row pointer update — the record is the CRL/expiry pairing source of truth, so it
   * must track the emitted cert. NOT status-versioned (INV8 covers status, not expiry). Returns the
   * NEW record. Throws if `deviceId` is unknown.
   */
  renewLeafExpiry(deviceId: string, notAfter: string): Promise<DeviceRecord>
  versions(deviceId: string): Promise<readonly DeviceStatusVersionRow[]>
}

/** Reservation of a subdomain label; atomic single-winner under concurrency (INV1). */
export interface SubdomainStore {
  reserve(subdomain: string): Promise<boolean> // false ⇒ already taken
  isTaken(subdomain: string): Promise<boolean>
}

export interface PairingRow {
  readonly record: PairingCodeRecord
  readonly redeemAttempts: number
}

export type RedeemOutcome =
  | 'ok'
  | 'already_redeemed'
  | 'expired'
  | 'unknown'
  | 'too_many_attempts'
  | 'bad_csr'

export type CasOutcome = 'ok' | 'already_redeemed' | 'unknown'

export interface PairingStore {
  insert(rec: PairingCodeRecord): Promise<void>
  get(codeHash: string): Promise<PairingRow | null>
  /**
   * Atomic compare-and-set of `redeemedAt` from null (double-spend guard). Exactly one
   * concurrent caller wins 'ok'; the loser gets 'already_redeemed'. Lockout/expiry are
   * pre-checked by the registry from `get()`.
   */
  casRedeem(codeHash: string, nowIso: string): Promise<CasOutcome>
  /** Atomic failed-attempt increment for a known code_hash; returns the new attempt count. */
  registerFailure(codeHash: string): Promise<number>
}

/** Redis-like routing table with per-key heartbeat TTL (INV7). */
export interface RouteStore {
  set(hostId: string, entry: RouteEntry, ttlSec: number): Promise<void>
  refreshTtl(hostId: string, ttlSec: number): Promise<boolean> // false if key already expired/absent
  get(hostId: string): Promise<RouteEntry | null>
  delete(hostId: string): Promise<void>
  /** Live routes whose entry targets `nodeId` (used by drain enumeration). */
  listByNode(nodeId: string): Promise<readonly { hostId: string; entry: RouteEntry }[]>
}

export type NodeStatus = 'active' | 'draining'
export interface NodeRow {
  readonly nodeId: string
  readonly addr: string
  readonly status: NodeStatus
  readonly lastSeen: string
}
export interface NodeStore {
  upsert(row: NodeRow): Promise<void>
  get(nodeId: string): Promise<NodeRow | null>
  setStatus(nodeId: string, status: NodeStatus): Promise<void>
  touch(nodeId: string, ts: string): Promise<void>
  delete(nodeId: string): Promise<void>
}

export interface MeteringStore {
  append(row: MeteringSampleRow): Promise<void> // append-only (INV8)
  query(accountId: string, fromIso: string, toIso: string): Promise<readonly MeteringSampleRow[]>
}

export interface AuditRow {
  readonly action: string
  readonly principalId: string
  readonly accountId: string
  readonly hostId: string | null
  readonly ts: string
  readonly meta: Readonly<Record<string, string>>
}
export interface AuditStore {
  append(row: AuditRow): Promise<void> // append-only, no update/delete path (INV10)
  query(accountId: string, fromIso: string, toIso: string): Promise<readonly AuditRow[]>
}

export interface Stores {
  readonly accounts: AccountStore
  readonly hosts: HostStore
  readonly sessions: SessionStore
  readonly subdomains: SubdomainStore
  readonly pairing: PairingStore
  readonly routes: RouteStore
  readonly nodes: NodeStore
  readonly metering: MeteringStore
  readonly audit: AuditStore
}
