/**
 * Formal correction for mistaken auto REVIEW-GATE decisions.
 * Marks REVIEW overruled/obsolete and atomically restores REPORT validity.
 * Append-only audit — never deletes historical REVIEW/REPORT bodies.
 */

import { promises as fs } from "node:fs";
import { basename, join } from "node:path";

import {
  parseMarkdownFrontmatter,
  renderFrontmatter,
  strField,
} from "../ledger/frontmatter.ts";
import { bodyAfterFrontmatter } from "../ledger/leaderLedgerContextPack.ts";
import { LedgerBuilder } from "../ledger/LedgerBuilder.ts";
import { resolveLedgerLayout } from "../ledger/paths.ts";
import { findTaskLocationById } from "../lifecycle/taskPathUtils.ts";
import { TaskFrontmatterStore } from "../lifecycle/TaskFrontmatterStore.ts";

export type OverruleReviewGateInput = {
  projectRoot: string;
  reviewFilename: string;
  actor: string;
  reason?: string;
  now?: () => Date;
};

export type OverruleReviewGateResult =
  | {
      ok: true;
      review_path: string;
      report_id?: string;
      task_id?: string;
      report_restored: boolean;
      task_cleared: boolean;
    }
  | { ok: false; code: string; error: string };

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

async function findReportPath(
  projectRoot: string,
  reportId: string,
): Promise<string | null> {
  const layout = resolveLedgerLayout(projectRoot);
  const name = `${normalizeReportId(reportId)}.md`;
  const candidates = [
    join(layout.reportsDir, name),
    join(layout.lifecycleRoot, "review", name),
    join(layout.lifecycleRoot, "done", name),
  ];
  for (const p of candidates) {
    try {
      await fs.access(p);
      return p;
    } catch {
      /* try next */
    }
  }
  return null;
}

/**
 * Overrule an automatic REVIEW-GATE artefact and restore the linked REPORT.
 */
export async function overruleReviewGate(
  input: OverruleReviewGateInput,
): Promise<OverruleReviewGateResult> {
  const filename = basename(input.reviewFilename);
  if (!filename || filename.includes("..") || filename.includes("/") || filename.includes("\\")) {
    return { ok: false, code: "INVALID_FILENAME", error: "invalid review filename" };
  }

  const layout = resolveLedgerLayout(input.projectRoot);
  const reviewPath = join(layout.reviewsDir, filename);
  let raw = "";
  try {
    raw = await fs.readFile(reviewPath, "utf-8");
  } catch {
    // Also allow already-moved reviews under approved/rejected/obsolete
    const nested = ["obsolete", "approved", "rejected", "overruled"];
    let found = "";
    for (const sub of nested) {
      const p = join(layout.reviewsDir, sub, filename);
      try {
        raw = await fs.readFile(p, "utf-8");
        found = p;
        break;
      } catch {
        /* continue */
      }
    }
    if (!found) {
      return { ok: false, code: "FILE_NOT_FOUND", error: `review not found: ${filename}` };
    }
    return overruleAtPath({ ...input, reviewPath: found, raw });
  }
  return overruleAtPath({ ...input, reviewPath, raw });
}

async function overruleAtPath(input: OverruleReviewGateInput & {
  reviewPath: string;
  raw: string;
}): Promise<OverruleReviewGateResult> {
  const now = input.now?.() ?? new Date();
  const iso = now.toISOString();
  const actor = String(input.actor || "PM").trim().toUpperCase() || "PM";
  const reason =
    String(input.reason ?? "").trim() || "PM overruled mistaken REVIEW-GATE suggestion";

  const fm = parseMarkdownFrontmatter(input.raw) as Record<string, unknown>;
  const reviewer = strField(fm, "reviewer").toUpperCase();
  if (reviewer && reviewer !== "REVIEW-GATE") {
    return {
      ok: false,
      code: "NOT_REVIEW_GATE",
      error: "only REVIEW-GATE artefacts can be overruled via this API",
    };
  }

  const reportId = normalizeReportId(
    strField(fm, "report_id") || strField(fm, "subject_id"),
  );
  const taskId = strField(fm, "task_id");
  const body = bodyAfterFrontmatter(input.raw);

  const nextFm: Record<string, unknown> = {
    ...fm,
    decision: "overruled",
    resolution_status: "overruled",
    resolved_at: iso,
    overruled_by: actor,
    overrule_reason: reason,
    suggestion_only: true,
  };

  const audit = [
    "",
    "## 纠正记录 / Overrule Audit",
    `- 结果：**overruled**`,
    `- 时间：${iso}`,
    `- 操作人：${actor}`,
    `- 原因：${reason}`,
    "",
  ].join("\n");

  await fs.writeFile(
    input.reviewPath,
    `${renderFrontmatter(nextFm)}\n\n${body.trimEnd()}\n${audit}`,
    "utf-8",
  );

  // Move under overruled/ when still in reviews root (append-only history via move + keep body)
  const layout = resolveLedgerLayout(input.projectRoot);
  const baseName = basename(input.reviewPath);
  const inRoot = join(layout.reviewsDir, baseName) === input.reviewPath;
  let finalReviewPath = input.reviewPath;
  if (inRoot) {
    const destDir = join(layout.reviewsDir, "overruled");
    await fs.mkdir(destDir, { recursive: true });
    finalReviewPath = join(destDir, baseName);
    await fs.rename(input.reviewPath, finalReviewPath);
  }

  let reportRestored = false;
  if (reportId) {
    const reportPath = await findReportPath(input.projectRoot, reportId);
    if (reportPath) {
      const reportRaw = await fs.readFile(reportPath, "utf-8");
      const reportFm = parseMarkdownFrontmatter(reportRaw) as Record<string, unknown>;
      const reportBody = bodyAfterFrontmatter(reportRaw);
      const restored: Record<string, unknown> = {
        ...reportFm,
        status: "done",
        valid: true,
      };
      delete restored["invalidated_by"];
      delete restored["invalid_reason"];
      delete restored["superseded_by"];
      restored["restored_by"] = actor;
      restored["restored_at"] = iso;
      restored["restored_from_review"] = baseName.replace(/\.md$/i, "");
      await fs.writeFile(
        reportPath,
        `${renderFrontmatter(restored)}\n\n${reportBody.trimEnd()}\n`,
        "utf-8",
      );
      reportRestored = true;
    }
  }

  let taskCleared = false;
  if (taskId) {
    const located = await findTaskLocationById(layout.lifecycleRoot, taskId);
    if (located) {
      const store = new TaskFrontmatterStore();
      const { fm: taskFm, body: taskBody } = await store.read(located.path);
      const nextTaskFm = { ...taskFm } as Record<string, unknown>;
      delete nextTaskFm["display_status"];
      delete nextTaskFm["pm_attention_reason"];
      delete nextTaskFm["pm_attention_report_id"];
      if (String(nextTaskFm["review_status"] ?? "").toLowerCase() === "rejected") {
        delete nextTaskFm["review_status"];
      }
      nextTaskFm["review_gate_overruled_at"] = iso;
      nextTaskFm["review_gate_overruled_by"] = actor;
      await store.write(located.path, nextTaskFm as typeof taskFm, taskBody);
      taskCleared = true;
    }
  }

  await new LedgerBuilder({ projectRoot: input.projectRoot }).rebuild();

  return {
    ok: true,
    review_path: finalReviewPath,
    report_id: reportId || undefined,
    task_id: taskId || undefined,
    report_restored: reportRestored,
    task_cleared: taskCleared,
  };
}
