import { spawnSync } from "node:child_process";
import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
import { join } from "node:path";
import { tmpdir } from "node:os";
import { compactSubmitSuccessResponseForTest } from "./src/code-queue";

type JsonRecord = Record<string, unknown>;

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

function runCli(args: string[], stdin?: string): { status: number | null; stdout: string; stderr: string; json: JsonRecord | null } {
  const result = spawnSync("bun", ["scripts/cli.ts", ...args], {
    cwd: process.cwd(),
    input: stdin,
    encoding: "utf8",
  });
  const stdout = String(result.stdout || "");
  let json: JsonRecord | null = null;
  try {
    json = JSON.parse(stdout) as JsonRecord;
  } catch {
    json = null;
  }
  return {
    status: result.status,
    stdout,
    stderr: String(result.stderr || ""),
    json,
  };
}

function nestedRecord(value: unknown, path: string[]): JsonRecord {
  let current: unknown = value;
  for (const key of path) {
    assertCondition(current !== null && typeof current === "object" && !Array.isArray(current), "expected object while traversing JSON", { path, key, current });
    current = (current as JsonRecord)[key];
  }
  assertCondition(current !== null && typeof current === "object" && !Array.isArray(current), "expected nested object", { path, current });
  return current as JsonRecord;
}

function stringArray(value: unknown): string[] {
  return Array.isArray(value) ? value.map((item) => String(item)) : [];
}

function assertDryRunPrompt(response: JsonRecord, expectedText: string): void {
  assertCondition(response.ok === true, "submit dry-run should succeed", response);
  const data = nestedRecord(response.data, []);
  assertCondition(data.dryRun === true, "submit dry-run should expose dryRun=true", data);
  const request = nestedRecord(response.data, ["request"]);
  const prompt = nestedRecord(request, ["prompt"]);
  assertCondition(prompt.text === expectedText, "submit dry-run prompt text mismatch", prompt);
  assertCondition(prompt.chars === expectedText.length, "submit dry-run prompt char count mismatch", prompt);
  assertCondition(prompt.truncated === false, "submit dry-run prompt must expose the full prompt", prompt);
}

export function runCodeQueueCliSubmitPromptContract(): JsonRecord {
  const multilinePrompt = [
    "Goal: verify stdin prompt path",
    "JSON: {\"quote\":\"'single' and \\\"double\\\"\"}",
    "Markdown: `code` | table | value",
    "Backslash: C:\\tmp\\prompt",
    "",
  ].join("\n");

  const stdin = runCli(["codex", "submit", "--prompt-stdin", "--queue", "prompt-contract", "--dry-run"], multilinePrompt);
  assertDryRunPrompt(stdin.json ?? {}, multilinePrompt);
  assertCondition(String(stdin.json?.command || "") === "codex submit --prompt-stdin --queue prompt-contract --dry-run", "stdin command should list flags without echoing prompt", stdin.json ?? {});

  const tmp = mkdtempSync(join(tmpdir(), "unidesk-code-queue-submit-"));
  const promptFile = join(tmp, "prompt.md");
  const filePrompt = `${multilinePrompt}file prompt tail\n`;
  writeFileSync(promptFile, filePrompt, "utf8");
  try {
    const fromFile = runCli(["codex", "submit", "--prompt-file", promptFile, "--queue", "prompt-contract", "--dry-run"]);
    assertDryRunPrompt(fromFile.json ?? {}, filePrompt);
    assertCondition(String(fromFile.json?.command || "").includes(`--prompt-file ${promptFile}`), "prompt-file command should retain file path for review", fromFile.json ?? {});
    assertCondition(!String(fromFile.json?.command || "").includes("file prompt tail"), "prompt-file command must not echo file prompt text", fromFile.json ?? {});
  } finally {
    rmSync(tmp, { recursive: true, force: true });
  }

  const positional = runCli(["codex", "submit", "short smoke prompt", "--dry-run"]);
  assertDryRunPrompt(positional.json ?? {}, "short smoke prompt");
  assertCondition(String(positional.json?.command || "").includes("<prompt:redacted>"), "outer command should redact positional submit prompt", positional.json ?? {});
  assertCondition(!String(positional.json?.command || "").includes("short smoke prompt"), "outer command must not echo positional submit prompt", positional.json ?? {});

  const duplicateSource = runCli(["codex", "submit", "positional", "--prompt-stdin", "--dry-run"], "stdin\n");
  assertCondition(duplicateSource.status !== 0, "duplicate prompt source should fail", duplicateSource.json ?? { stdout: duplicateSource.stdout });
  const duplicateMessage = String(nestedRecord(duplicateSource.json, ["error"]).message || "");
  assertCondition(duplicateMessage.includes("exactly one prompt source"), "duplicate prompt source error should be explicit", { duplicateMessage });

  const longSubmittedPrompt = `${multilinePrompt}${"submitted prompt body must not be echoed\n".repeat(80)}`;
  const submitSuccess = compactSubmitSuccessResponseForTest({
    tasks: [{
      id: "codex_submit_success_contract",
      queueId: "prompt-contract",
      status: "queued",
      providerId: "D601",
      model: "gpt-5.5",
      cwd: "/workspace",
      prompt: longSubmittedPrompt,
      maxAttempts: 99,
      createdAt: "2026-05-22T00:00:00.000Z",
      updatedAt: "2026-05-22T00:00:00.000Z",
    }],
    queue: {
      total: 1,
      queueCount: 1,
      counts: { queued: 1 },
      queuedTaskIds: ["codex_submit_success_contract"],
    },
  }, { ok: true, status: 200 }, { mode: "local-atomic-directory-submit-serialization", acquiredAfterMs: 1, heldMs: 2, throttleMs: 2000 });
  const submitSuccessJson = JSON.stringify(submitSuccess);
  const submitted = nestedRecord(submitSuccess, ["submitted"]);
  assertCondition(submitted.accepted === true, "submit success should confirm accepted write", submitSuccess);
  assertCondition((submitted.taskIds as unknown[]).includes("codex_submit_success_contract"), "submit success should expose task id", submitSuccess);
  assertCondition(submitSuccessJson.includes("promptOmitted"), "submit success should explicitly mark prompt omitted", submitSuccess);
  assertCondition(!submitSuccessJson.includes("submitted prompt body must not be echoed"), "submit success must not echo prompt text", submitSuccess);
  assertCondition(!submitSuccessJson.includes("promptPreview"), "submit success must not include promptPreview", submitSuccess);

  const help = runCli(["codex", "submit", "--help"]);
  assertCondition(help.status === 0 && help.json?.ok === true, "codex submit help should succeed", help.json ?? { stdout: help.stdout });
  const data = nestedRecord(help.json?.data, []);
  const usage = stringArray(data.usage);
  const promptInput = nestedRecord(data, ["promptInput"]);
  const recommended = stringArray(promptInput.recommended);
  const examples = nestedRecord(data, ["examples"]);
  assertCondition(usage.some((line) => line.includes("--prompt-stdin")), "help usage should include --prompt-stdin", { usage });
  assertCondition(usage.some((line) => line.includes("--prompt-file")), "help usage should include --prompt-file", { usage });
  assertCondition(usage.some((line) => line.includes("cat <<'PROMPT'")), "help usage should include a quoted heredoc example", { usage });
  assertCondition(recommended.includes("--prompt-stdin") && recommended.includes("--prompt-file"), "help should recommend stdin and file prompt sources", promptInput);
  assertCondition(String(promptInput.sourceRule || "").includes("Exactly one prompt source"), "help should document exact prompt source rule", promptInput);
  assertCondition(stringArray(examples.stdin).some((line) => line.includes("--prompt-stdin")), "help examples should include stdin command", examples);
  assertCondition(String(examples.file || "").includes("--prompt-file"), "help examples should include file command", examples);

  return {
    ok: true,
    checks: [
      "submit --prompt-stdin preserves multiline quotes and newlines",
      "submit --prompt-file preserves reviewed file contents",
      "submit positional prompt is redacted from the outer command envelope",
      "duplicate submit prompt source fails explicitly",
      "submit success confirms write without echoing prompt",
      "codex submit help documents stdin/file recommendations and copyable examples",
    ],
  };
}

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