import type { RenderedCliResult } from "../output";
import { formatTable, shortenEnd, textValue } from "./manifest";
import { configLabel } from "./types";

export function renderObservabilityPlan(result: Record<string, unknown>): RenderedCliResult {
  const config = record(result.config);
  const target = record(config.target);
  const images = record(config.images);
  const collector = record(config.collector);
  const traceBackend = record(config.traceBackend);
  const metricsBackend = record(config.metricsBackend);
  const renderPlan = record(result.renderPlan);
  const metricsPlan = record(renderPlan.metrics);
  const otlp = record(renderPlan.otlp);
  const connections = records(config.serviceConnections);
  const objects = records(renderPlan.objects);
  const policy = records(result.policy);
  const configRefs = connections.flatMap((connection) => records(connection.configRefs));
  const presentConfigRefs = configRefs.filter((item) => item.present === true);
  const missingConfigRefs = configRefs.filter((item) => item.present !== true);
  const failedPolicy = policy.filter((item) => item.ok === false);
  const serviceNames = new Set(connections.map((item) => textValue(item.serviceName)).filter((item) => item !== "-"));
  const nodes = new Set(connections.map((item) => textValue(record(item.resolved).targetNode)).filter((item) => item !== "-"));
  const connectionRows = connections.map((item) => {
    const resolved = record(item.resolved);
    const refs = records(item.configRefs);
    const present = refs.filter((ref) => ref.present === true).length;
    return [
      shortenEnd(textValue(item.serviceName), 28),
      textValue(resolved.targetNode),
      textValue(resolved.lane),
      textValue(resolved.namespace),
      refs.length === 0 ? "inline" : `${present}/${refs.length}`,
      String(values(item.requiredSpans).length),
    ];
  });
  const objectRows = objects.map((item) => [textValue(item.kind), textValue(item.name)]);
  const lines = [
    `platform-infra observability plan (${result.ok === false ? "not-ok" : "ok"})`,
    "",
    `target=${textValue(target.id)} route=${textValue(target.route)} namespace=${textValue(target.namespace)} role=${textValue(target.role)}`,
    `collector=${textValue(collector.serviceName)} replicas=${textValue(collector.replicas)} image=${textValue(images.collector)}`,
    `tempo=${textValue(traceBackend.serviceName)} replicas=${textValue(traceBackend.replicas)} retention=${textValue(record(traceBackend.storage).retention)} image=${textValue(images.traceBackend)}`,
    `prometheus=${textValue(metricsPlan.disposition)} service=${textValue(metricsBackend.serviceName)} targets=${values(metricsPlan.targetIds).join(",")} replicas=${textValue(metricsBackend.replicas)} retention=${textValue(record(metricsBackend.storage).retention)} image=${textValue(images.metricsBackend)}`,
    `scrapeSelector=${textValue(record(record(metricsBackend.discovery).labelSelector).key)}=${textValue(record(record(metricsBackend.discovery).labelSelector).value)} namespaces=${values(record(metricsBackend.discovery).namespaces).length === 0 ? "all" : values(record(metricsBackend.discovery).namespaces).join(",")} queryBudget=${textValue(record(metricsBackend.query).timeoutSeconds)}s/${textValue(record(metricsBackend.query).maxSeries)}series/${textValue(record(metricsBackend.query).maxResponseBytes)}bytes`,
    `serviceConnections=${connections.length} services=${serviceNames.size} nodes=${nodes.size} configRefs=${presentConfigRefs.length}/${configRefs.length} missing=${missingConfigRefs.length} objects=${objects.length}`,
    "",
    "Service connections:",
    formatTable(
      ["SERVICE", "NODE", "LANE", "NAMESPACE", "CONFIG_REFS", "SPANS"],
      connectionRows.length > 0 ? connectionRows : [["-", "-", "-", "-", "-", "-"]],
    ),
    "",
    "Render objects:",
    formatTable(["KIND", "NAME"], objectRows.length > 0 ? objectRows : [["-", "-"]]),
  ];
  if (missingConfigRefs.length > 0) {
    lines.push(
      "",
      "Missing configRefs:",
      formatTable(
        ["ID", "FILE", "PATH"],
        missingConfigRefs.map((item) => [
          shortenEnd(textValue(item.id), 48),
          shortenEnd(textValue(item.file), 48),
          shortenEnd(textValue(item.path), 64),
        ]),
      ),
    );
  }
  if (failedPolicy.length > 0) {
    lines.push(
      "",
      "Failed policy:",
      formatTable(["NAME", "DETAIL"], failedPolicy.map((item) => [textValue(item.name), shortenEnd(textValue(item.detail), 100)])),
    );
  }
  lines.push(
    "",
    "Endpoints:",
    `  collector-grpc: ${textValue(otlp.collectorGrpcEndpoint)}`,
    `  collector-http: ${textValue(otlp.collectorHttpEndpoint)}`,
    `  tempo-grpc: ${textValue(otlp.backendGrpcEndpoint)}`,
    `  prometheus-http: ${textValue(otlp.prometheusHttpEndpoint)}`,
    "",
    "Next:",
    `  bun scripts/cli.ts platform-infra observability plan --target ${textValue(target.id)} --full`,
    `  bun scripts/cli.ts platform-infra observability plan --target ${textValue(target.id)} --raw`,
    `  bun scripts/cli.ts platform-infra observability status --target ${textValue(target.id)}`,
    "",
    "Disclosure:",
    "  默认视图只展示计划摘要；--full/--raw 显式披露完整 render plan。",
  );
  return {
    ok: result.ok !== false,
    command: "platform-infra observability plan",
    contentType: "text/plain",
    renderedText: lines.join("\n"),
  };
}

export function renderObservabilityPlanFailure(error: unknown, targetId: string | null): RenderedCliResult {
  const message = error instanceof Error ? error.message : String(error);
  const configRef = extractConfigRef(message);
  const sourcePath = configRef?.split("#")[0] ?? configLabel;
  const inspectPattern = configRef?.split("#")[1]?.split(/[.[]/u)[0] ?? "instrumentation";
  const target = targetId ?? "YAML-default";
  const targetArg = targetId === null ? "" : ` --target ${targetId}`;
  const lines = [
    "platform-infra observability plan (not-ok)",
    "",
    `target=${target}`,
    `config=${configLabel}`,
    `configRef=${configRef ?? "unresolved"}`,
    `reason=${shortenEnd(message.replace(/\s+/gu, " ").trim(), 500)}`,
    "",
    "Next:",
    `  rg -n ${quoteArg(inspectPattern)} ${quoteArg(sourcePath)}`,
    `  bun scripts/cli.ts platform-infra observability plan${targetArg} --full`,
    "",
    "Disclosure:",
    "  配置或 configRef 失败会直接显示来源与检查入口，不依赖 stdout dump。",
  ];
  return {
    ok: false,
    command: "platform-infra observability plan",
    contentType: "text/plain",
    renderedText: lines.join("\n"),
  };
}

function extractConfigRef(message: string): string | null {
  const match = message.match(/config\/[A-Za-z0-9_./-]+[.]yaml(?:#[A-Za-z0-9_.\-[\]]+)?/u);
  return match?.[0] ?? null;
}

function quoteArg(value: string): string {
  return /^[A-Za-z0-9_./:[\]-]+$/u.test(value) ? value : `'${value.replaceAll("'", "'\\''")}'`;
}

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

function records(value: unknown): Record<string, unknown>[] {
  return Array.isArray(value) ? value.map(record) : [];
}

function values(value: unknown): unknown[] {
  return Array.isArray(value) ? value : [];
}
