/**
 * T0 (v0.8 MVP) — flat account store + manual provisioning. Deliberately minimal: no
 * immutable-record ceremony (that arrives in CP1/T3-T4, which RETIRE this shim). It exists
 * only to gate the café demo. All INV5 guarantees (hashes-at-rest, constant-time compare,
 * raw-secret-returned-once) are honoured and re-shipped properly in CP1.
 *
 * Backing store is injectable. The default is in-memory; production points it at the
 * `db/flat.sqlite.sql` schema (SQLite) — an integration seam, not exercised in unit tests.
 */
import { randomUUID, randomBytes } from 'node:crypto'
import { hashSecret, verifySecret } from './util/hash.js'

export interface FlatAccount {
  readonly accountId: string
  readonly subdomain: string
  readonly agentTokenHash: string
  readonly clientTokenHash: string
}

export interface FlatBackend {
  insert(row: FlatAccount): Promise<void>
  bySubdomain(subdomain: string): Promise<FlatAccount | null>
}

/** In-memory backend (default). SQLite backend is the production swap (see db/flat.sqlite.sql). */
export function inMemoryFlatBackend(): FlatBackend {
  const rows = new Map<string, FlatAccount>()
  return {
    async insert(row) {
      if (rows.has(row.subdomain)) throw new Error(`subdomain already provisioned: ${row.subdomain}`)
      rows.set(row.subdomain, row)
    },
    async bySubdomain(subdomain) {
      return rows.get(subdomain) ?? null
    },
  }
}

export interface FlatStore {
  provisionFlat(subdomain: string): Promise<{ accountId: string; agentToken: string; clientToken: string }>
  lookupBySubdomain(subdomain: string): Promise<FlatAccount | null>
  verifyAgentToken(subdomain: string, raw: string): Promise<boolean>
}

const newToken = (): string => randomBytes(32).toString('base64url')

export function createFlatStore(backend: FlatBackend = inMemoryFlatBackend()): FlatStore {
  return {
    async provisionFlat(subdomain) {
      const agentToken = newToken()
      const clientToken = newToken()
      const row: FlatAccount = {
        accountId: randomUUID(),
        subdomain,
        agentTokenHash: hashSecret(agentToken), // hash at rest — raw discarded (INV5)
        clientTokenHash: hashSecret(clientToken),
      }
      await backend.insert(row)
      // Raw tokens returned exactly ONCE at mint time; never persisted.
      return { accountId: row.accountId, agentToken, clientToken }
    },
    async lookupBySubdomain(subdomain) {
      return backend.bySubdomain(subdomain)
    },
    async verifyAgentToken(subdomain, raw) {
      const row = await backend.bySubdomain(subdomain)
      if (row === null) return false
      return verifySecret(raw, row.agentTokenHash) // constant-time compare of hashes
    },
  }
}
