import type Database from "better-sqlite3";
import { z } from "zod";
import { OfferSchema } from "./types.js";
import { OFFER_FRESHNESS_MS, type CachedOffers, type Offer, type OfferCache } from "./types.js";

const CachedOffersSchema = z.array(OfferSchema);

/**
 * 15-minute response cache keyed (store, canonical number). Operational only —
 * deliberately not a product catalog (see TECHNICAL-SPEC §3 on legal posture).
 * Shares the app's SQLite handle (see db.ts); owns only its own table.
 */
export class ResponseCache implements OfferCache {
  private readonly ttlMs: number;

  constructor(
    private db: Database.Database,
    opts: { ttlMs?: number } = {},
  ) {
    this.ttlMs = opts.ttlMs ?? OFFER_FRESHNESS_MS;
    this.db.exec(`
      CREATE TABLE IF NOT EXISTS response_cache (
        store      TEXT NOT NULL,
        query      TEXT NOT NULL,
        cached_at  INTEGER NOT NULL,
        offers     TEXT NOT NULL,
        PRIMARY KEY (store, query)
      )
    `);
  }

  get(store: string, canonical: string): Offer[] | null {
    return this.getEntry(store, canonical)?.offers ?? null;
  }

  getEntry(store: string, canonical: string): CachedOffers | null {
    const row = this.db
      .prepare("SELECT cached_at, offers FROM response_cache WHERE store = ? AND query = ?")
      .get(store, canonical) as { cached_at: number; offers: string } | undefined;
    if (!row || Date.now() - row.cached_at > this.ttlMs) return null;

    // Rows written before an Offer schema change are a miss, not a crash — without
    // this, every schema change ships stale-shaped offers to the UI for up to a TTL.
    let parsed: unknown;
    try {
      parsed = JSON.parse(row.offers);
    } catch {
      return null;
    }
    const result = CachedOffersSchema.safeParse(parsed);
    return result.success ? { offers: result.data, observedAt: row.cached_at } : null;
  }

  set(store: string, canonical: string, offers: Offer[], observedAt = Date.now()): void {
    this.db
      .prepare(
        `INSERT INTO response_cache (store, query, cached_at, offers) VALUES (?, ?, ?, ?)
         ON CONFLICT(store, query) DO UPDATE SET cached_at = excluded.cached_at, offers = excluded.offers`,
      )
      .run(store, canonical, observedAt, JSON.stringify(offers));
  }
}
