import { readFileSync } from "node:fs";
import type { UniDeskConfig } from "../config";
import type { RenderedCliResult } from "../output";
import { renderTable } from "./render";
import { boolField, compactCapture, parseJsonOutput, runRemoteCodexPoolScript } from "./remote";
import { announcementsScript } from "./remote-scripts";
import { codexPoolRuntimeTarget, defaultCodexPoolRuntimeTargetId } from "./runtime-target";

type AnnouncementAction = "list" | "get" | "create";

interface AnnouncementOptions {
  action: AnnouncementAction;
  id: number | null;
  page: number;
  pageSize: number;
  status: string | null;
  search: string | null;
  sortBy: string;
  sortOrder: "asc" | "desc";
  title: string | null;
  content: string | null;
  announcementStatus: "draft" | "active" | "archived";
  notifyMode: "silent" | "popup";
  targeting: Record<string, unknown>;
  startsAt: number | null;
  endsAt: number | null;
  confirm: boolean;
  targetId: string;
  json: boolean;
}

export async function codexPoolAnnouncements(config: UniDeskConfig, args: string[]): Promise<Record<string, unknown> | RenderedCliResult> {
  if (args[0] === "help" || args.includes("--help")) return renderAnnouncementsHelp();
  const options = parseAnnouncementOptions(args);
  const target = codexPoolRuntimeTarget(options.targetId);
  const payload = announcementPayload(options);
  if (options.action === "create" && !options.confirm) {
    const response = {
      ok: true,
      action: "platform-infra-sub2api-codex-pool-announcements",
      target: targetSummary(target),
      request: payload,
      report: {
        ok: true,
        mode: "dry-run",
        mutation: false,
        writeAttempted: 0,
        announcement: payload.announcement,
        valuesPrinted: false,
      },
      valuesPrinted: false,
    };
    return options.json ? renderAnnouncementsJson(response) : renderAnnouncements(response);
  }
  const remote = await runRemoteCodexPoolScript(config, "announcements", announcementsScript(payload, target), target);
  const report = parseJsonOutput(remote.stdout);
  const ok = remote.exitCode === 0 && boolField(report, "ok", false);
  const response = {
    ok,
    action: "platform-infra-sub2api-codex-pool-announcements",
    target: targetSummary(target),
    request: payload,
    report,
    remote: compactCapture(remote, { full: !ok || report === null }),
    valuesPrinted: false,
  };
  return options.json ? renderAnnouncementsJson(response) : renderAnnouncements(response);
}

function parseAnnouncementOptions(args: string[]): AnnouncementOptions {
  const [actionRaw = "list", ...rest] = args;
  if (!(["list", "get", "create"] as string[]).includes(actionRaw)) throw new Error("announcements usage: list|get|create [options]");
  const action = actionRaw as AnnouncementAction;
  let id: number | null = null;
  let page = 1;
  let pageSize = 20;
  let status: string | null = null;
  let search: string | null = null;
  let sortBy = "created_at";
  let sortOrder: "asc" | "desc" = "desc";
  let title: string | null = null;
  let content: string | null = null;
  let contentFile: string | null = null;
  let announcementStatus: "draft" | "active" | "archived" = "draft";
  let notifyMode: "silent" | "popup" = "silent";
  let targeting: Record<string, unknown> = {};
  let startsAt: number | null = null;
  let endsAt: number | null = null;
  let confirm = false;
  let targetId = defaultCodexPoolRuntimeTargetId();
  let json = false;
  const readValue = (index: number, option: string): [string, number] => {
    const value = rest[index + 1];
    if (value === undefined || value.startsWith("--")) throw new Error(`${option} requires a value`);
    return [value, index + 1];
  };
  for (let index = 0; index < rest.length; index += 1) {
    const arg = rest[index]!;
    if (arg === "--confirm") confirm = true;
    else if (arg === "--json") json = true;
    else if (arg === "--id") { let value: string; [value, index] = readValue(index, arg); id = positiveInteger(value, arg); }
    else if (arg.startsWith("--id=")) id = positiveInteger(arg.slice(5), "--id");
    else if (arg === "--page") { let value: string; [value, index] = readValue(index, arg); page = positiveInteger(value, arg); }
    else if (arg.startsWith("--page=")) page = positiveInteger(arg.slice(7), "--page");
    else if (arg === "--page-size") { let value: string; [value, index] = readValue(index, arg); pageSize = boundedPageSize(value); }
    else if (arg.startsWith("--page-size=")) pageSize = boundedPageSize(arg.slice(12));
    else if (arg === "--status") [status, index] = readValue(index, arg);
    else if (arg.startsWith("--status=")) status = arg.slice(9);
    else if (arg === "--search") [search, index] = readValue(index, arg);
    else if (arg.startsWith("--search=")) search = arg.slice(9);
    else if (arg === "--sort-by") [sortBy, index] = readValue(index, arg);
    else if (arg.startsWith("--sort-by=")) sortBy = arg.slice(10);
    else if (arg === "--sort-order") { let value: string; [value, index] = readValue(index, arg); sortOrder = parseSortOrder(value); }
    else if (arg.startsWith("--sort-order=")) sortOrder = parseSortOrder(arg.slice(13));
    else if (arg === "--title") [title, index] = readValue(index, arg);
    else if (arg.startsWith("--title=")) title = arg.slice(8);
    else if (arg === "--content") [content, index] = readValue(index, arg);
    else if (arg.startsWith("--content=")) content = arg.slice(10);
    else if (arg === "--content-file") [contentFile, index] = readValue(index, arg);
    else if (arg.startsWith("--content-file=")) contentFile = arg.slice(15);
    else if (arg === "--announcement-status") { let value: string; [value, index] = readValue(index, arg); announcementStatus = parseAnnouncementStatus(value); }
    else if (arg.startsWith("--announcement-status=")) announcementStatus = parseAnnouncementStatus(arg.slice(22));
    else if (arg === "--notify-mode") { let value: string; [value, index] = readValue(index, arg); notifyMode = parseNotifyMode(value); }
    else if (arg.startsWith("--notify-mode=")) notifyMode = parseNotifyMode(arg.slice(14));
    else if (arg === "--targeting-json") { let value: string; [value, index] = readValue(index, arg); targeting = parseTargeting(value); }
    else if (arg.startsWith("--targeting-json=")) targeting = parseTargeting(arg.slice(17));
    else if (arg === "--starts-at") { let value: string; [value, index] = readValue(index, arg); startsAt = parseTimestamp(value, arg); }
    else if (arg.startsWith("--starts-at=")) startsAt = parseTimestamp(arg.slice(12), "--starts-at");
    else if (arg === "--ends-at") { let value: string; [value, index] = readValue(index, arg); endsAt = parseTimestamp(value, arg); }
    else if (arg.startsWith("--ends-at=")) endsAt = parseTimestamp(arg.slice(10), "--ends-at");
    else if (arg === "--target") [targetId, index] = readValue(index, arg);
    else if (arg.startsWith("--target=")) targetId = arg.slice(9);
    else throw new Error(`unsupported announcements option: ${arg}`);
  }
  if (action !== "create" && confirm) throw new Error("--confirm is only valid for announcements create");
  if (action === "get" && id === null) throw new Error("announcements get requires --id");
  if (action === "create") {
    if ((content === null) === (contentFile === null)) throw new Error("announcements create requires exactly one of --content or --content-file");
    if (contentFile !== null) content = readFileSync(contentFile, "utf8");
    title = title?.trim() ?? null;
    content = content?.trim() ?? null;
    if (title === null || title.length === 0 || title.length > 200) throw new Error("announcements create requires --title up to 200 characters");
    if (content === null || content.length === 0) throw new Error("announcements create requires non-empty Markdown content");
    if (startsAt !== null && endsAt !== null && startsAt >= endsAt) throw new Error("--starts-at must be earlier than --ends-at");
  }
  return { action, id, page, pageSize, status, search, sortBy, sortOrder, title, content, announcementStatus, notifyMode, targeting, startsAt, endsAt, confirm, targetId, json };
}

function announcementPayload(options: AnnouncementOptions): Record<string, unknown> {
  if (options.action === "list") return { action: "list", page: options.page, pageSize: options.pageSize, status: options.status, search: options.search, sortBy: options.sortBy, sortOrder: options.sortOrder };
  if (options.action === "get") return { action: "get", id: options.id };
  return {
    action: "create",
    confirm: options.confirm,
    announcement: { title: options.title, content: options.content, status: options.announcementStatus, notify_mode: options.notifyMode, targeting: options.targeting, starts_at: options.startsAt, ends_at: options.endsAt },
  };
}

function renderAnnouncements(response: Record<string, unknown>): RenderedCliResult {
  const request = record(response.request);
  const report = record(response.report);
  const action = String(request.action ?? "-");
  const lines = [`ANNOUNCEMENTS action=${action} mode=${text(report.mode)} ok=${text(response.ok)} mutation=${text(report.mutation)}`];
  if (action === "list") {
    const items = arrayOfRecords(report.items);
    lines.push(`SUMMARY total=${text(report.total)} page=${text(report.page)} page-size=${text(report.pageSize)} returned=${items.length}`);
    if (items.length > 0) lines.push("", renderTable([["ID", "STATUS", "NOTIFY", "TITLE", "STARTS_AT", "ENDS_AT"], ...items.map((item) => [text(item.id), text(item.status), text(item.notify_mode), text(item.title), text(item.starts_at), text(item.ends_at)])]));
  } else {
    const announcement = record(report.announcement);
    const mismatchedFields = Array.isArray(report.mismatchedFields) ? report.mismatchedFields.join(",") : "-";
    lines.push(`WRITE attempted=${text(report.writeAttempted)} reconciled=${text(report.reconciled)} id=${text(announcement.id)} mismatched-fields=${mismatchedFields || "-"}`);
    lines.push("SCOPE-CHECK manual-required: 正文必须明确时间范围和接收范围；targeting 不能替代业务范围说明");
    lines.push("COMPENSATION-CHECK manual-required: 列出全部接收用户 ID、可脱敏邮箱、每人金额和总额");
    lines.push(`TITLE ${text(announcement.title)}`, `STATUS ${text(announcement.status)} NOTIFY_MODE ${text(announcement.notify_mode)}`, `STARTS_AT ${text(announcement.starts_at)} ENDS_AT ${text(announcement.ends_at)}`, `TARGETING ${JSON.stringify(record(announcement.targeting))}`, "CONTENT", String(announcement.content ?? ""));
  }
  if (typeof report.error === "string") lines.push(`ERROR ${text(report.error)}`);
  return { ok: response.ok === true, command: "platform-infra sub2api codex-pool announcements", renderedText: lines.join("\n"), contentType: "text/plain", projection: response };
}

function renderAnnouncementsJson(response: Record<string, unknown>): RenderedCliResult {
  return { ok: response.ok === true, command: "platform-infra sub2api codex-pool announcements --json", renderedText: JSON.stringify(response), contentType: "application/json", projection: response };
}

function renderAnnouncementsHelp(): RenderedCliResult {
  const renderedText = [
    "platform-infra sub2api codex-pool announcements list [--page 1] [--page-size 20] [--status draft|active|archived] [--search text] [--sort-by created_at] [--sort-order asc|desc] [--target PK01] [--json]",
    "platform-infra sub2api codex-pool announcements get --id <positive-integer> [--target PK01] [--json]",
    "platform-infra sub2api codex-pool announcements create --title <text> (--content <markdown>|--content-file <path>) [--announcement-status draft|active|archived] [--notify-mode silent|popup] [--targeting-json <json-object>] [--starts-at <unix-seconds|RFC3339>] [--ends-at <unix-seconds|RFC3339>] [--target PK01] [--confirm] [--json]",
    "create 默认 dry-run；只有 --confirm 才调用 Sub2API 原生创建 API，并按返回 ID 回读对账全部公告字段。",
    "范围要求：正文必须明确时间范围和接收范围；--targeting-json 只控制投递，不能替代业务适用范围。",
    "补偿公告：必须列出故障或补偿时间窗、全部实际接收用户 ID、可脱敏邮箱、每人金额和总额；禁止只写‘符合条件的用户’。",
  ].join("\n");
  return { ok: true, command: "platform-infra sub2api codex-pool announcements --help", renderedText, contentType: "text/plain", projection: { ok: true, mutation: false } };
}

function parseTargeting(value: string): Record<string, unknown> { const parsed: unknown = JSON.parse(value); if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) throw new Error("--targeting-json requires a JSON object"); return parsed as Record<string, unknown>; }
function parseTimestamp(value: string, option: string): number { if (/^[1-9][0-9]*$/u.test(value)) return Number(value); const millis = Date.parse(value); if (!Number.isFinite(millis)) throw new Error(`${option} requires Unix seconds or RFC3339`); return Math.floor(millis / 1000); }
function positiveInteger(value: string, option: string): number { const parsed = Number(value); if (!Number.isSafeInteger(parsed) || parsed <= 0) throw new Error(`${option} requires a positive integer`); return parsed; }
function boundedPageSize(value: string): number { const parsed = positiveInteger(value, "--page-size"); if (parsed > 100) throw new Error("--page-size must be at most 100"); return parsed; }
function parseSortOrder(value: string): "asc" | "desc" { if (value !== "asc" && value !== "desc") throw new Error("--sort-order must be asc or desc"); return value; }
function parseAnnouncementStatus(value: string): "draft" | "active" | "archived" { if (value !== "draft" && value !== "active" && value !== "archived") throw new Error("--announcement-status must be draft, active, or archived"); return value; }
function parseNotifyMode(value: string): "silent" | "popup" { if (value !== "silent" && value !== "popup") throw new Error("--notify-mode must be silent or popup"); return value; }
function targetSummary(target: ReturnType<typeof codexPoolRuntimeTarget>): Record<string, unknown> { return { id: target.id, route: target.route, runtimeMode: target.runtimeMode, endpoint: target.serviceDns }; }
function record(value: unknown): Record<string, unknown> { return typeof value === "object" && value !== null && !Array.isArray(value) ? value as Record<string, unknown> : {}; }
function arrayOfRecords(value: unknown): Record<string, unknown>[] { return Array.isArray(value) ? value.filter((item): item is Record<string, unknown> => typeof item === "object" && item !== null && !Array.isArray(item)) : []; }
function text(value: unknown): string { return value === null || value === undefined || value === "" ? "-" : String(value).replace(/[\r\n\t]+/gu, " "); }
