#!/usr/bin/env tsx
/**
 * 阶段0 Network-First 录证（独立脚本，不含业务状态机）
 *
 * 用法:
 *   npm run record:phase0
 *   npx tsx scripts/phase0/record-baseline.ts   （推荐：stdin 更可靠）
 *
 * 非 TTY / Enter 无效时:
 *   set PHASE0_USE_FILE_TRIGGER=1
 *   检查点 A: New-Item -Force .rpa\phase0\trigger-checkpoint-a
 *   检查点 B: New-Item -Force .rpa\phase0\trigger-checkpoint-b
 */
import "dotenv/config";
import fs from "node:fs";
import path from "node:path";
import { chromium, type BrowserContext, type Page } from "playwright";
import {
  createSharedReadline,
  TRIGGER_CHECKPOINT_A,
  TRIGGER_CHECKPOINT_B,
  waitForCheckpoint,
  writeRecordingStatus,
} from "./lib/checkpoint";
import { resolveBrowserChannel, resolveEntryUrl } from "./lib/env";
import { pickBestQuoteEntry, readHarFile } from "./lib/har-parse";
import {
  BASELINE_HAR,
  PHASE0_DIR,
  QUOTE_CONTRACT_SCHEMA,
  SESSION_STORAGE_SNAPSHOT,
} from "./lib/paths";
import { buildQuoteContractSchema } from "./lib/schema-infer";

const PROMPT_ADDRESS_LOCKED =
  "提货+派送地址均已点选联想并锁定。确认后触发检查点 A（Enter 或 trigger 文件）。";
const PROMPT_QUOTE_DONE =
  "货物已填、已提交、四档报价可见。确认后触发检查点 B（Enter 或 trigger 文件）。";

async function captureStorageSnapshot(
  page: Page,
  context: BrowserContext,
): Promise<Record<string, unknown>> {
  const storageState = await context.storageState();
  const pageUrl = page.url();

  const sessionStorageByOrigin = await page.evaluate(() => {
    const result: Record<string, Record<string, string>> = {};
    try {
      const entries: Record<string, string> = {};
      for (let i = 0; i < sessionStorage.length; i += 1) {
        const key = sessionStorage.key(i);
        if (key) {
          entries[key] = sessionStorage.getItem(key) ?? "";
        }
      }
      result[window.location.origin] = entries;
    } catch {
      /* ignore */
    }
    return result;
  });

  const documentCookies = await page
    .evaluate(() => document.cookie)
    .catch(() => "");

  return {
    capturedAt: new Date().toISOString(),
    checkpoint: "address_locked",
    pageUrl,
    storageState,
    sessionStorageByOrigin,
    documentCookies,
  };
}

function writeSnapshotFile(snapshot: Record<string, unknown>): string {
  const absPath = path.resolve(SESSION_STORAGE_SNAPSHOT);
  fs.mkdirSync(path.dirname(absPath), { recursive: true });
  fs.writeFileSync(absPath, `${JSON.stringify(snapshot, null, 2)}\n`, "utf8");
  if (!fs.existsSync(absPath)) {
    throw new Error(`写入后文件仍不存在: ${absPath}`);
  }
  const stat = fs.statSync(absPath);
  if (stat.size < 10) {
    throw new Error(`写入文件过小 (${stat.size} bytes): ${absPath}`);
  }
  return absPath;
}

function extractSchemaFromHar(harPath: string): void {
  const har = readHarFile(harPath);
  const entries = har.log?.entries ?? [];
  const picked = pickBestQuoteEntry(entries);

  if (!picked) {
    console.warn(
      "[phase0] 警告: HAR 中未识别到 quote JSON 响应，quote-contract.schema.json 将写入占位说明。",
    );
    fs.writeFileSync(
      QUOTE_CONTRACT_SCHEMA,
      `${JSON.stringify(
        {
          $schema: "https://json-schema.org/draft/2020-12/schema",
          title: "MotherShip Quote Response Contract (placeholder)",
          description:
            "录证未完成或 HAR 无 quote 响应。请重新执行录证并确保提交后捕获到 axel/quote 等接口。",
          generatedAt: new Date().toISOString(),
          body: { type: "object" },
          incomplete: true,
        },
        null,
        2,
      )}\n`,
      "utf8",
    );
    return;
  }

  const doc = buildQuoteContractSchema({
    body: picked.body,
    harEntry: {
      url: picked.entry.request.url,
      method: picked.entry.request.method,
      status: picked.entry.response.status,
    },
  });

  fs.writeFileSync(QUOTE_CONTRACT_SCHEMA, `${JSON.stringify(doc, null, 2)}\n`, "utf8");
  console.log(
    `[phase0] quote-contract.schema.json ← ${picked.entry.request.method} ${picked.entry.request.url}`,
  );
}

function printBanner(entryUrl: string): void {
  const absOut = path.resolve(PHASE0_DIR);
  const tty = process.stdin.isTTY ? "是" : "否";
  const mode =
    process.env.PHASE0_USE_FILE_TRIGGER === "1" || !process.stdin.isTTY
      ? "文件触发"
      : "Enter 键";

  console.log(`
╔══════════════════════════════════════════════════════════════╗
║  阶段0 Network-First 录证（headed 人工操作）                  ║
╚══════════════════════════════════════════════════════════════╝

入口 URL: ${entryUrl}
产出目录: ${absOut}
stdin TTY: ${tty} → 检查点模式: ${mode}

操作流程:
  1. 浏览器打开后，完成提货/派送地址点选锁定
  2. 检查点 A → 写入 session-storage-snapshot.json
  3. 填货并提交，等待四档报价
  4. 检查点 B → 写入 baseline.har + quote-contract.schema.json

Enter 无效时请用:
  $env:PHASE0_USE_FILE_TRIGGER="1"; npx tsx scripts/phase0/record-baseline.ts
  检查点 A: New-Item -ItemType File -Force -Path ".rpa\\phase0\\trigger-checkpoint-a"
`);
}

async function main(): Promise<void> {
  const entryUrl = resolveEntryUrl();
  fs.mkdirSync(PHASE0_DIR, { recursive: true });

  writeRecordingStatus({
    phase: "started",
    entryUrl,
    cwd: process.cwd(),
    stdinIsTTY: process.stdin.isTTY,
    snapshotTarget: path.resolve(SESSION_STORAGE_SNAPSHOT),
  });

  printBanner(entryUrl);

  const channel = resolveBrowserChannel();
  const browser = await chromium.launch({
    headless: false,
    channel: channel as "chrome" | "msedge" | undefined,
    slowMo: Number(process.env.RPA_SLOW_MO_MS ?? 0) || undefined,
  });

  const context = await browser.newContext({
    locale: process.env.RPA_BROWSER_LOCALE?.trim() || undefined,
    recordHar: {
      path: BASELINE_HAR,
      mode: "full",
      content: "embed",
    },
  });

  const page = await context.newPage();
  page.setDefaultTimeout(120_000);

  console.log(`[phase0] 导航至 ${entryUrl}`);
  await page.goto(entryUrl, { waitUntil: "domcontentloaded", timeout: 60_000 });

  writeRecordingStatus({ phase: "waiting_checkpoint_a", pageUrl: page.url() });

  const rl =
    process.stdin.isTTY && process.env.PHASE0_USE_FILE_TRIGGER !== "1"
      ? createSharedReadline()
      : null;

  await waitForCheckpoint({
    label: "检查点 A",
    message: PROMPT_ADDRESS_LOCKED,
    triggerPath: TRIGGER_CHECKPOINT_A,
    rl,
  });

  console.log("[phase0] 正在捕获存储快照…");
  let snapshotPath: string;
  try {
    const snapshot = await captureStorageSnapshot(page, context);
    snapshotPath = writeSnapshotFile(snapshot);
  } catch (error) {
    writeRecordingStatus({
      phase: "checkpoint_a_failed",
      error: error instanceof Error ? error.message : String(error),
    });
    throw error;
  }

  writeRecordingStatus({
    phase: "checkpoint_a_done",
    snapshotPath,
    snapshotBytes: fs.statSync(snapshotPath).size,
  });

  console.log(`[phase0] ✓ 已写入 ${snapshotPath}`);
  console.log(`[phase0]   大小 ${fs.statSync(snapshotPath).size} bytes`);

  writeRecordingStatus({ phase: "waiting_checkpoint_b", pageUrl: page.url() });

  await waitForCheckpoint({
    label: "检查点 B",
    message: PROMPT_QUOTE_DONE,
    triggerPath: TRIGGER_CHECKPOINT_B,
    rl,
  });

  if (rl) {
    rl.close();
  }

  await context.close();
  await browser.close();

  if (!fs.existsSync(BASELINE_HAR)) {
    throw new Error(`HAR 未生成: ${path.resolve(BASELINE_HAR)}`);
  }

  extractSchemaFromHar(BASELINE_HAR);

  writeRecordingStatus({
    phase: "completed",
    harPath: path.resolve(BASELINE_HAR),
    schemaPath: path.resolve(QUOTE_CONTRACT_SCHEMA),
  });

  console.log(`
[phase0] 录证完成。请执行验收:
  npm run verify:phase0
`);
}

main().catch((error) => {
  writeRecordingStatus({
    phase: "failed",
    error: error instanceof Error ? error.message : String(error),
  });
  console.error("[phase0] 录证失败:", error instanceof Error ? error.message : error);
  process.exit(1);
});
