import type { UniDeskConfig } from "./config";
import { renderMachine } from "./cicd-render";
import type { RenderedCliResult } from "./output";
import { getPlatformInfraPipelinesAsCodeNodeStatus } from "./platform-infra-pipelines-as-code";

interface NodeStatusOptions {
  nodeId: string | null;
  targetId: string | null;
  full: boolean;
  raw: boolean;
  output: "text" | "json";
}

export function cicdNodeStatusHelp(): Record<string, unknown> {
  return {
    command: "cicd status --node <NODE>",
    description: "只读汇总节点上每个 Pipelines-as-Code consumer 的手动发布与后续交付状态。",
    usage: [
      "bun scripts/cli.ts cicd status --node NC01",
      "bun scripts/cli.ts cicd status --node NC01 --full",
      "bun scripts/cli.ts cicd status --node NC01 --json",
    ],
    output: "compact text by default; --json/--raw returns machine payload",
    source: "config/platform-infra/pipelines-as-code.yaml consumers filtered by node",
  };
}

export async function runCicdNodeStatusCommand(config: UniDeskConfig | null, args: string[]): Promise<RenderedCliResult> {
  if (args[0] === undefined || args.some(isHelpToken)) {
    return renderMachine("cicd status", cicdNodeStatusHelp(), "json");
  }
  const options = parseNodeStatusOptions(args);
  if (config === null) throw new Error("cicd status requires UniDesk config; run through bun scripts/cli.ts cicd status --node <NODE>");
  const result = await getPlatformInfraPipelinesAsCodeNodeStatus(config, {
    targetId: options.targetId,
    nodeId: options.nodeId,
    full: options.full,
    raw: options.raw,
  });
  if (options.raw) return renderMachine("cicd status", result, "json", result.ok !== false);
  if (options.output === "json") return renderMachine("cicd status", compactNodeStatus(result), "json", result.ok !== false);
  return renderNodeStatus(result, options.full);
}

export function compactNodeStatus(result: Record<string, unknown>): Record<string, unknown> {
  return {
    ok: result.ok === true,
    action: result.action,
    mutation: false,
    node: result.node,
    target: result.target,
    summary: result.summary,
    observation: result.observation,
    readErrors: result.readErrors,
    remoteCaptureCount: result.remoteCaptureCount,
    consumers: arrayRecords(result.consumers).map((row) => {
      const argo = record(row.argo);
      const runtime = record(row.runtime);
      return {
        consumer: row.consumer,
        ready: row.ready === true,
        ciReady: row.ciReady === true,
        argoReady: row.argoReady === true,
        runtimeReady: row.runtimeReady === true,
        pipelineStatus: row.pipelineStatus,
        sourceCommit: row.sourceCommit,
        argo: { sync: argo.sync, health: argo.health },
        runtime: { readyReplicas: runtime.readyReplicas, replicas: runtime.replicas },
        firstBreak: record(row.firstBreak).code,
        reason: row.reason,
      };
    }),
    warnings: result.warnings,
    next: result.next,
    valuesPrinted: false,
  };
}

function parseNodeStatusOptions(args: string[]): NodeStatusOptions {
  let nodeId: string | null = null;
  let targetId: string | null = null;
  let full = false;
  let raw = false;
  let output: "text" | "json" = "text";
  for (let index = 0; index < args.length; index += 1) {
    const arg = args[index];
    if (arg === "status") continue;
    if (arg === "--node" || arg === "--target") {
      const value = args[index + 1];
      if (value === undefined || value.startsWith("--")) throw new Error(`${arg} requires a value`);
      if (!/^[A-Za-z0-9._-]+$/u.test(value)) throw new Error(`${arg} must be a simple node id`);
      if (arg === "--node") nodeId = value;
      else targetId = value;
      index += 1;
    } else if (arg === "--full") {
      full = true;
    } else if (arg === "--raw" || arg === "-o=json" || (arg === "-o" && args[index + 1] === "json")) {
      raw = true;
      full = true;
      output = "json";
      if (arg === "-o") index += 1;
    } else if (arg === "--json") {
      output = "json";
    } else if (isHelpToken(arg)) {
      output = "json";
    } else {
      throw new Error(`unsupported cicd status option: ${arg}`);
    }
  }
  if (nodeId === null && targetId === null) throw new Error("cicd status requires --node <NODE>");
  return { nodeId, targetId, full, raw, output };
}

export function renderNodeStatus(result: Record<string, unknown>, full: boolean): RenderedCliResult {
  const target = record(result.target);
  const summary = record(result.summary);
  const consumers = arrayRecords(result.consumers);
  const rows = consumers.map((row) => [
    stringValue(row.consumer),
    boolText(row.ready),
    stringValue(row.pipelineStatus),
    `${stringValue(row.durationSeconds)}s`,
    short(stringValue(row.sourceCommit)),
    short(stringValue(row.gitopsCommit)),
    `${stringValue(record(row.argo).sync)}/${stringValue(record(row.argo).health)}`,
    stringValue(record(record(row.argo).revisionRelation).relation),
    runtimeText(record(row.runtime)),
    short(stringValue(row.traceId), 10),
    stringValue(record(row.firstBreak).code),
    stringValue(record(row.error).code),
    stringValue(row.reason),
  ]);
  const detailRows = consumers.map((row) => [
    stringValue(row.consumer),
    stringValue(row.latestPipelineRun),
    short(stringValue(row.digest), 18),
    stringValue(row.imageStatus),
    registryText(record(record(row.diagnostics).registry)),
    compactLine(stringValue(record(row.diagnostics).hint)),
  ]);
  const lines = [
    "CI/CD NODE STATUS",
    ...table(["NODE", "TARGET", "STATUS", "CODE", "READY", "READY_COUNT", "BLOCKED", "ELAPSED_MS"], [[
      stringValue(result.node),
      stringValue(target.id),
      stringValue(summary.status),
      stringValue(summary.code),
      boolText(summary.ready),
      `${stringValue(summary.readyCount)}/${stringValue(summary.total)}`,
      stringValue(summary.blockedCount),
      stringValue(summary.elapsedMs),
    ]]),
    "",
    "CONSUMERS",
    ...(rows.length === 0 ? ["-"] : table(["CONSUMER", "READY", "PIPELINE", "DUR", "SOURCE", "GITOPS", "ARGO", "RELATION", "RUNTIME", "TRACE", "FIRST_BREAK", "ERROR", "REASON"], rows)),
    "",
    ...(full ? [
      "DETAIL",
      ...(detailRows.length === 0 ? ["-"] : table(["CONSUMER", "PIPELINERUN", "DIGEST", "IMAGE_STATUS", "REGISTRY", "HINT"], detailRows)),
      "",
    ] : []),
    "NEXT",
    `  status: ${stringValue(record(result.next).status)}`,
    `  history: ${stringValue(record(result.next).history)}`,
    `  fix-manual-release: ${stringValue(record(record(result.next).fixManualRelease).reference)}`,
  ];
  return { ok: result.ok !== false, command: "cicd status", renderedText: lines.join("\n"), contentType: "text/plain" };
}

function registryText(registry: Record<string, unknown>): string {
  const status = stringValue(registry.status);
  return status === "not-configured" ? "N/A" : status;
}

function isHelpToken(value: string | undefined): boolean {
  return value === "help" || value === "--help" || value === "-h";
}

function record(value: unknown): Record<string, unknown> {
  return typeof value === "object" && value !== null && !Array.isArray(value) ? value as Record<string, unknown> : {};
}

function arrayRecords(value: unknown): Record<string, unknown>[] {
  return Array.isArray(value) ? value.filter((item) => typeof item === "object" && item !== null && !Array.isArray(item)) as Record<string, unknown>[] : [];
}

function stringValue(value: unknown, fallback = "-"): string {
  if (typeof value === "string" && value.length > 0) return value;
  if (typeof value === "number" || typeof value === "boolean") return String(value);
  return fallback;
}

function boolText(value: unknown): string {
  return value === true ? "true" : "false";
}

function short(value: string, length = 12): string {
  if (value === "-" || value.length <= length) return value;
  return value.slice(0, length);
}

function runtimeText(runtime: Record<string, unknown>): string {
  const ready = `${stringValue(runtime.readyReplicas)}/${stringValue(runtime.replicas)}`;
  const digest = short(stringValue(runtime.digest), 18);
  return digest === "-" ? ready : `${ready}:${digest}`;
}

function compactLine(value: string): string {
  const trimmed = value.replace(/\s+/gu, " ").trim();
  return trimmed.length > 0 ? trimmed.slice(0, 120) : "-";
}

function table(headers: string[], rows: string[][]): string[] {
  const widths = headers.map((header, index) => Math.max(header.length, ...rows.map((row) => (row[index] ?? "").length)));
  const format = (row: string[]) => row.map((cell, index) => cell.padEnd(widths[index])).join("  ");
  return [format(headers), format(headers.map((header, index) => "-".repeat(Math.max(header.length, widths[index])))), ...rows.map(format)];
}
