import type { UniDeskConfig } from "./config";
import { rootPath } from "./config";
import { runCommand } from "./command";
import type { RenderedCliResult } from "./output";
import { egressBenchmarkCompactResult, egressBenchmarkDryRun, egressBenchmarkStartScript, egressBenchmarkStatusScript, type EgressBenchmarkSpec } from "./egress-proxy-benchmark";
import { egressProxyTrafficCompactResult, egressProxyTrafficScript, type EgressProxyTrafficSpec } from "./egress-proxy-traffic";
import { readSub2ApiConfig } from "./platform-infra/config";
import { resolveTarget } from "./platform-infra/manifest";
import { runK3sBuildBenchmarkCommand } from "./platform-infra-k3s-build-benchmark";

type BenchmarkAction = "benchmark" | "benchmark-status" | "benchmark-logs";
type EgressProxyAction = BenchmarkAction | "traffic";

interface BenchmarkOptions {
  action: BenchmarkAction;
  targetId: string;
  profile: "no-mirror";
  confirm: boolean;
  dryRun: boolean;
  samples: number;
  sampleTimeoutSeconds: number;
  timeoutSeconds: number;
  tailLines: number;
}

interface TrafficOptions {
  action: "traffic";
  targetId: string;
  sampleSeconds: number;
  timeoutSeconds: number;
  limit: number;
}

type EgressProxyOptions = BenchmarkOptions | TrafficOptions;

export async function runPlatformInfraEgressProxyCommand(_config: UniDeskConfig, args: string[]): Promise<Record<string, unknown> | RenderedCliResult> {
  if (args[0] === "host") {
    const { runPlatformInfraHostProxyCommand } = await import("./platform-infra-host-proxy");
    return await runPlatformInfraHostProxyCommand(_config, args.slice(1));
  }
  if (args[0] === "k3s-build-benchmark") return runK3sBuildBenchmarkCommand(args.slice(1));
  const options = parseEgressProxyOptions(args);
  if (options.action === "traffic") {
    const spec = platformTrafficSpec(options);
    return proxyTraffic(spec, options);
  }
  const spec = platformBenchmarkSpec(options);
  if (options.action === "benchmark-status" || options.action === "benchmark-logs") return benchmarkStatus(spec, options);
  if (options.dryRun) {
    return renderBenchmark({
      ok: true,
      command: "platform-infra egress-proxy benchmark",
      mode: "dry-run",
      mutation: false,
      plan: egressBenchmarkDryRun(spec),
      next: { confirm: `bun scripts/cli.ts platform-infra egress-proxy benchmark --target ${options.targetId} --profile ${options.profile} --confirm` },
    });
  }
  const result = runTrans(spec.route, egressBenchmarkStartScript(spec), options.timeoutSeconds);
  const parsed = parseJson(result.stdout);
  return renderBenchmark({
    ok: result.exitCode === 0,
    command: "platform-infra egress-proxy benchmark",
    mode: "async-job",
    mutation: result.exitCode === 0,
    target: options.targetId,
    profile: options.profile,
    start: typeof parsed === "object" && parsed !== null ? parsed : { stdoutPreview: result.stdout.slice(0, 2000) },
    result: egressBenchmarkCompactResult(result),
  });
}

function proxyTraffic(spec: EgressProxyTrafficSpec, options: TrafficOptions): RenderedCliResult {
  const result = runTrans(spec.route, egressProxyTrafficScript(spec, options.sampleSeconds), options.timeoutSeconds);
  const parsed = parseJson(result.stdout);
  const data = typeof parsed === "object" && parsed !== null
    ? parsed as Record<string, unknown>
    : { ok: false, reason: "traffic-output-unparseable", stdoutPreview: result.stdout.slice(0, 2000) };
  return renderTraffic({
    ...data,
    ok: result.exitCode === 0 && data.ok !== false,
    command: "platform-infra egress-proxy traffic",
    mode: "traffic",
    target: spec.targetId,
    namespace: spec.namespace,
    serviceName: spec.serviceName,
    servicePort: spec.port,
    sampleSecondsRequested: options.sampleSeconds,
    limit: options.limit,
    result: egressProxyTrafficCompactResult(result),
  });
}

function benchmarkStatus(spec: EgressBenchmarkSpec, options: BenchmarkOptions): RenderedCliResult {
  const result = runTrans(spec.route, egressBenchmarkStatusScript(spec, options.tailLines), options.timeoutSeconds);
  const parsed = parseJson(result.stdout);
  const status = record(record(parsed).status);
  const state = text(status.state, "unknown");
  return renderBenchmark({
    ok: result.exitCode === 0 && (options.action === "benchmark-logs" || state !== "failed"),
    command: `platform-infra egress-proxy ${options.action}`,
    mode: "status",
    mutation: false,
    target: options.targetId,
    profile: options.profile,
    job: typeof parsed === "object" && parsed !== null ? parsed : { stdoutPreview: result.stdout.slice(0, 2000) },
    result: egressBenchmarkCompactResult(result),
  });
}

function platformBenchmarkSpec(options: BenchmarkOptions): EgressBenchmarkSpec {
  const sub2api = readSub2ApiConfig();
  const target = resolveTarget(sub2api, options.targetId);
  const proxy = target.egressProxy;
  if (proxy === null || !proxy.enabled) throw new Error(`target ${target.id} has no enabled egressProxy`);
  return {
    scope: "platform-infra",
    targetId: target.id,
    route: target.route,
    namespace: target.namespace,
    serviceName: proxy.serviceName,
    port: proxy.listenPort,
    noProxy: proxy.noProxy,
    sourceType: proxy.sourceType,
    sourceRef: proxy.sourceRef,
    sourceConfigRef: proxy.sourceConfigRef,
    sourceFingerprint: proxy.sourceFingerprint,
    profile: options.profile,
    samples: options.samples,
    sampleTimeoutSeconds: options.sampleTimeoutSeconds,
  };
}

function platformTrafficSpec(options: TrafficOptions): EgressProxyTrafficSpec {
  const sub2api = readSub2ApiConfig();
  const target = resolveTarget(sub2api, options.targetId);
  const proxy = target.egressProxy;
  if (proxy === null || !proxy.enabled) throw new Error(`target ${target.id} has no enabled egressProxy`);
  return {
    scope: "platform-infra",
    targetId: target.id,
    route: target.route,
    namespace: target.namespace,
    deploymentName: proxy.deploymentName,
    serviceName: proxy.serviceName,
    port: proxy.listenPort,
    sourceType: proxy.sourceType,
    sourceRef: proxy.sourceRef,
    sourceConfigRef: proxy.sourceConfigRef,
    sourceFingerprint: proxy.sourceFingerprint,
  };
}

function parseEgressProxyOptions(args: string[]): EgressProxyOptions {
  const actionRaw = args[0] ?? "benchmark";
  if (!isEgressProxyAction(actionRaw)) {
    throw new Error("platform-infra egress-proxy usage: benchmark|benchmark-status|benchmark-logs|traffic|k3s-build-benchmark --target D601|D518 [--profile no-mirror|no-mirror-600m|real-deps-500m] [--sample-seconds N]");
  }
  const action = actionRaw;
  const rest = args.slice(1);
  const targetId = option(rest, "--target") ?? "D601";
  if (action === "traffic") {
    const sampleSeconds = positiveIntOption(rest, "--sample-seconds", 5, 45);
    return {
      action,
      targetId,
      sampleSeconds,
      timeoutSeconds: positiveIntOption(rest, "--timeout-seconds", Math.min(60, sampleSeconds + 20), 60),
      limit: positiveIntOption(rest, "--limit", 50, 200),
    };
  }
  const profileRaw = option(rest, "--profile") ?? "no-mirror";
  if (profileRaw !== "no-mirror") throw new Error("--profile currently supports no-mirror");
  const confirm = rest.includes("--confirm");
  const dryRun = rest.includes("--dry-run") || !confirm;
  if (confirm && rest.includes("--dry-run")) throw new Error("benchmark accepts only one of --confirm or --dry-run");
  return {
    action,
    targetId,
    profile: profileRaw,
    confirm,
    dryRun: action === "benchmark" ? dryRun : false,
    samples: positiveIntOption(rest, "--samples", 5, 20),
    sampleTimeoutSeconds: positiveIntOption(rest, "--sample-timeout-seconds", 240, 900),
    timeoutSeconds: positiveIntOption(rest, "--timeout-seconds", 30, 60),
    tailLines: positiveIntOption(rest, "--tail", 40, 200),
  };
}

function isEgressProxyAction(value: string): value is EgressProxyAction {
  return value === "benchmark" || value === "benchmark-status" || value === "benchmark-logs" || value === "traffic";
}

function runTrans(route: string, script: string, timeoutSeconds: number) {
  return runCommand(["/root/.local/bin/trans", route, "sh", "--", script], rootPath(), { timeoutMs: timeoutSeconds * 1000 });
}

function option(args: string[], name: string): string | null {
  const index = args.indexOf(name);
  if (index === -1) return null;
  const value = args[index + 1];
  if (value === undefined || value.startsWith("--")) throw new Error(`${name} requires a value`);
  return value;
}

function positiveIntOption(args: string[], name: string, defaultValue: number, maxValue: number): number {
  const raw = option(args, name);
  if (raw === null) return defaultValue;
  const value = Number.parseInt(raw, 10);
  if (!Number.isInteger(value) || value < 1 || value > maxValue) throw new Error(`${name} must be an integer from 1 to ${maxValue}`);
  return value;
}

function parseJson(text: string): unknown {
  const trimmed = text.trim();
  if (trimmed.length === 0) return null;
  try { return JSON.parse(trimmed); } catch {
    const start = trimmed.indexOf("{");
    const end = trimmed.lastIndexOf("}");
    if (start >= 0 && end > start) {
      try { return JSON.parse(trimmed.slice(start, end + 1)); } catch {}
    }
  }
  return null;
}

function renderBenchmark(result: Record<string, unknown>): RenderedCliResult {
  const start = record(result.start);
  const job = record(result.job);
  const status = record(job.status);
  const plan = record(result.plan);
  const next = record(result.next);
  const rows = Array.isArray(status.rows) ? status.rows.map(record) : [];
  const target = text(result.target ?? plan.target);
  const profile = text(result.profile ?? plan.profile, "no-mirror");
  const statusText = text(status.state, result.ok === false ? "failed" : "ok");
  const logTail = typeof job.logTail === "string" ? job.logTail.trimEnd() : "";
  const lines = [
    `platform-infra egress-proxy ${result.mode === "dry-run" ? "benchmark dry-run" : "benchmark"}`,
    "",
    ...table(["TARGET", "PROFILE", "MODE", "STATUS"], [[target, profile, text(result.mode), statusText]]),
    "",
    rows.length === 0 ? "RESULTS\n-" : [
      "RESULTS",
      ...table(["TEST", "SUCCESS", "SAMPLES", "P50", "P95", "FAILURES"], rows.map((row) => [
        text(row.test),
        text(row.success),
        text(row.samples),
        text(row.p50Ms),
        text(row.p95Ms),
        JSON.stringify(row.failureFamilies ?? {}),
      ])),
    ].join("\n"),
    ...(logTail.length === 0 ? [] : ["", "LOG TAIL", logTail]),
    "",
    "NEXT",
    `  ${text(next.confirm, "")}`,
    `  ${text(start.statusCommand, `bun scripts/cli.ts platform-infra egress-proxy benchmark-status --target ${target} --profile ${profile}`)}`,
    `  ${text(start.logsCommand, `bun scripts/cli.ts platform-infra egress-proxy benchmark-logs --target ${target} --profile ${profile}`)}`,
    "",
    "Disclosure: default output is a bounded benchmark table; Secret/proxy source values are redacted.",
  ].filter((line) => line !== "  ");
  return { ok: result.ok !== false, command: text(result.command, "platform-infra egress-proxy benchmark"), renderedText: lines.join("\n"), contentType: "text/plain" };
}

function renderTraffic(result: Record<string, unknown>): RenderedCliResult {
  const totals = record(result.totals);
  const source = record(result.source);
  const next = record(result.next);
  const clients = arrayRecords(result.clients).slice(0, Number(result.limit ?? 50));
  const allClientCount = arrayRecords(result.clients).length;
  const ok = result.ok !== false;
  const target = text(result.target);
  const reason = text(result.reason, ok ? "ok" : "failed");
  const rows = clients.map((client) => {
    const destinations = arrayRecords(client.topDestinations)
      .map((item) => `${text(item.destination)}(${text(item.samples)})`)
      .join(", ");
    return [
      text(client.client),
      text(client.activeConnections, "0"),
      rate(client.totalBps),
      rate(client.uploadBps),
      rate(client.downloadBps),
      bytes(client.windowTotalBytes),
      bytes(client.activeTotalBytes),
      destinations || "-",
    ];
  });
  const lines = [
    "PLATFORM-INFRA EGRESS-PROXY TRAFFIC",
    "",
    ...table(["TARGET", "STATUS", "POD", "DURATION", "SNAPSHOTS", "ACTIVE"], [[
      target,
      ok ? "ok" : reason,
      text(result.pod),
      `${numberText(result.sampleSecondsActual, result.sampleSecondsRequested)}s`,
      text(result.snapshots, "0"),
      text(totals.activeConnections, "0"),
    ]]),
    "",
    "TOTALS",
    ...table(["SCOPE", "RATE", "WINDOW", "ACTIVE/CUMULATIVE"], [
      ["per-client", rate(totals.clientTotalBps), bytes(totals.clientWindowTotalBytes), bytes(totals.clientActiveTotalBytes)],
      ["process", rate(totals.processWindowBps), bytes(totals.processWindowTotalBytes), bytes(totals.processTotalBytes)],
    ]),
    "",
    rows.length === 0 ? "CLIENTS\n-" : [
      "CLIENTS",
      ...table(["CLIENT", "CONNS", "TOTAL/s", "UP/s", "DOWN/s", "WINDOW", "ACTIVE_TOTAL", "TOP_DESTINATIONS"], rows),
    ].join("\n"),
    allClientCount > clients.length ? `\nDisclosure: ${allClientCount - clients.length} more clients hidden; rerun with --limit ${Math.min(200, allClientCount)}.` : "",
    "",
    "SOURCE",
    `  target=${target} namespace=${text(result.namespace)} service=${text(result.serviceName)}:${text(result.servicePort)} controller=${text(result.controller)}`,
    `  sourceType=${text(source.sourceType)} sourceRef=${text(source.sourceRef)} sourceFingerprint=${text(source.sourceFingerprint)} valuesPrinted=false`,
    "",
    "NEXT",
    `  ${text(next.apply, "")}`,
    `  ${text(next.status, `bun scripts/cli.ts platform-infra sub2api status --target ${target} --full`)}`,
    `  bun scripts/cli.ts platform-infra egress-proxy traffic --target ${target} --sample-seconds 15`,
    "",
    "Disclosure: proxy API is read through pod loopback only. Per-client cumulative bytes are for the sample window; process cumulative is since proxy process start when reported by sing-box.",
  ].filter((line) => line !== "  ");
  return { ok, command: "platform-infra egress-proxy traffic", renderedText: lines.join("\n"), contentType: "text/plain" };
}

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

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): Array<Record<string, unknown>> {
  return Array.isArray(value) ? value.map(record) : [];
}

function text(value: unknown, fallback = "-"): string {
  if (value === undefined || value === null || value === "") return fallback;
  return String(value);
}

function numberText(value: unknown, fallback: unknown): string {
  const number = typeof value === "number" && Number.isFinite(value) ? value : Number(fallback);
  if (!Number.isFinite(number)) return text(value);
  return number.toFixed(number >= 10 ? 0 : 1).replace(/\.0$/u, "");
}

function bytes(value: unknown): string {
  const number = Number(value);
  if (!Number.isFinite(number) || number <= 0) return "0 B";
  const units = ["B", "KiB", "MiB", "GiB", "TiB"];
  let scaled = number;
  let unit = 0;
  while (scaled >= 1024 && unit < units.length - 1) {
    scaled /= 1024;
    unit += 1;
  }
  return `${scaled >= 10 || unit === 0 ? scaled.toFixed(0) : scaled.toFixed(1)} ${units[unit]}`;
}

function rate(value: unknown): string {
  return `${bytes(value)}/s`;
}
