import { codexUnreadTriageForTest } from "./src/code-queue";

type JsonRecord = Record<string, unknown>;
type RequestRecord = { path: string; method: string; body: unknown };

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

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

function asArray(value: unknown): unknown[] {
  assertCondition(Array.isArray(value), "expected JSON array", { value });
  return value as unknown[];
}

function task(id: string, queueId: string, status: string, updatedAt: string, readAt: string | null, prompt: string): JsonRecord {
  return {
    id,
    queueId,
    status,
    currentAttempt: 1,
    updatedAt,
    finishedAt: status === "running" ? null : updatedAt,
    readAt,
    displayPrompt: prompt,
    basePrompt: prompt,
    prompt,
    cwd: "/root/unidesk",
    referenceTaskIds: [],
    lastError: null,
  };
}

function fixtureResponse(path: string, init?: { method?: string; body?: unknown }, requests: RequestRecord[] = []): JsonRecord {
  const method = init?.method ?? "GET";
  requests.push({ path, method, body: init?.body });
  if (path.endsWith("/read")) {
    const taskId = decodeURIComponent(path.split("/api/tasks/")[1]?.split("/")[0] ?? "unknown");
    return {
      ok: true,
      status: 200,
      body: {
        ok: true,
        task: task(taskId, "default", "succeeded", "2026-05-22T00:10:00.000Z", "2026-05-22T00:10:01.000Z", "read response prompt should stay compact"),
        queue: { counts: { succeeded: 1 }, unreadTerminal: 0 },
      },
    };
  }
  assertCondition(path.startsWith("/api/microservices/code-queue/proxy/api/tasks/overview"), "unexpected path", { path, method });
  return {
    ok: true,
    status: 200,
    body: {
      ok: true,
      queue: { unreadTerminal: 4 },
      pagination: {
        limit: 200,
        returned: 6,
        total: 6,
        hasMore: false,
        nextBeforeId: null,
        includeActive: true,
      },
      tasks: [
        task("task-new-1", "default", "succeeded", "2026-05-22T00:05:00.000Z", null, "pikasTech/unidesk#20 closeout RAW_PROMPT_SHOULD_NOT_LEAK"),
        task("task-new-2", "review", "failed", "2026-05-22T00:04:00.000Z", null, "https://github.com/pikasTech/unidesk/issues/20 failed task RAW_PROMPT_SHOULD_NOT_LEAK"),
        task("task-new-3", "review", "canceled", "2026-05-22T00:03:00.000Z", null, "pikasTech/unidesk#21 canceled task RAW_PROMPT_SHOULD_NOT_LEAK"),
        task("task-unknown", "misc", "succeeded", "2026-05-22T00:02:00.000Z", null, "no repo marker RAW_PROMPT_SHOULD_NOT_LEAK"),
        task("task-read", "default", "succeeded", "2026-05-22T00:01:00.000Z", "2026-05-22T00:01:01.000Z", "pikasTech/unidesk#20 already read RAW_PROMPT_SHOULD_NOT_LEAK"),
        task("task-running", "default", "running", "2026-05-22T00:00:00.000Z", null, "pikasTech/unidesk#20 running RAW_PROMPT_SHOULD_NOT_LEAK"),
      ],
    },
  };
}

function countItems(bucket: JsonRecord): JsonRecord[] {
  return asArray(bucket.items).map(asRecord);
}

function itemCount(bucket: JsonRecord, key: string): number {
  const row = countItems(bucket).find((item) => item.key === key);
  return typeof row?.count === "number" ? row.count : 0;
}

export function runCodeQueueUnreadTriageContract(): JsonRecord {
  const requests: RequestRecord[] = [];
  const fetcher = (path: string, init?: { method?: string; body?: unknown }): JsonRecord => fixtureResponse(path, init, requests);

  const summary = codexUnreadTriageForTest(["--limit", "2"], fetcher);
  const summaryBody = JSON.stringify(summary);
  const triage = asRecord(asRecord(summary).unreadTriage);
  const counts = asRecord(triage.counts);
  const newest = asRecord(triage.newest);
  const newestItems = asArray(newest.items).map(asRecord);
  const commands = asRecord(triage.commands);

  assertCondition(asRecord(summary).ok === true, "default unread triage should succeed", asRecord(summary));
  assertCondition(triage.readOnly === true && triage.bounded === true, "default unread triage must be read-only and bounded", triage);
  assertCondition(counts.totalUnreadTerminal === 4, "counts should include only unread terminal tasks", counts);
  assertCondition(itemCount(asRecord(counts.byRepo), "pikasTech/unidesk") === 3, "repo counts should group owner/name refs", counts);
  assertCondition(itemCount(asRecord(counts.byIssue), "#20") === 2, "issue counts should group issue refs", counts);
  assertCondition(itemCount(asRecord(counts.byStatus), "succeeded") === 2, "status counts should include terminal statuses", counts);
  assertCondition(itemCount(asRecord(counts.byQueue), "review") === 2, "queue counts should include queues", counts);
  assertCondition(newest.returned === 2 && newest.hasMore === true, "newest items should obey --limit and expose pagination", newest);
  assertCondition(newestItems.every((item) => item.commands === undefined && typeof item.nextStep === "string"), "default unread rows must stay compact without repeated per-task command blocks", { newestItems });
  assertCondition(typeof commands.perTaskRead === "string" && String(commands.perTaskRead).includes("codex read <taskId>"), "triage should preserve per-task read drill-down", commands);
  assertCondition(typeof commands.full === "string" && String(commands.full).includes("codex unread") && String(commands.full).includes("--full"), "triage should expose one full-view expansion command", commands);
  assertCondition(!summaryBody.includes("RAW_PROMPT_SHOULD_NOT_LEAK"), "triage output must not dump raw prompt text", { summaryBody });
  assertCondition(!requests.some((request) => request.path.includes("/summary")), "triage must not fetch per-task summaries by default", { requests });
  assertCondition(!requests.some((request) => request.method === "POST"), "default triage must not mutate", { requests });

  const full = codexUnreadTriageForTest(["--view", "full", "--limit", "2"], fetcher);
  const fullBody = JSON.stringify(full);
  const fullTriage = asRecord(asRecord(full).unreadTriage);
  const fullNewest = asRecord(fullTriage.newest);
  const fullItems = asArray(fullNewest.items).map(asRecord);
  const fullItemCommands = fullItems.map((item) => asRecord(item.commands));
  assertCondition(asRecord(fullTriage.filters).view === "full", "codex unread --view full should disclose full view", fullTriage);
  assertCondition(fullItems.length === 2 && fullItemCommands.every((item) => typeof item.detail === "string" && typeof item.read === "string"), "explicit full unread view should expand per-task commands", { fullItems });
  assertCondition(summaryBody.length < fullBody.length, "default unread summary should stay smaller than explicit full view", { summaryChars: summaryBody.length, fullChars: fullBody.length });

  const guardStart = requests.length;
  const guarded = codexUnreadTriageForTest(["mark-read", "--repo", "pikasTech/unidesk", "--issue", "20", "--limit", "2"], fetcher);
  const guardedTriage = asRecord(asRecord(guarded).unreadTriage);
  const guardedMutation = asRecord(guardedTriage.mutation);
  assertCondition(asRecord(guarded).ok === false, "batch mark-read without --confirm should fail closed", asRecord(guarded));
  assertCondition(guardedMutation.blocked === true && guardedMutation.confirmed === false, "confirm guard should describe blocked mutation", guardedMutation);
  assertCondition(!requests.slice(guardStart).some((request) => request.method === "POST"), "missing --confirm must not POST read calls", { requests: requests.slice(guardStart) });

  const confirmStart = requests.length;
  const confirmed = codexUnreadTriageForTest(["mark-read", "--repo", "pikasTech/unidesk", "--issue", "20", "--limit", "2", "--confirm"], fetcher);
  const confirmedTriage = asRecord(asRecord(confirmed).unreadTriage);
  const confirmedMutation = asRecord(confirmedTriage.mutation);
  const readPosts = requests.slice(confirmStart).filter((request) => request.method === "POST" && request.path.endsWith("/read"));
  assertCondition(asRecord(confirmed).ok === true, "confirmed batch mark-read should succeed", asRecord(confirmed));
  assertCondition(confirmedTriage.readOnly === false && confirmedMutation.confirmed === true, "confirmed mutation should be explicit", confirmedMutation);
  assertCondition(confirmedMutation.attempted === 2 && confirmedMutation.succeeded === 2, "confirmed mutation should respect filters and limit", confirmedMutation);
  assertCondition(readPosts.length === 2 && readPosts[0]?.path.includes("task-new-1") && readPosts[1]?.path.includes("task-new-2"), "confirmed mutation should POST newest filtered task reads only", { readPosts });

  return {
    ok: true,
    checks: [
      "default unread triage is read-only",
      "repo/issue/status/queue counts are present",
      "newest items are bounded",
      "default rows avoid repeated per-task command blocks",
      "--view full expands per-task commands",
      "raw prompt text is omitted",
      "batch mark-read requires --confirm",
      "confirmed batch read respects filters and limit",
    ],
    summaryChars: summaryBody.length,
    fullChars: fullBody.length,
  };
}

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