/**
 * write_red_team_review.ts — Schneier's primary writer.
 *
 * UPDATEs public.hypotheses (sets reviewed_by + updated_at) and APPENDS a
 * "## Red-team review (Schneier)" section to the H-NNNN.md case file.
 *
 * No new row is created — a hypothesis can be red-teamed multiple times;
 * each review appends a dated section to the case file and overwrites
 * reviewed_by. The /h/[id] page reads the case file to display the latest
 * review.
 */
import { readFile, writeFile } from "node:fs/promises";
import path from "node:path";
import { audit } from "../lib/audit";
import { env } from "../lib/env";
import { query, queryOne } from "../lib/pg";

export interface RedTeamReviewArgs {
  hypothesis_id: string;
  severity: "low" | "medium" | "high";
  hidden_assumptions: string[];
  hidden_assumptions_pt_br?: string[];
  failure_modes: string[];
  failure_modes_pt_br?: string[];
  alternative_explanations: string[];
  alternative_explanations_pt_br?: string[];
  recommended_tests: string[];
  recommended_tests_pt_br?: string[];
  verdict_one_sentence: string;
  verdict_one_sentence_pt_br?: string;
}

export interface RedTeamReviewContext {
  job_id: string;
  detective: string;
}

const SECTION_MARKER = "## Red-team review";

function buildSection(args: RedTeamReviewArgs, ctx: RedTeamReviewContext): string {
  const ts = new Date().toISOString();
  const bullets = (items: string[], emptyMsg: string): string =>
    items.length === 0 ? emptyMsg : items.map((x) => `- ${x}`).join("\n");
  const pt = (a: string[] | undefined, fb: string[]) => a && a.length === fb.length ? a : fb;
  const ptHidden  = pt(args.hidden_assumptions_pt_br, args.hidden_assumptions);
  const ptFail    = pt(args.failure_modes_pt_br, args.failure_modes);
  const ptAlt     = pt(args.alternative_explanations_pt_br, args.alternative_explanations);
  const ptTests   = pt(args.recommended_tests_pt_br, args.recommended_tests);
  const ptVerdict = args.verdict_one_sentence_pt_br ?? args.verdict_one_sentence;

  return [
    "",
    `${SECTION_MARKER} (Schneier · ${args.severity} severity)`,
    "",
    `_Reviewed by ${ctx.detective} on ${ts} — job \`${ctx.job_id}\`._`,
    "",
    `**Verdict (EN).** ${args.verdict_one_sentence}`,
    `**Veredito (PT-BR).** ${ptVerdict}`,
    "",
    "### Hidden assumptions (EN)",
    bullets(args.hidden_assumptions, "_(none flagged)_"),
    "",
    "### Premissas ocultas (PT-BR)",
    bullets(ptHidden, "_(nenhuma sinalizada)_"),
    "",
    "### Failure modes (EN)",
    bullets(args.failure_modes, "_(none flagged)_"),
    "",
    "### Modos de falha (PT-BR)",
    bullets(ptFail, "_(nenhum sinalizado)_"),
    "",
    "### Alternative explanations not addressed (EN)",
    bullets(args.alternative_explanations, "_(none flagged)_"),
    "",
    "### Explicações alternativas não abordadas (PT-BR)",
    bullets(ptAlt, "_(nenhuma sinalizada)_"),
    "",
    "### Recommended discriminating tests (EN)",
    bullets(args.recommended_tests, "_(none flagged)_"),
    "",
    "### Testes discriminantes recomendados (PT-BR)",
    bullets(ptTests, "_(nenhum sinalizado)_"),
    "",
  ].join("\n");
}

/**
 * If the case file already has a "## Red-team review" section, replace from
 * that line through the end of the markdown body. Schneier reviews are
 * idempotent — most recent overwrites the previous.
 */
function appendOrReplace(existing: string, section: string): string {
  const idx = existing.indexOf(`\n${SECTION_MARKER}`);
  if (idx === -1) return existing.trimEnd() + "\n" + section;
  return existing.slice(0, idx).trimEnd() + "\n" + section;
}

export async function writeRedTeamReview(
  body: RedTeamReviewArgs,
  ctx: RedTeamReviewContext,
): Promise<{ hypothesis_id: string; case_file: string; severity: string }> {
  if (!body.hypothesis_id?.match(/^H-\d{4}$/)) {
    throw new Error(`bad hypothesis_id: ${body.hypothesis_id}`);
  }
  if (body.severity !== "low" && body.severity !== "medium" && body.severity !== "high") {
    throw new Error(`bad severity: ${body.severity}`);
  }
  if (!body.verdict_one_sentence?.trim()) throw new Error("verdict_one_sentence required");
  if (!body.verdict_one_sentence_pt_br?.trim()) {
    throw new Error("verdict_one_sentence_pt_br required (bilingual contract)");
  }
  if (body.verdict_one_sentence.length > 280) {
    throw new Error(`verdict too long (${body.verdict_one_sentence.length} > 280)`);
  }
  if (body.verdict_one_sentence_pt_br.length > 280) {
    throw new Error(`verdict_pt_br too long (${body.verdict_one_sentence_pt_br.length} > 280)`);
  }

  // Defensive: cap each array to 5 entries × 240 chars (prompt says ≤ 200 but
  // we leave some slack rather than truncate silently).
  const cap = (items: unknown): string[] =>
    Array.isArray(items)
      ? items
          .map((x) => typeof x === "string" ? x.trim() : "")
          .filter((s) => s.length > 0 && s.length <= 240)
          .slice(0, 5)
      : [];
  const hidden = cap(body.hidden_assumptions);
  const fail   = cap(body.failure_modes);
  const alt    = cap(body.alternative_explanations);
  const tests  = cap(body.recommended_tests);

  // Verify hypothesis exists.
  const h = await queryOne<{ hypothesis_id: string }>(
    `SELECT hypothesis_id FROM public.hypotheses WHERE hypothesis_id = $1`,
    [body.hypothesis_id],
  );
  if (!h) throw new Error(`hypothesis not found: ${body.hypothesis_id}`);

  // UPDATE DB.
  await query(
    `UPDATE public.hypotheses
        SET reviewed_by = $1, updated_at = NOW()
      WHERE hypothesis_id = $2`,
    [ctx.detective, body.hypothesis_id],
  );

  // Append / replace red-team section in the case file.
  const file = path.join(env.CASE_ROOT, "hypotheses", `${body.hypothesis_id}.md`);
  let existing: string;
  try {
    existing = await readFile(file, "utf-8");
  } catch (e) {
    throw new Error(`hypothesis case file missing: ${file} (${(e as Error).message})`);
  }
  const merged = { ...body, hidden_assumptions: hidden, failure_modes: fail, alternative_explanations: alt, recommended_tests: tests };
  const section = buildSection(merged, ctx);
  const next = appendOrReplace(existing, section);
  await writeFile(file, next, "utf-8");

  await audit({
    event: "write_red_team_review",
    job_id: ctx.job_id,
    detective: ctx.detective,
    hypothesis_id: body.hypothesis_id,
    severity: body.severity,
    n_assumptions: hidden.length,
    n_failure_modes: fail.length,
    n_alternatives: alt.length,
    n_tests: tests.length,
    file,
  });

  return { hypothesis_id: body.hypothesis_id, case_file: file, severity: body.severity };
}
