// SPEC: PJ2026-01060307 控制面模块化 draft-2026-06-25-p0. actions module for scripts/src/platform-infra-observability.ts.
// SPEC: PJ2026-01060308 cicd-yaml-targets draft-2026-06-25-cicd-yaml-targets.

// Moved mechanically from scripts/src/platform-infra-observability.ts:515-623 for #903.

// SPEC: PJ2026-01060501 OTel追踪 draft-2026-06-19-p0.
// Responsibility: YAML-first platform-infra OpenTelemetry tracing control commands.
import { Buffer } from "node:buffer";
import { readFileSync } from "node:fs";
import type { UniDeskConfig } from "../config";
import { rootPath } from "../config";
import type { RenderedCliResult } from "../output";
import {
  compactCapture,
  createYamlFieldReader,
  numberField,
  parseJsonOutput,
  redactSensitiveUnknown,
  shQuote,
  capture,
} from "../platform-infra-ops-library";

import type { ApplyOptions, CommonOptions, ObservabilityConfig, ObservabilityTarget, StatusEndpoint } from "./types";
import { readObservabilityConfig } from "./config";
import { applyScript, statusScript } from "./search-script";
import { compactStatus, configSummary, manifestObjectSummary, policyChecks, statusSummary, targetSummary } from "./summary";
import { renderManifest } from "./trace-script";
import { formatTable, shortenEnd, textValue } from "./manifest";
import { apiPathField, configLabel, kubernetesNameField, stringField } from "./types";
import { metricsEnabledForTarget, metricsTargetDisposition } from "./metrics-target";

export function parseStatusEndpoint(record: Record<string, unknown>, index: number): StatusEndpoint {
  const path = `probes.statusEndpoints[${index}]`;
  return {
    name: stringField(record, "name", path),
    service: kubernetesNameField(record, "service", path),
    portName: stringField(record, "portName", path),
    path: apiPathField(record, "path", path),
  };
}

export function assertKnownEnabledTarget(targets: ObservabilityTarget[], targetId: string, path: string): void {
  const target = targets.find((item) => item.id.toLowerCase() === targetId.toLowerCase());
  if (target === undefined) throw new Error(`${configLabel}.${path} references unknown target ${targetId}; known targets: ${targets.map((item) => item.id).join(", ")}`);
  if (!target.enabled) throw new Error(`${configLabel}.${path} references disabled target ${target.id}`);
}

export function resolveTarget(observability: ObservabilityConfig, targetId: string | null): ObservabilityTarget {
  const resolved = targetId ?? observability.defaults.targetId;
  const target = observability.targets.find((item) => item.id.toLowerCase() === resolved.toLowerCase());
  if (target === undefined) throw new Error(`unknown observability target ${resolved}; known targets: ${observability.targets.map((item) => item.id).join(", ")}`);
  if (!target.enabled) throw new Error(`observability target ${target.id} is disabled in ${configLabel}`);
  return target;
}

export function plan(options: CommonOptions): Record<string, unknown> {
  const observability = readObservabilityConfig();
  const target = resolveTarget(observability, options.targetId);
  const yaml = renderManifest(observability, target);
  const policy = policyChecks(yaml, target);
  const metricsDisposition = metricsTargetDisposition(observability, target);
  return {
    ok: policy.every((check) => check.ok),
    action: "platform-infra-observability-plan",
    mutation: false,
    config: configSummary(observability, target),
    renderPlan: {
      target: targetSummary(target),
      objects: manifestObjectSummary(yaml),
      metrics: {
        disposition: metricsDisposition,
        targetIds: observability.metricsBackend.targetIds,
      },
      otlp: {
        collectorGrpcEndpoint: `${observability.collector.serviceName}.${target.namespace}.svc.cluster.local:${observability.collector.otlp.grpcPort}`,
        collectorHttpEndpoint: `http://${observability.collector.serviceName}.${target.namespace}.svc.cluster.local:${observability.collector.otlp.httpPort}`,
        backendGrpcEndpoint: `${observability.traceBackend.serviceName}.${target.namespace}.svc.cluster.local:${observability.traceBackend.otlp.grpcPort}`,
        prometheusHttpEndpoint: metricsEnabledForTarget(observability, target) ? `http://${observability.metricsBackend.serviceName}.${target.namespace}.svc.cluster.local:${observability.metricsBackend.httpPort}` : null,
        scrapeDiscovery: observability.metricsBackend.discovery,
      },
      instrumentation: observability.instrumentation.serviceConnections,
      resourceAttributes: observability.resourceAttributes,
    },
    policy,
    next: {
      dryRun: `bun scripts/cli.ts platform-infra observability apply --target ${target.id} --dry-run`,
      apply: `bun scripts/cli.ts platform-infra observability apply --target ${target.id} --confirm`,
      status: `bun scripts/cli.ts platform-infra observability status --target ${target.id}`,
      validate: `bun scripts/cli.ts platform-infra observability validate --target ${target.id}`,
      trace: `bun scripts/cli.ts platform-infra observability trace --target ${target.id} --trace-id <traceId>`,
      metricsQuery: `bun scripts/cli.ts platform-infra observability metrics-query --target ${target.id} --query <promql>`,
    },
  };
}

export async function apply(config: UniDeskConfig, options: ApplyOptions): Promise<Record<string, unknown>> {
  const observability = readObservabilityConfig();
  const target = resolveTarget(observability, options.targetId);
  const yaml = renderManifest(observability, target);
  const policy = policyChecks(yaml, target);
  if (!policy.every((check) => check.ok)) return { ok: false, action: "platform-infra-observability-apply", mode: "policy-blocked", policy };
  const result = await capture(config, target.route, ["sh"], applyScript({
    yaml,
    target,
    dryRun: options.dryRun,
    wait: options.wait,
    collectorDeploymentName: observability.collector.deploymentName,
    backendDeploymentName: observability.traceBackend.deploymentName,
  }));
  const parsed = parseJsonOutput(result.stdout);
  return {
    ok: result.exitCode === 0 && parsed?.ok === true,
    action: "platform-infra-observability-apply",
    mode: options.dryRun ? "dry-run" : "confirmed",
    mutation: !options.dryRun,
    target: targetSummary(target),
    policy,
    remote: parsed ?? compactCapture(result, { full: true }),
  };
}

export async function status(config: UniDeskConfig, options: CommonOptions): Promise<Record<string, unknown> | RenderedCliResult> {
  const observability = readObservabilityConfig();
  const target = resolveTarget(observability, options.targetId);
  const result = await capture(config, target.route, ["sh"], statusScript(observability, target, options.full));
  const parsed = parseJsonOutput(result.stdout);
  const summary = parsed === null ? null : statusSummary(parsed);
  if (!options.full && !options.raw) {
    return renderStatusTable({
      ok: result.exitCode === 0 && summary?.ready === true,
      target,
      backendEndpoint: `${observability.traceBackend.serviceName}.${target.namespace}.svc.cluster.local:${observability.traceBackend.httpPort}`,
      collectorEndpoint: `${observability.collector.serviceName}.${target.namespace}.svc.cluster.local:${observability.collector.otlp.grpcPort}`,
      summary,
      remote: parsed === null ? compactCapture(result, { full: false }) : null,
    });
  }
  return {
    ok: result.exitCode === 0 && summary?.ready === true,
    action: "platform-infra-observability-status",
    mutation: false,
    target: targetSummary(target),
    config: configSummary(observability, target),
    summary,
    remote: options.raw ? parsed : compactStatus(parsed, options.full) ?? compactCapture(result, { full: true }),
    next: {
      plan: `bun scripts/cli.ts platform-infra observability plan --target ${target.id}`,
      apply: `bun scripts/cli.ts platform-infra observability apply --target ${target.id} --confirm`,
      validate: `bun scripts/cli.ts platform-infra observability validate --target ${target.id}`,
    },
  };
}

function renderStatusTable(input: {
  ok: boolean;
  target: ObservabilityTarget;
  backendEndpoint: string;
  collectorEndpoint: string;
  summary: Record<string, unknown> | null;
  remote: Record<string, unknown> | null;
}): RenderedCliResult {
  const deployments = recordList(input.summary?.deployments).map((item) => [
    shortenEnd(textValue(item.name), 34),
    textValue(item.ready),
    `${textValue(item.availableReplicas)}/${textValue(item.replicas)}`,
  ]);
  const pods = recordList(input.summary?.pods).slice(0, 12).map((item) => [
    shortenEnd(textValue(item.name), 44),
    textValue(item.phase),
    textValue(item.ready),
    shortenEnd(textValue(item.reason), 28),
  ]);
  const probes = recordList(input.summary?.probes).map((item) => [
    shortenEnd(textValue(item.name), 24),
    textValue(item.ok),
    shortenEnd(textValue(item.service), 26),
    shortenEnd(textValue(item.path), 38),
  ]);
  const lines = [
    `platform-infra observability status (${input.ok ? "ok" : "not-ok"})`,
    "",
    `target=${input.target.id} namespace=${input.target.namespace} ready=${textValue(input.summary?.ready)} route=${input.target.route}`,
    `tempo=${input.backendEndpoint} collector=${input.collectorEndpoint} deployments=${deployments.length} pods=${pods.length} probes=${probes.length}`,
    "",
    "Deployments:",
    formatTable(["NAME", "READY", "AVAILABLE"], deployments.length > 0 ? deployments : [["-", "-", "-"]]),
    "",
    "Pods:",
    formatTable(["NAME", "PHASE", "READY", "REASON"], pods.length > 0 ? pods : [["-", "-", "-", "-"]]),
    "",
    "Probes:",
    formatTable(["NAME", "OK", "SERVICE", "PATH"], probes.length > 0 ? probes : [["-", "-", "-", "-"]]),
  ];
  if (input.remote !== null) {
    lines.push("", "Remote:");
    lines.push(`  ${shortenEnd(JSON.stringify(input.remote), 240)}`);
  }
  lines.push("", "Next:");
  lines.push(`  bun scripts/cli.ts platform-infra observability validate --target ${input.target.id}`);
  lines.push(`  bun scripts/cli.ts platform-infra observability status --target ${input.target.id} --full`);
  lines.push("", "Disclosure:");
  lines.push("  default view is a bounded table; use --full for compact JSON or --raw for backend/parser debugging.");
  return {
    ok: input.ok,
    command: "platform-infra observability status",
    contentType: "text/plain",
    renderedText: lines.join("\n"),
  };
}

function recordList(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))
    : [];
}

export async function validate(config: UniDeskConfig, options: CommonOptions): Promise<Record<string, unknown> | RenderedCliResult> {
  const observability = readObservabilityConfig();
  const target = resolveTarget(observability, options.targetId);
  const result = await capture(config, target.route, ["sh"], statusScript(observability, target, options.full, true));
  const parsed = parseJsonOutput(result.stdout);
  const summary = parsed === null ? null : statusSummary(parsed);
  const ready = summary?.ready === true;
  const validationTrace = parsed !== null && typeof parsed.validationTrace === "object" && parsed.validationTrace !== null && !Array.isArray(parsed.validationTrace)
    ? parsed.validationTrace as Record<string, unknown>
    : null;
  const validationTraceId = typeof validationTrace?.traceId === "string" ? validationTrace.traceId : null;
  const validationTraceOk = validationTrace?.ok === true;
  const ok = result.exitCode === 0 && ready && validationTraceOk;
  if (!options.full && !options.raw) {
    return renderValidateTable({
      ok,
      target,
      summary,
      validationTrace,
      validationTraceId,
      remote: parsed === null ? compactCapture(result, { full: false }) : null,
    });
  }
  return {
    ok,
    action: "platform-infra-observability-validate",
    mutation: false,
    target: targetSummary(target),
    config: configSummary(observability, target),
    summary,
    validation: {
      readiness: ready ? "passed" : "failed",
      testTrace: validationTrace ?? "not-generated-runtime-not-ready",
      traceQuery: validationTraceId !== null
        ? `bun scripts/cli.ts platform-infra observability trace --target ${target.id} --trace-id ${validationTraceId}`
        : "blocked-until-validation-trace-generated",
      metricsBoundary: "Prometheus/RUM remains outside this trace readiness check.",
    },
    remote: options.raw ? parsed : compactStatus(parsed, options.full) ?? compactCapture(result, { full: true }),
  };
}

function renderValidateTable(input: {
  ok: boolean;
  target: ObservabilityTarget;
  summary: Record<string, unknown> | null;
  validationTrace: Record<string, unknown> | null;
  validationTraceId: string | null;
  remote: Record<string, unknown> | null;
}): RenderedCliResult {
  const ingest = input.validationTrace === null ? null : asRecord(input.validationTrace.ingest);
  const query = input.validationTrace === null ? null : asRecord(input.validationTrace.query);
  const lines = [
    `platform-infra observability validate (${input.ok ? "ok" : "not-ok"})`,
    "",
    `target=${input.target.id} namespace=${input.target.namespace} ready=${textValue(input.summary?.ready)} route=${input.target.route}`,
    "",
    "Runtime:",
    formatTable(["DEPLOYMENTS", "SERVICES", "PODS", "PROBES"], [[
      String(recordList(input.summary?.deployments).length),
      String(Array.isArray(input.summary?.services) ? input.summary.services.length : 0),
      String(recordList(input.summary?.pods).length),
      String(recordList(input.summary?.probes).length),
    ]]),
    "",
    "Validation trace:",
    formatTable(["TRACE", "STAGE", "INGEST", "QUERY", "VALUES_PRINTED"], [[
      shortenEnd(input.validationTraceId ?? "-", 32),
      textValue(input.validationTrace?.stage),
      textValue(ingest?.ok),
      textValue(query?.ok),
      textValue(input.validationTrace?.valuesPrinted),
    ]]),
  ];
  if (input.remote !== null) {
    lines.push("", "Remote:");
    lines.push(`  ${shortenEnd(JSON.stringify(input.remote), 240)}`);
  }
  lines.push("", "Next:");
  if (input.validationTraceId !== null) {
    lines.push(`  bun scripts/cli.ts platform-infra observability trace --target ${input.target.id} --trace-id ${input.validationTraceId}`);
  }
  lines.push(`  bun scripts/cli.ts platform-infra observability status --target ${input.target.id}`);
  lines.push("", "Disclosure:");
  lines.push("  default view is a bounded validation table; use --full for compact JSON or --raw for backend/parser debugging.");
  return {
    ok: input.ok,
    command: "platform-infra observability validate",
    contentType: "text/plain",
    renderedText: lines.join("\n"),
  };
}

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