#!/usr/bin/env node

import { mkdir, writeFile } from "node:fs/promises";
import path from "node:path";
import { fileURLToPath } from "node:url";

const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../..");
const deviceId = process.env.WORKPHONE_DEVICE_ID || "xgfe65eimrrofyws";
const base = (process.env.WORKPHONE_BASE || "http://127.0.0.1:8899").replace(/\/$/, "");
const writeTo = process.env.WORKPHONE_TEST_TO_ID || "";
const includeRealWrite = process.env.WORKPHONE_INCLUDE_REAL_WRITE === "1";
const stamp = new Date().toISOString().replace(/\D/g, "").slice(0, 14);
const outputDir = path.join(
  root,
  "开发文档/8、部署/05-测试验收",
  `${stamp.slice(0, 8)}_微信私域能力验收`,
);
const rows = [];

async function request(method, route, body) {
  const response = await fetch(`${base}${route}`, {
    method,
    headers: body ? { "Content-Type": "application/json" } : undefined,
    body: body ? JSON.stringify(body) : undefined,
    signal: AbortSignal.timeout(90_000),
  });
  const text = await response.text();
  let data;
  try {
    data = JSON.parse(text);
  } catch {
    data = { raw: text };
  }
  if (!response.ok) {
    throw new Error(`HTTP ${response.status}: ${JSON.stringify(data).slice(0, 300)}`);
  }
  return data;
}

function query(route, params) {
  const search = new URLSearchParams(params);
  return `${route}?${search.toString()}`;
}

function dataOf(body) {
  return body?.data && typeof body.data === "object" ? body.data : body;
}

function summarize(body) {
  const data = dataOf(body) || {};
  const summary = {
    code: body?.code,
    channel_used: body?.channel_used || data.channel,
    success: data.success ?? body?.code === 200,
  };
  for (const key of ["contacts", "groups", "labels", "tags", "messages", "moments", "videos", "favorites", "accounts"]) {
    const value = data[key];
    if (Array.isArray(value)) summary[`${key}_count`] = value.length;
  }
  for (const key of [
    "count",
    "contact_count",
    "group_count",
    "tag_count",
    "message_count",
    "returned_count",
    "requested_limit",
    "offset",
    "total_count",
    "raw_total_count",
    "has_more",
    "dry_run",
    "confirm_required",
    "error_code",
    "error",
    "note",
  ]) {
    if (key in data) summary[key] = data[key];
  }
  return summary;
}

function passes(body) {
  const data = dataOf(body) || {};
  return (
    body?.code === undefined ||
    body.code === 200 ||
    data.success === true ||
    data.dry_run === true ||
    data.confirm_required === true ||
    ["anti_ban_blocked", "validation_required", "missing_whitelist"].includes(data.error_code)
  );
}

async function run(name, category, task) {
  const started = Date.now();
  process.stdout.write(`▶ ${category.padEnd(10)} ${name.padEnd(28)} `);
  try {
    const body = await task();
    const status = passes(body) ? "passed" : "failed";
    rows.push({ name, category, status, elapsed_ms: Date.now() - started, summary: summarize(body) });
    console.log(`${status} ${Date.now() - started}ms`);
  } catch (error) {
    rows.push({ name, category, status: "failed", elapsed_ms: Date.now() - started, error: String(error.message || error) });
    console.log(`failed ${Date.now() - started}ms`);
  }
}

const common = { device_id: deviceId, platform: "wechat" };
const hook = (action, params = {}) =>
  request("POST", "/api/v3/hook/execute", {
    ...common,
    action,
    params,
    hook_only: true,
  });

await run("health", "base", () => request("GET", "/health"));
await run("hook_probe", "base", () => request("GET", `/api/v3/hook/probe/${deviceId}`));
await run("devices", "base", () => request("GET", "/api/v3/devices"));
await run("profile", "read", () => request("GET", query("/api/v3/profile/get", common)));
await run("contacts", "read", () => request("GET", query("/api/v3/contacts", { ...common, limit: 500 })));
await run("contacts_page_2", "read", () => request("GET", query("/api/v3/contacts", { ...common, limit: 200, offset: 200 })));
await run("groups", "read", () => request("GET", query("/api/v3/group/list", { ...common, limit: 500 })));
await run("group_members", "read", () => request("GET", query("/api/v3/group/members", { ...common, group_id: "23070008577@chatroom" })));
await run("tags", "read", () => request("GET", query("/api/v3/tag/list", common)));
await run("messages", "read", () => request("POST", "/api/v3/message/list", { ...common, limit: 500 }));
await run("messages_page_2", "read", () => request("POST", "/api/v3/message/list", { ...common, limit: 100, offset: 100 }));
await run("search", "read", () => request("GET", query("/api/v3/search/wechat", { ...common, keyword: "客户" })));
await run("hook_data_preview", "read", () =>
  request(
    "GET",
    query(`/api/v3/hook/data/${deviceId}`, {
      modules: "profile,contacts,groups,labels,messages,device_info",
      contact_limit: 200,
      message_limit: 50,
    }),
  ),
);
await run("customer_profile_bundle", "read", () =>
  request("GET", query("/api/v3/customer/profile-bundle", { ...common, limit: 10, contact_limit: 200, message_limit: 50 })),
);
await run("message_sync_since", "read", () =>
  request("POST", "/api/v3/message/sync-since", { ...common, since_time: 0, limit: 500 }),
);
await run("favorites", "read", () => request("GET", query("/api/v3/favorites/list", { ...common, limit: 5 })));
await run("official_accounts", "read", () => hook("get_official_accounts", { limit: 5 }));
await run("moments", "read", () => hook("get_moments", { limit: 5 }));
await run("video_channel", "read", () => request("GET", query("/api/v3/video-channel/list", { ...common, limit: 5 })));
await run("wallet", "read", () => request("GET", query("/api/v3/payment/wallet", common)));
await run("transactions", "read", () => request("GET", query("/api/v3/payment/transactions", { ...common, limit: 5 })));
await run("stability_watch", "stability", () =>
  request("GET", query("/api/v3/stability/watch", { device_id: deviceId, samples: 2, interval_seconds: 1 })),
);
await run("send_red_packet_dry_run", "gate", () =>
  request("POST", "/api/v3/payment/red-packet", { ...common, to_id: writeTo || "filehelper", amount: "0.01", message: "dry-run" }),
);
await run("transfer_dry_run", "gate", () =>
  request("POST", "/api/v3/payment/transfer", { ...common, to_id: writeTo || "filehelper", amount: "0.01", message: "dry-run" }),
);

if (includeRealWrite && writeTo) {
  await run("send_message_whitelist", "write", () =>
    request("POST", "/api/v3/message/send", {
      ...common,
      to_id: writeTo,
      content: `[私域验收] ${stamp}`,
      msg_type: "text",
      channel: "hook",
    }),
  );
} else {
  rows.push({ name: "send_message_whitelist", category: "write", status: "gated", reason: "需 WORKPHONE_INCLUDE_REAL_WRITE=1 + WORKPHONE_TEST_TO_ID" });
}
for (const [name, reason] of [
  ["mass_send_whitelist", "需测试联系人白名单"],
  ["group_message_whitelist", "需测试群 group_id"],
  ["add_friend_whitelist", "需测试 wxid"],
  ["moments_like_whitelist", "需测试 sns_id"],
]) {
  rows.push({ name, category: "write", status: "gated", reason });
}

const summary = {
  passed: rows.filter((row) => row.status === "passed").length,
  failed: rows.filter((row) => row.status === "failed").length,
  gated: rows.filter((row) => row.status === "gated").length,
  total: rows.length,
};
await mkdir(outputDir, { recursive: true });
const reportPath = path.join(outputDir, `private_domain_acceptance_node_${stamp}.json`);
await writeFile(reportPath, `${JSON.stringify({ timestamp: new Date().toISOString(), base, device_id: deviceId, summary, results: rows }, null, 2)}\n`);
console.log(`\n私域能力验收(Node): passed=${summary.passed} failed=${summary.failed} gated=${summary.gated} -> ${reportPath}`);
process.exitCode = summary.failed === 0 ? 0 : 1;
