import { mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { spawnSync } from "node:child_process";
import { commanderApprovalProxyFailureSummary } from "../src/components/microservices/host-codex-commander/src/approval-notification";

type JsonRecord = Record<string, unknown>;

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

function asRecord(value: unknown, label: string): JsonRecord {
  assertCondition(typeof value === "object" && value !== null && !Array.isArray(value), `${label} must be an object`, value);
  return value as JsonRecord;
}

function runCli(args: string[], expectStatus: number, extraEnv: Record<string, string> = {}): JsonRecord {
  const result = spawnSync(process.execPath, ["scripts/cli.ts", ...args], {
    cwd: process.cwd(),
    encoding: "utf8",
    env: {
      ...process.env,
      ...extraEnv,
    },
    maxBuffer: 4 * 1024 * 1024,
  });
  assertCondition(result.status === expectStatus, `status mismatch for ${args.join(" ")}`, {
    status: result.status,
    stdout: result.stdout.slice(-2000),
    stderr: result.stderr.slice(-2000),
  });
  assertCondition(result.stdout.trim().length > 0, `command produced no stdout: ${args.join(" ")}`);
  return asRecord(JSON.parse(result.stdout) as unknown, "cli envelope");
}

function dataOf(envelope: JsonRecord): JsonRecord {
  return asRecord(envelope.data, "data");
}

function charLength(value: string): number {
  return Array.from(value).length;
}

function assertPlainApprovalMessage(message: string): void {
  assertCondition(charLength(message) <= 200, "approval notification draft must be <=200 chars", { message, chars: charLength(message) });
  assertCondition(!/[`*_#[\]|]/u.test(message), "approval notification draft must not contain Markdown syntax", { message });
  assertCondition(!message.includes("\n"), "approval notification draft must be one paragraph", { message });
}

const approval = dataOf(runCli([
  "commander",
  "approval",
  "request",
  "--action",
  "code-queue-task-interrupt",
  "--task-id",
  "stale-active-118",
  "--reason",
  "stale-active 恢复需要 interrupt；token=ghp_1234567890abcdef；不要使用 `local` 路径",
  "--dry-run",
], 0, {
  PATH: "/usr/bin:/bin",
}));

assertCondition(approval.ok === true, "approval dry-run must succeed without local powershell", approval);
assertCondition(approval.mutation === false, "approval dry-run must be non-mutating", approval);
assertCondition(!JSON.stringify(approval).includes("ghp_1234567890abcdef"), "approval dry-run must redact secret-like reason", approval);

const claudeqq = asRecord(approval.claudeqq, "claudeqq");
assertCondition(claudeqq.mutation === false, "ClaudeQQ dry-run must not mutate", claudeqq);
assertCondition(claudeqq.sendImplemented === false, "commander skeleton must not claim send implementation", claudeqq);
assertCondition(claudeqq.dryRunNoClaudeQqSend === true, "dry-run must explicitly report no ClaudeQQ send", claudeqq);

const draft = asRecord(claudeqq.notificationDraft, "claudeqq.notificationDraft");
assertCondition(draft.format === "plain-text", "approval draft must be plain text", draft);
assertCondition(draft.markdownAllowed === false, "approval draft must forbid Markdown", draft);
assertCondition(draft.containsMarkdownSyntax === false, "approval draft must report no Markdown syntax", draft);
assertPlainApprovalMessage(String(draft.message));

const path = asRecord(claudeqq.notificationPath, "claudeqq.notificationPath");
assertCondition(path.error === "notification-path-unavailable", "dry-run must expose notification-path-unavailable blocker", path);
assertCondition(path.servicePath === "/api/microservices/claudeqq/proxy/api/push/text", "service path must be backend-core ClaudeQQ proxy", path);
assertCondition(String(path.backendCoreProxyCommand).includes("bun scripts/cli.ts microservice proxy claudeqq /api/push/text --method POST"), "backend-core proxy command must be returned", path);
assertCondition(!String(path.backendCoreProxyCommand).includes("powershell"), "backend-core proxy command must not use local powershell", path);
assertCondition(!String(path.backendCoreProxyCommand).includes(".agents/skills/claudeqq"), "backend-core proxy command must not use local skill path", path);
assertCondition(path.timeoutMs === 15000, "proxy path must disclose timeout", path);

const failure = commanderApprovalProxyFailureSummary({
  ok: false,
  status: 503,
  error: "microservice proxy task failed",
  stderrTail: "token=ghp_1234567890abcdef powershell.exe failed",
});
assertCondition(failure.ok === false, "proxy failure summary must be failing", failure);
assertCondition(failure.error === "notification-proxy-failed", "proxy failure summary must be structured", failure);
assertCondition(failure.degradedReason === "microservice-proxy-failed", "proxy failure degraded reason must be microservice-proxy-failed", failure);
assertCondition(asRecord(failure.rollback, "rollback").issueUpdateRolledBack === false, "proxy failure must not imply issue rollback", failure);
assertCondition(!JSON.stringify(failure).includes("ghp_1234567890abcdef"), "proxy failure summary must redact secrets", failure);

const tmp = mkdtempSync(join(tmpdir(), "unidesk-gh-brief-notify-"));
try {
  const bodyPath = join(tmp, "brief.md");
  writeFileSync(bodyPath, [
    "# 指挥简报",
    "",
    "## 常驻观察与长期建议",
    "",
    "- 保持监督。",
    "",
    "## 更新 2026-05-23 10:00 北京时间",
    "",
    "- 新增高风险审批路径 dry-run 预览。",
    "",
  ].join("\n"), "utf8");
  const ghDryRun = dataOf(runCli([
    "gh",
    "issue",
    "edit",
    "24",
    "--body-file",
    bodyPath,
    "--notify-claudeqq-brief-diff",
    "--dry-run",
  ], 0, {
    UNIDESK_COMMANDER_BRIEF_CLAUDEQQ_BASE_URL: "http://127.0.0.1:9082",
  }));
  const notification = asRecord(ghDryRun.commanderBriefNotification, "commanderBriefNotification");
  const ghClaudeqq = asRecord(notification.claudeqq, "commanderBriefNotification.claudeqq");
  assertCondition(ghClaudeqq.wouldSend === false, "dry-run helper must not allow non-proxy ClaudeQQ path", ghClaudeqq);
  assertCondition(ghClaudeqq.blockedReason === "notification-path-unavailable", "dry-run helper must structure non-proxy blocker", ghClaudeqq);
  assertCondition(String(ghClaudeqq.recommendedCommand).includes("microservice proxy claudeqq"), "dry-run helper must recommend repo-owned proxy command", ghClaudeqq);
} finally {
  rmSync(tmp, { recursive: true, force: true });
}

const hostDoc = readFileSync("docs/reference/host-codex-commander.md", "utf8");
const supervisionDoc = readFileSync("docs/reference/code-queue-supervision.md", "utf8");
for (const snippet of [
  "notification-path-unavailable",
  "microservice proxy claudeqq /api/push/text",
  "200 字以内中文纯文本",
]) {
  assertCondition(hostDoc.includes(snippet), `host commander doc missing snippet: ${snippet}`);
}
for (const snippet of [
  "不超过 200 个中文字符",
  "不使用 Markdown 语法",
  "发送失败只记录到 #24 或对应 blocker issue",
]) {
  assertCondition(supervisionDoc.includes(snippet), `code queue supervision doc missing snippet: ${snippet}`);
}

process.stdout.write(`${JSON.stringify({
  ok: true,
  checks: [
    "commander approval dry-run survives without local powershell or ClaudeQQ skill server",
    "dry-run generates a <=200 char Chinese plain-text ClaudeQQ draft and does not send",
    "dry-run exposes notification-path-unavailable plus backend-core microservice proxy command",
    "microservice proxy failure summary is structured, redacted, and best-effort",
    "legacy gh issue edit notify dry-run rejects non-proxy ClaudeQQ base URLs",
    "reference docs state the high-risk ClaudeQQ approval notification path",
  ],
}, null, 2)}\n`);
