import { readFileSync } from "node:fs";
import {
  buildDocumentNumber,
  extractDocumentNumberFromLegacy,
  parseDocumentNumber,
} from "../src/components/microservices/decision-center/src/document-contract";
import { runDecisionCenterCommandAsync } from "./src/decision-center";

type JsonRecord = Record<string, unknown>;

interface FetchCall {
  path: string;
  init?: { method?: string; body?: unknown };
}

function assertCondition(condition: unknown, message: string, detail: JsonRecord = {}): void {
  if (!condition) throw new Error(`${message}: ${JSON.stringify(detail)}`);
}

function source(path: string): string {
  return readFileSync(path, "utf8");
}

function asRecord(value: unknown): JsonRecord {
  return typeof value === "object" && value !== null && !Array.isArray(value) ? value as JsonRecord : {};
}

function makeFetcher(calls: FetchCall[]) {
  return async (path: string, init?: { method?: string; body?: unknown }): Promise<unknown> => {
    calls.push({ path, init });
    return { ok: true, status: init?.method === "POST" ? 201 : 200, body: { ok: true, record: { id: "dc_test", docNo: "DC-GOAL-P0-2026-001" } } };
  };
}

async function assertCliContract(): Promise<string[]> {
  const checks: string[] = [];
  const config = {} as Parameters<typeof runDecisionCenterCommandAsync>[0];

  {
    const calls: FetchCall[] = [];
    await runDecisionCenterCommandAsync(config, [
      "requirement",
      "create",
      "--title",
      "Doc Create",
      "--body",
      "body",
      "--type",
      "goal",
      "--priority",
      "P0",
      "--doc-type",
      "GOAL",
      "--doc-priority",
      "P0",
      "--doc-year",
      "2026",
      "--signer",
      "Decision Center",
      "--issued-at",
      "2026-05-21",
      "--effective-scope",
      "unidesk",
    ], makeFetcher(calls));
    const call = calls[0];
    const body = asRecord(call?.init?.body);
    assertCondition(call?.path === "/api/microservices/decision-center/proxy/api/requirements", "create must call requirements collection route", { call });
    assertCondition(call?.init?.method === "POST", "create must use POST", { call });
    assertCondition(body.docType === "GOAL" && body.docPriority === "P0" && body.docYear === 2026, "create must include document allocation fields", { body });
    assertCondition(body.signer === "Decision Center" && body.issuedAt === "2026-05-21" && body.effectiveScope === "unidesk", "create must include document metadata fields", { body });
    checks.push("cli-create-document-fields");
  }

  {
    const calls: FetchCall[] = [];
    await runDecisionCenterCommandAsync(config, [
      "requirement",
      "upsert",
      "--id",
      "dc_goal_big_paper_submission",
      "--title",
      "Doc Upsert",
      "--body",
      "body",
      "--doc-no",
      "dc-goal-p0-2026-001",
      "--supersedes",
      "DC-DCSN-P0-2026-001",
    ], makeFetcher(calls));
    const call = calls[0];
    const body = asRecord(call?.init?.body);
    assertCondition(call?.init?.method === "PUT", "upsert must use PUT", { call });
    assertCondition(body.docNo === "DC-GOAL-P0-2026-001", "upsert must normalize explicit docNo", { body });
    assertCondition(Array.isArray(body.supersedes) && body.supersedes[0] === "DC-DCSN-P0-2026-001", "upsert must include supersedes list", { body });
    checks.push("cli-upsert-document-fields");
  }

  {
    const calls: FetchCall[] = [];
    await runDecisionCenterCommandAsync(config, [
      "requirement",
      "update",
      "DC-GOAL-P0-2026-001",
      "--signer",
      "Signer B",
      "--superseded-by",
      "DC-GOAL-P0-2026-002",
    ], makeFetcher(calls));
    const call = calls[0];
    const body = asRecord(call?.init?.body);
    assertCondition(call?.path === "/api/microservices/decision-center/proxy/api/requirements/DC-GOAL-P0-2026-001", "update must address records by docNo", { call });
    assertCondition(call?.init?.method === "PUT", "update must use PUT", { call });
    assertCondition(body.signer === "Signer B", "update must include signer", { body });
    assertCondition(Array.isArray(body.supersededBy) && body.supersededBy[0] === "DC-GOAL-P0-2026-002", "update must include supersededBy list", { body });
    checks.push("cli-update-document-fields");
  }

  {
    const calls: FetchCall[] = [];
    await runDecisionCenterCommandAsync(config, [
      "requirement",
      "list",
      "--doc-no",
      "DC-GOAL-P0-2026-001",
      "--doc-type",
      "GOAL",
      "--doc-priority",
      "P0",
      "--year",
      "2026",
    ], makeFetcher(calls));
    const path = calls[0]?.path ?? "";
    assertCondition(path.includes("/api/requirements?"), "list must call requirements query route", { path });
    assertCondition(path.includes("docNo=DC-GOAL-P0-2026-001"), "list must filter by docNo", { path });
    assertCondition(path.includes("docType=GOAL") && path.includes("docPriority=P0") && path.includes("docYear=2026"), "list must filter by document components", { path });
    checks.push("cli-list-document-query");
  }

  {
    const calls: FetchCall[] = [];
    await runDecisionCenterCommandAsync(config, ["requirement", "show", "DC-GOAL-P0-2026-001"], makeFetcher(calls));
    assertCondition(calls[0]?.path === "/api/microservices/decision-center/proxy/api/requirements/DC-GOAL-P0-2026-001", "show must support docNo path keys", { call: calls[0] });
    checks.push("cli-show-document-key");
  }

  return checks;
}

export async function runDecisionCenterDocumentContract(): Promise<JsonRecord> {
  const service = source("src/components/microservices/decision-center/src/index.ts");
  const cli = source("scripts/src/decision-center.ts");
  const cliEntry = source("scripts/cli.ts");
  const remote = source("scripts/src/remote.ts");
  const doc = parseDocumentNumber("dc-goal-p0-2026-1");
  assertCondition(doc.docNo === "DC-GOAL-P0-2026-001", "docNo parser must normalize sequence width", { doc });
  assertCondition(buildDocumentNumber("DCSN", "P0", 2026, 1) === "DC-DCSN-P0-2026-001", "docNo builder must produce canonical sequence");

  const legacyCases = [
    { id: "dc_decision_thesis_unidesk_integration_rule", expected: "DC-DCSN-P0-2026-001" },
    { id: "x", title: "DC-GOAL-P0-2026-001 big paper", expected: "DC-GOAL-P0-2026-001" },
    { id: "x", body: "doc-no: DC-GOAL-P0-2026-002\n\nBody stays unchanged.", expected: "DC-GOAL-P0-2026-002" },
    { id: "x", tags: ["doc-no:DC-DCSN-P0-2026-001"], expected: "DC-DCSN-P0-2026-001" },
    { id: "dc_goal_big_paper_submission", expected: "DC-GOAL-P0-2026-001" },
    { id: "dc_goal_small_paper_submission_gate", expected: "DC-GOAL-P0-2026-002" },
  ];
  for (const item of legacyCases) {
    const extracted = extractDocumentNumberFromLegacy(item);
    assertCondition(extracted?.docNo === item.expected, "legacy document number extraction failed", { item, extracted });
  }

  assertCondition(service.includes("CREATE UNIQUE INDEX IF NOT EXISTS idx_decision_center_records_doc_no_unique"), "service must enforce docNo uniqueness");
  assertCondition(service.includes("doc_no = 'DC-' || doc_type || '-' || doc_priority || '-' || doc_year::text || '-' || lpad(doc_seq::text, 3, '0')"), "schema must keep docNo and parsed components consistent");
  assertCondition(service.includes("clearInvalidDocumentNumbers") && service.indexOf("await clearInvalidDocumentNumbers();") < service.indexOf("ADD CONSTRAINT decision_center_records_doc_shape_check"), "schema migration must clear invalid doc fields before adding the document shape check");
  assertCondition(service.includes("document number already exists") && service.includes("code: \"doc_no_conflict\""), "service must expose structured duplicate docNo errors");
  assertCondition(service.includes("nextDocumentSequence") && service.includes("LOCK TABLE decision_center_records IN SHARE ROW EXCLUSIVE MODE"), "service must allocate next doc sequence under table lock");
  assertCondition(service.includes("getRecordByIdOrDocNo(id, docNo)") && service.includes("doc_no = ${docNo || null}"), "requirement upsert must resolve existing records by id or docNo");
  assertCondition(service.includes("clearDuplicateDocumentNumbers") && service.indexOf("await backfillLegacyDocumentNumbers();") < service.indexOf("CREATE UNIQUE INDEX IF NOT EXISTS idx_decision_center_records_doc_no_unique"), "schema migration must de-duplicate/backfill before creating docNo unique index");
  assertCondition(service.includes("backfillLegacyDocumentNumbers") && service.includes("updated_at = updated_at"), "service must run idempotent legacy backfill without touching body/title/tags");
  assertCondition(service.includes("const documentFields = [\"docNo\"") && service.includes("delete inheritedBase[field]"), "meeting import must persist its own document fields without cloning them to child decisions");
  assertCondition(service.includes("WHERE id = ${id}") && service.includes("doc_no = ${docNo || null}"), "service get route must support id or docNo lookup");
  assertCondition(service.includes("ORDER BY") && service.includes("doc_seq ASC NULLS LAST"), "service list route must sort by document number components");
  assertCondition(cli.includes("--doc-no") && cli.includes("--doc-type") && cli.includes("--doc-priority") && cli.includes("--issued-at"), "CLI must expose document options");
  assertCondition(cliEntry.includes("const result = await runDecisionCenterCommand(config, args.slice(1))") && cliEntry.includes("if (!ok) process.exitCode = 1"), "local CLI must propagate decision ok:false as process failure");
  assertCondition(remote.includes("const result = await runDecisionCenterCommandAsync(config, args.slice(1), fetcher)") && remote.includes("return ok ? 0 : 1"), "remote CLI must propagate decision ok:false as process failure");

  const cliChecks = await assertCliContract();
  return {
    ok: true,
    checks: [
      "doc-number-parse-and-build",
      "doc-no-unique-structured-error-contract",
      "docNo-component-consistency-check",
      "invalid-docNo-migration-guard",
      "doc-sequence-auto-allocation-contract",
      "upsert-by-docNo-contract",
      "duplicate-docNo-migration-guard",
      "legacy-doc-no-title-tag-body-idempotent-backfill",
      "meeting-import-document-field-contract",
      "service-docNo-and-component-query-contract",
      "service-docNo-sort-contract",
      "cli-structured-error-exit-contract",
      ...cliChecks,
    ],
  };
}

if (import.meta.main) {
  process.stdout.write(`${JSON.stringify(await runDecisionCenterDocumentContract(), null, 2)}\n`);
}
