import { promises as fs } from "node:fs";
import { createHash } from "node:crypto";
import { join } from "node:path";

import {
  parseMarkdownFrontmatter,
  renderFrontmatter,
  strField,
} from "../ledger/frontmatter.ts";
import { resolveLedgerLayout } from "../ledger/paths.ts";
import type { EvidenceSummary } from "./ReviewEvidenceResolver.ts";
import { formatFactCheckJudgmentSummary } from "./factCheckLabels.ts";
import type { FactCheckResult } from "./ReviewFactGate.ts";

export { buildPmAttentionReason, factCheckReasonLabel, formatFactCheckJudgmentSummary } from "./factCheckLabels.ts";

export type WriteFactCheckReviewInput = {
  projectRoot: string;
  taskId: string;
  reportId: string;
  evidence: EvidenceSummary;
  result: FactCheckResult;
  now?: () => Date;
};

export type WriteFactCheckReviewResult = {
  path: string;
  created: boolean;
  review_input_digest: string;
};

function dateKey(d: Date): string {
  const y = d.getUTCFullYear();
  const m = String(d.getUTCMonth() + 1).padStart(2, "0");
  const day = String(d.getUTCDate()).padStart(2, "0");
  return `${y}${m}${day}`;
}

async function nextReviewSeq(reviewsDir: string, date: string): Promise<number> {
  let names: string[] = [];
  try {
    names = await fs.readdir(reviewsDir);
  } catch {
    return 1;
  }
  let max = 0;
  const prefix = `REVIEW-${date}-`;
  for (const name of names) {
    const m = name.match(new RegExp(`^${prefix}(\\d{3})`, "i"));
    if (m) max = Math.max(max, Number(m[1]));
  }
  return max + 1;
}

function decisionFromResult(
  _result: FactCheckResult,
): "approved" | "changes_requested" | "abstained" {
  // REVIEW-GATE is a deterministic rail observation, never a business
  // approval or rejection. PM/ADMIN owns the review decision even when every
  // execution receipt is verified.
  return "abstained";
}

function normalizeReportRef(id: string): string {
  return id.replace(/\.md$/i, "").trim();
}

async function findExistingFactCheckReview(
  reviewsDir: string,
  taskId: string,
  reportId: string,
  reviewInputDigest: string,
): Promise<string | null> {
  const suffix = `-on-${taskId}.md`;
  const wantReport = normalizeReportRef(reportId);
  let names: string[] = [];
  try {
    names = await fs.readdir(reviewsDir);
  } catch {
    return null;
  }
  for (const name of names) {
    if (!name.endsWith(suffix) || !name.startsWith("REVIEW-")) continue;
    const path = join(reviewsDir, name);
    const raw = await fs.readFile(path, "utf-8").catch(() => "");
    const fm = parseMarkdownFrontmatter(raw) as Record<string, unknown>;
    const rid = normalizeReportRef(
      strField(fm, "report_id") || strField(fm, "subject_id"),
    );
    if (
      rid &&
      rid === wantReport &&
      strField(fm, "review_input_digest") === reviewInputDigest
    ) return path;
  }
  return null;
}

/**
 * Write REVIEW-GATE fact-check artefact under fcop/reviews/.
 */
export async function writeFactCheckReview(
  input: WriteFactCheckReviewInput,
): Promise<WriteFactCheckReviewResult> {
  const now = input.now?.() ?? new Date();
  const layout = resolveLedgerLayout(input.projectRoot);
  await fs.mkdir(layout.reviewsDir, { recursive: true });

  const reviewInputDigest = `sha256:${createHash("sha256").update(JSON.stringify({
    task_id: input.taskId,
    report_id: input.reportId,
    result: input.result,
    evidence: input.evidence,
  })).digest("hex")}`;

  const existing = await findExistingFactCheckReview(
    layout.reviewsDir,
    input.taskId,
    input.reportId,
    reviewInputDigest,
  );
  if (existing) {
    return {
      path: existing,
      created: false,
      review_input_digest: reviewInputDigest,
    };
  }

  const dk = dateKey(now);
  const seq = await nextReviewSeq(layout.reviewsDir, dk);
  const seqStr = String(seq).padStart(3, "0");
  const reviewId = `REVIEW-${dk}-${seqStr}-REVIEW-GATE`;
  const filename = `${reviewId}-on-${input.taskId}.md`;
  const path = join(layout.reviewsDir, filename);

  const decision = decisionFromResult(input.result);
  const reviewedAt = now.toISOString();
  const observationOnly = true;

  const bodyLines = [
    renderFrontmatter({
      protocol: "fcop",
      version: 1,
      kind: "fact_check",
      review_id: reviewId,
      subject_id: input.reportId,
      task_id: input.taskId,
      report_id: input.reportId,
      reviewer: "REVIEW-GATE",
      decision,
      reviewed_at: reviewedAt,
      fact_check_verdict: input.result.verdict,
      review_state: input.result.review_state ??
        (input.result.verdict === "pass" ? "pass" : input.result.verdict === "fail" ? "deterministic_fail" : "needs_pm"),
      reason_code: input.result.reason_code,
      observation_only: observationOnly,
      business_decision: false,
      suggestion_only: false,
      awaiting_pm_decision: true,
      execution_evidence_state: input.result.execution_evidence_state ?? "unavailable",
      third_party_source_state: input.result.third_party_source_state ?? "not_configured",
      reported_outcome: input.result.reported_outcome ?? "unknown",
      pm_decision: input.result.pm_decision ?? "pending",
      action_receipt_count: input.result.action_receipt_count ?? 0,
      fact_source_ids: input.result.fact_source_ids ?? [],
      fact_standard_ids: input.result.fact_standard_ids ?? [],
      evidence_conflicts: input.result.conflicts ?? [],
      review_input_digest: reviewInputDigest,
      acceptance_contract_digest: input.result.acceptance_contract_digest,
      evidence_digest: input.result.evidence_digest,
      failed_item_ids: input.result.failed_item_ids ?? [],
    }),
    "",
    "# Fact Check Record / 事实核查记录",
    "",
    `Task: ${input.taskId}`,
    `Report: ${input.reportId}`,
    `Execution evidence: **${input.result.execution_evidence_state ?? "unavailable"}**`,
    `Third-party source: **${input.result.third_party_source_state ?? "not_configured"}**`,
    `Reported outcome: **${input.result.reported_outcome ?? "unknown"}**`,
    `PM decision: **${input.result.pm_decision ?? "pending"}**`,
    `Compatibility verdict: **${input.result.verdict}**`,
    `Reason: \`${input.result.reason_code}\``,
    "",
    "> 本文件是程序生成的确定性证据记录，不是审批、裁决或返工指令。最终业务判断由 PM/ADMIN 作出。",
    "",
    "## 判定说明 / Judgment",
    "",
    formatFactCheckJudgmentSummary(input.result),
    "",
    "## Evidence Summary",
    "",
    `- Session found: ${input.evidence.session.found}`,
    `- Files read: ${input.evidence.files.read.length}`,
    `- Files changed: ${input.evidence.files.changed.length}`,
    `- Commands: ${input.evidence.commands.length}`,
    `- Data queries: ${input.evidence.data_queries.length}`,
    `- Cited evidence files: ${(input.evidence.cited_evidence_files ?? []).length}`,
    "",
  ];

  if (input.result.unsupported_claims.length > 0) {
    bodyLines.push("## Unsupported Claims", "");
    for (const c of input.result.unsupported_claims) {
      bodyLines.push(`- ${c}`);
    }
    bodyLines.push("");
  }

  if (input.result.required_changes.length > 0) {
    bodyLines.push("## Required Changes", "");
    for (const c of input.result.required_changes) {
      bodyLines.push(`- ${c}`);
    }
    bodyLines.push("");
  }

  if (input.evidence.warnings.length > 0) {
    bodyLines.push("## Evidence Warnings", "");
    for (const w of input.evidence.warnings) {
      bodyLines.push(`- ${w}`);
    }
    bodyLines.push("");
  }

  await fs.writeFile(path, bodyLines.join("\n"), "utf-8");
  return {
    path,
    created: true,
    review_input_digest: reviewInputDigest,
  };
}
