import { createHash } from "node:crypto";
import { spawnSync } from "node:child_process";
import { copyFileSync, existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from "node:fs";
import { resolve } from "node:path";

import { readWp5RemoteGate, validateWp407FrozenInputs, validateWp5FinalManifest } from "./lib/wp4-07-gate.mjs";

function findFiles(directory, name) {
  if (!existsSync(directory)) return [];
  return readdirSync(directory, { withFileTypes: true }).flatMap((entry) => {
    const path = resolve(directory, entry.name);
    return entry.isDirectory() ? findFiles(path, name) : entry.name === name ? [path] : [];
  });
}

function readBaselineMerges() {
  const log = spawnSync("git", ["log", "--merges", "--format=%H%x09%s", "HEAD"], { encoding: "utf8", timeout: 30_000 });
  if ((log.status ?? 1) !== 0) throw new Error("WP4_07_BASELINE_MERGES_UNREADABLE");
  return log.stdout.trim().split(/\r?\n/).filter(Boolean).map((line) => {
    const [sha, ...subject] = line.split("\t");
    return { sha, subject: subject.join("\t") };
  }).filter((entry) => entry.subject.startsWith("merge: integrate WP5-"));
}

function readGitState() {
  const commitResult = spawnSync("git", ["rev-parse", "HEAD"], { encoding: "utf8", timeout: 30_000 });
  const statusResult = spawnSync("git", ["status", "--porcelain"], { encoding: "utf8", timeout: 30_000 });
  if ((commitResult.status ?? 1) !== 0 || (statusResult.status ?? 1) !== 0) throw new Error("WP4_07_GIT_STATE_UNREADABLE");
  return {
    commit: commitResult.stdout.trim(),
    worktree_under_test: statusResult.stdout.trim() ? "uncommitted implementation" : "clean committed implementation",
  };
}

const layer = process.argv[2];
if (!['visual', 'performance'].includes(layer)) {
  console.error("Usage: node scripts/run-wp4-07-layer.mjs <visual|performance>");
  process.exit(2);
}

try {
  const fixture = validateWp407FrozenInputs();
  const gate = readWp5RemoteGate();
  if (!gate.complete) {
    console.error(JSON.stringify({
      code: "WP4_07_WP5_GATE_INCOMPLETE",
      fixture_sha256: fixture.fixture_sha256,
      layer,
      candidate_baseline_branch: gate.candidate_baseline_branch,
      candidate_baseline_sha: gate.candidate_baseline_sha,
      missing_remote_tasks: gate.missing_tasks,
      observed_remote_heads: gate.heads,
      observed_task_shas: gate.task_shas,
      required_final_branches: gate.required_final_branches,
      status: "red",
    }, null, 2));
    process.exit(1);
  }
  const runId = process.env.DADA_TDD_RUN_ID ?? `wp4-07-${layer}-${new Date().toISOString().replace(/[^0-9]/g, "")}`;
  const runDirectory = resolve("artifacts", "tdd", runId);
  const casesDirectory = resolve(runDirectory, "cases");
  const releaseDirectory = resolve(runDirectory, "real-release");
  mkdirSync(casesDirectory, { recursive: true });
  let environment = {
    ...process.env,
    DADA_PLAYWRIGHT_OUTPUT_DIR: resolve(runDirectory, "playwright-output"),
    DADA_WP4_07_EVIDENCE_DIR: casesDirectory,
    DADA_WP4_07_HARNESS_MODE: "real_archive",
    DADA_WP4_07_LAYER: layer,
    DADA_WP5_03_RUN_DIRECTORY: releaseDirectory,
  };
  const build = spawnSync(process.env.ComSpec ?? "cmd.exe", ["/d", "/s", "/c", "pnpm --filter @dada/shared-contracts build && pnpm --filter @dada/static-sticker-catalog build && pnpm --filter @dada/template-registry build && pnpm --filter @dada/asset-renderer build && pnpm --filter @dada/asset-compiler build"], { encoding: "utf8", env: environment, maxBuffer: 64 * 1024 * 1024 });
  if (build.stdout) process.stdout.write(build.stdout);
  if (build.stderr) process.stderr.write(build.stderr);
  if ((build.status ?? 1) !== 0) process.exit(build.status ?? 1);
  let manifestPath = process.env.DADA_WP4_07_FINAL_ASSET_MANIFEST;
  if (!manifestPath) {
    const compile = spawnSync(process.execPath, ["scripts/compile-wp5-03-assets.mjs"], { encoding: "utf8", env: environment, maxBuffer: 64 * 1024 * 1024 });
    if (compile.stdout) process.stdout.write(compile.stdout);
    if (compile.stderr) process.stderr.write(compile.stderr);
    if ((compile.status ?? 1) !== 0) process.exit(compile.status ?? 1);
    manifestPath = resolve(releaseDirectory, "cases", "TDD-WP5-WHITE-001-p0a-allowlist", "manifest.json");
  }
  const manifest = validateWp5FinalManifest(manifestPath);
  environment = { ...environment, DADA_WP4_07_FINAL_ASSET_MANIFEST: resolve(manifestPath) };
  const playwright = spawnSync(process.env.ComSpec ?? "cmd.exe", ["/d", "/s", "/c", "pnpm exec playwright test --config playwright.wp4-07.config.ts"], { encoding: "utf8", env: environment, maxBuffer: 64 * 1024 * 1024 });
  if (playwright.stdout) process.stdout.write(playwright.stdout);
  if (playwright.stderr) process.stderr.write(playwright.stderr);
  if ((playwright.status ?? 1) !== 0) process.exit(playwright.status ?? 1);
  const caseDirectory = resolve(casesDirectory, layer === "visual" ? "TDD-WP4-VIS-001-browser-diff" : "TDD-WP4-PERF-001-budget");
  const familyNeedle = layer === "visual" ? "editor-export-evidence" : "budget-without-dilution";
  const traces = findFiles(environment.DADA_PLAYWRIGHT_OUTPUT_DIR, "trace.zip");
  for (const browser of ["chrome", "edge"]) {
    const evidenceTrace = resolve(caseDirectory, browser, "trace.zip");
    if (existsSync(evidenceTrace)) continue;
    const trace = traces.find((path) => path.toLowerCase().includes(familyNeedle) && path.toLowerCase().includes(browser));
    if (!trace) throw new Error(`WP4_07_${layer.toUpperCase()}_${browser.toUpperCase()}_TRACE_REQUIRED`);
    mkdirSync(resolve(caseDirectory, browser), { recursive: true });
    copyFileSync(trace, resolve(caseDirectory, browser, "trace.zip"));
  }
  const aggregator = spawnSync(process.execPath, [layer === "visual" ? "scripts/compare-wp4-07-screenshots.mjs" : "scripts/aggregate-wp4-07-performance.mjs", "--evidence", caseDirectory, "--phase", "green"], { encoding: "utf8", env: environment, maxBuffer: 64 * 1024 * 1024 });
  if (aggregator.stdout) process.stdout.write(aggregator.stdout);
  if (aggregator.stderr) process.stderr.write(aggregator.stderr);
  if ((aggregator.status ?? 1) !== 0) process.exit(aggregator.status ?? 1);
  writeFileSync(resolve(caseDirectory, "release-inputs.json"), `${JSON.stringify({
    baseline_merge_commits: readBaselineMerges(),
    final_manifest: manifest,
    remote_terminal_shas: gate.terminal_branch_shas,
    source_task_shas: gate.task_shas,
    status: "passed",
  }, null, 2)}\n`);
  const git = readGitState();
  const manifestSha = createHash("sha256").update(readFileSync("tasks.manifest.json")).digest("hex").toUpperCase();
  const visual = layer === "visual";
  const evidenceRefs = visual
    ? [
      "pixel-diff.json", "layout-boxes.json", "manual-review.json", "release-inputs.json",
      "chrome/asset-sources.json", "chrome/editor.png", "chrome/canvas.png", "chrome/export-dialog.png", "chrome/layout-boxes.json", "chrome/trace.zip",
      "edge/asset-sources.json", "edge/editor.png", "edge/canvas.png", "edge/export-dialog.png", "edge/layout-boxes.json", "edge/trace.zip",
    ]
    : [
      "performance.json", "memory.json", "dom-count.json", "environment.json", "release-inputs.json",
      "chrome/asset-sources.json", "chrome/performance-raw.json", "chrome/trace.zip",
      "edge/asset-sources.json", "edge/performance-raw.json", "edge/trace.zip",
    ];
  const missingEvidence = evidenceRefs.filter((path) => !existsSync(resolve(caseDirectory, path)));
  const manualReviewPassed = !visual
    || JSON.parse(readFileSync(resolve(caseDirectory, "manual-review.json"), "utf8")).status === "passed";
  const status = missingEvidence.length > 0
    ? "failed"
    : manualReviewPassed ? "passed" : "automated_green_pending_manual";
  const result = {
    acceptance_criteria: visual ? ["AC-19", "AC-23", "AC-32"] : ["AC-27", "AC-32"],
    automation: visual ? ["automated", "manual_review"] : ["automated"],
    commit: git.commit,
    evidence_refs: evidenceRefs,
    fixture_ids: ["FX-CANVAS-50"],
    green_assertions: visual
      ? ["Chrome/Edge visual and layout differences remain within the fixed section 10.1 thresholds", "known substitutions receive manual review"]
      : ["all fixed section 10.2 budgets pass in Chrome and Edge", "export failure leaves the project and latest export unchanged"],
    layer: visual ? ["VIS-PERF", "MANUAL"] : ["VIS-PERF"],
    manifest: { path: "tasks.manifest.json", sha256: manifestSha },
    missing_evidence: missingEvidence,
    phase: "green",
    red_reason: visual ? "Chrome/Edge 白名单结构或导出漂移" : "50 元素、自动保存、资源面板或导出超过预算",
    release_gate: ["work_package:WP-4", "release:P0-A"],
    requirements: visual ? ["NFR-02"] : ["NFR-03"],
    run_id: runId,
    status,
    task_id: "TASK-WP4-07",
    test_id: visual ? "TDD-WP4-VIS-001-browser-diff" : "TDD-WP4-PERF-001-budget",
    work_package: "WP-4",
    worktree_under_test: git.worktree_under_test,
  };
  writeFileSync(resolve(caseDirectory, "result.json"), `${JSON.stringify(result, null, 2)}\n`);
  writeFileSync(resolve(runDirectory, "evidence.json"), `${JSON.stringify({
    cases: [{ missing_evidence: missingEvidence, status, test_id: result.test_id }],
    commit: git.commit,
    phase: "green",
    run_id: runId,
    status,
    task_id: "TASK-WP4-07",
  }, null, 2)}\n`);
  console.log(JSON.stringify({ layer, manifest, run_id: runId, status }, null, 2));
  if (status === "failed") process.exit(1);
} catch (error) {
  console.error(JSON.stringify({ code: error instanceof Error ? error.message : String(error), layer, status: "failed" }, null, 2));
  process.exit(1);
}
