// src/core/oracle/goldenReceipts.ts
// Golden test infrastructure for recording and replaying real LLM responses
//
// Usage:
//   1. Run tests with RUN_LIVE_TESTS=true to record golden data
//   2. Run tests normally to replay from recorded golden data
//   3. Regenerate golden data by deleting fixtures and running live again

import * as fs from "fs";
import * as path from "path";
import type { Hash } from "../../../../src/core/artifacts/hash";
import { sha256JSON } from "../../../../src/core/artifacts/hash";
import type { OracleReq, OracleResp } from "./protocol";
import type { ReceiptStore, OracleReceipt, ReceiptMode } from "./receipts";

export type GoldenMode = "off" | "record" | "replay" | "auto";

export interface GoldenReceiptConfig {
  /** Path to fixtures directory */
  fixturesDir: string;
  /** Mode: "record" saves new, "replay" uses cached, "auto" replays if exists else records */
  mode: GoldenMode;
  /** Test name for organizing fixtures */
  testName?: string;
}

/**
 * GoldenReceiptStore - File-based receipt store for golden tests
 *
 * Stores LLM request/response pairs in JSON files organized by test name.
 * Enables deterministic testing with real LLM responses.
 */
export class GoldenReceiptStore implements ReceiptStore {
  mode: ReceiptMode;
  private config: GoldenReceiptConfig;
  private cache = new Map<Hash, OracleReceipt>();
  private dirty = false;

  constructor(config: GoldenReceiptConfig) {
    this.config = config;
    this.mode = config.mode === "auto" ? "replay" : config.mode;

    // Ensure fixtures directory exists
    if (!fs.existsSync(config.fixturesDir)) {
      fs.mkdirSync(config.fixturesDir, { recursive: true });
    }

    // Load existing golden data
    this.loadFromFile();
  }

  private getFilePath(): string {
    const name = this.config.testName ?? "default";
    const safeName = name.replace(/[^a-zA-Z0-9-_]/g, "_");
    return path.join(this.config.fixturesDir, `${safeName}.golden.json`);
  }

  private loadFromFile(): void {
    const filePath = this.getFilePath();

    if (fs.existsSync(filePath)) {
      try {
        const content = fs.readFileSync(filePath, "utf8");
        const data = JSON.parse(content) as GoldenFileFormat;

        for (const receipt of data.receipts) {
          this.cache.set(receipt.key, receipt);
        }

        // In auto mode, if we have data, use replay; otherwise record
        if (this.config.mode === "auto" && this.cache.size > 0) {
          this.mode = "replay";
        }
      } catch {
        // Invalid file, start fresh
        this.cache.clear();
        if (this.config.mode === "auto") {
          this.mode = "record";
        }
      }
    } else if (this.config.mode === "auto") {
      // No file exists, switch to record mode
      this.mode = "record";
    }
  }

  private saveToFile(): void {
    if (!this.dirty) return;

    const filePath = this.getFilePath();
    const data: GoldenFileFormat = {
      version: 1,
      testName: this.config.testName ?? "default",
      createdAt: new Date().toISOString(),
      receipts: Array.from(this.cache.values()),
    };

    fs.writeFileSync(filePath, JSON.stringify(data, null, 2), "utf8");
    this.dirty = false;
  }

  get(key: Hash): OracleReceipt | undefined {
    return this.cache.get(key);
  }

  put(r: OracleReceipt): void {
    if (this.mode === "record") {
      this.cache.set(r.key, r);
      this.dirty = true;
    }
  }

  /** Check if we have a cached response for this request */
  has(key: Hash): boolean {
    return this.cache.has(key);
  }

  /** Get number of cached receipts */
  size(): number {
    return this.cache.size;
  }

  /** Flush any pending writes to disk */
  flush(): void {
    this.saveToFile();
  }

  /** Clear cache and optionally delete file */
  clear(deleteFile = false): void {
    this.cache.clear();
    this.dirty = false;

    if (deleteFile) {
      const filePath = this.getFilePath();
      if (fs.existsSync(filePath)) {
        fs.unlinkSync(filePath);
      }
    }
  }

  /** Switch mode (useful for hybrid record/replay) */
  setMode(mode: ReceiptMode): void {
    this.mode = mode;
  }

  /** Get all receipts (for debugging) */
  all(): OracleReceipt[] {
    return Array.from(this.cache.values());
  }
}

/** Format for golden test fixture files */
interface GoldenFileFormat {
  version: number;
  testName: string;
  createdAt: string;
  receipts: OracleReceipt[];
}

/**
 * Create a receipt key that's stable across runs
 * Normalizes request to remove runtime-specific data
 */
export function goldenReceiptKey(req: OracleReq): Hash {
  // Normalize request by removing fields that change between runs
  const normalized = normalizeRequest(req);
  return sha256JSON(normalized);
}

function normalizeRequest(req: OracleReq): any {
  switch (req.tag) {
    case "ReqEval":
      // Normalize envRef to a stable placeholder
      return { tag: req.tag, qexpr: req.qexpr, envRef: "<env>" };
    case "ReqApply":
      return { tag: req.tag, fn: req.fn, args: req.args, envRef: "<env>" };
    case "ReqObserve":
      return { tag: req.tag, what: req.what, stateRef: "<state>" };
    case "ReqSnapshot":
      return { tag: req.tag, envRef: "<env>", stateRef: "<state>" };
    case "ReqHydrate":
      return { tag: req.tag, receiptId: "<receipt>" };
    default:
      return req;
  }
}

/**
 * Helper: Create golden store for a specific test
 */
export function createGoldenStore(
  testName: string,
  opts?: { mode?: GoldenMode; fixturesDir?: string }
): GoldenReceiptStore {
  const fixturesDir = opts?.fixturesDir ?? path.resolve(__dirname, "../../../test/fixtures/golden");
  const mode = opts?.mode ?? (process.env.RECORD_GOLDEN === "true" ? "record" : "auto");

  return new GoldenReceiptStore({
    fixturesDir,
    mode,
    testName,
  });
}
