// SPEC: PJ2026-01060307 控制面模块化 draft-2026-06-25-p0. summary 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:3031-4400 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 { ImageSpec, ObservabilityConfig, ObservabilityTarget } from "./types";
import { status } from "./actions";
import { asRecord, configLabel } from "./types";

export function configSummary(observability: ObservabilityConfig, target: ObservabilityTarget): Record<string, unknown> {
  return {
    configPath: configLabel,
    spec: observability.metadata.spec,
    target: targetSummary(target),
    images: {
      collector: imageReference(observability.images.collector),
      traceBackend: imageReference(observability.images.tempo),
    },
    collector: observability.collector,
    traceBackend: observability.traceBackend,
    sampling: observability.sampling,
    serviceConnections: observability.instrumentation.serviceConnections.map((item) => ({
      serviceName: item.serviceName,
      owningRepo: item.owningRepo,
      resolved: {
        targetNode: item.targetNode,
        lane: item.lane,
        namespace: item.namespace,
      },
      configRefs: item.configRefGraph,
      requiredSpans: item.requiredSpans,
    })),
  };
}

export function targetSummary(target: ObservabilityTarget): Record<string, unknown> {
  return {
    id: target.id,
    route: target.route,
    namespace: target.namespace,
    role: target.role,
    createNamespace: target.createNamespace,
  };
}

export function policyChecks(yaml: string, target: ObservabilityTarget): Array<Record<string, unknown>> {
  return [
    { name: "yaml-source-of-truth", ok: true, detail: "All concrete images, routes, namespace, ports, retention and sampling values are read from config/platform-infra/observability.yaml." },
    { name: "clusterip-only", ok: !/^\s*type:\s*(NodePort|LoadBalancer)\s*$/mu.test(yaml), detail: "Collector and trace backend stay ClusterIP-only." },
    { name: "no-ingress", ok: !/^\s*kind:\s*Ingress\s*$/mu.test(yaml), detail: "No public ingress is rendered for the first tracing backend." },
    { name: "no-host-network", ok: !/^\s*hostNetwork:\s*true\s*$/mu.test(yaml), detail: "Pods must not use host network." },
    { name: "allow-all-network-policy", ok: yaml.includes("kind: NetworkPolicy") && yaml.includes("name: allow-all") && yaml.includes(`namespace: ${target.namespace}`), detail: `NetworkPolicy/allow-all is rendered in ${target.namespace}.` },
  ];
}

export function statusSummary(payload: Record<string, unknown>): Record<string, unknown> {
  const sections = asRecord(payload.sections, "status.sections");
  const deployments = objectList(sectionJson(sections, "deployments"));
  const services = objectList(sectionJson(sections, "services"));
  const pods = objectList(sectionJson(sections, "pods"));
  const events = objectList(sectionJson(sections, "events"));
  const podEvents = latestPodEvents(events);
  const probes = Array.isArray(payload.probes) ? payload.probes as Array<Record<string, unknown>> : [];
  const readyDeployments = deployments.map((item) => deploymentSummary(item));
  return {
    ready: payload.ok === true,
    namespace: payload.namespace,
    deployments: readyDeployments,
    services: services.map((item) => metadataName(item)),
    pods: pods.map((item) => podSummary(item, podEvents)),
    probes: probes.map((item) => ({
      name: item.name,
      ok: item.ok === true,
      service: item.service,
      path: item.path,
      stderrTail: item.ok === true ? "" : item.stderrTail,
    })),
  };
}

export function compactStatus(payload: Record<string, unknown> | null, full: boolean): Record<string, unknown> | null {
  if (payload === null) return null;
  if (full) return redactSensitiveUnknown(payload) as Record<string, unknown>;
  return statusSummary(payload);
}

export function sectionJson(sections: Record<string, unknown>, name: string): unknown {
  const section = asRecord(sections[name], `sections.${name}`);
  return section.json;
}

export function objectList(value: unknown): Record<string, unknown>[] {
  if (typeof value !== "object" || value === null) return [];
  const items = (value as Record<string, unknown>).items;
  if (!Array.isArray(items)) return [];
  return items.filter((item): item is Record<string, unknown> => typeof item === "object" && item !== null && !Array.isArray(item));
}

export function deploymentSummary(item: Record<string, unknown>): Record<string, unknown> {
  const spec = item.spec as Record<string, unknown> | undefined;
  const status = item.status as Record<string, unknown> | undefined;
  const replicas = typeof spec?.replicas === "number" ? spec.replicas : null;
  const available = typeof status?.availableReplicas === "number" ? status.availableReplicas : 0;
  return {
    name: metadataName(item),
    replicas,
    availableReplicas: available,
    ready: replicas !== null && available >= replicas,
  };
}

export function podSummary(item: Record<string, unknown>, podEvents: Map<string, Record<string, unknown>>): Record<string, unknown> {
  const status = item.status as Record<string, unknown> | undefined;
  const waiting = firstWaitingContainer(status);
  const scheduled = conditionSummary(status, "PodScheduled");
  const ready = conditionSummary(status, "Ready");
  const name = metadataName(item);
  const showEvent = status?.phase !== "Running" || waiting !== null || scheduled !== null || ready !== null;
  const latestEvent = showEvent && name !== null ? podEvents.get(name) ?? null : null;
  return {
    name,
    phase: status?.phase ?? null,
    reason: waiting?.reason ?? scheduled?.reason ?? ready?.reason ?? null,
    message: waiting?.message ?? scheduled?.message ?? ready?.message ?? null,
    latestEvent: latestEvent === null ? null : {
      reason: stringValue(latestEvent.reason),
      message: stringValue(latestEvent.message),
      type: stringValue(latestEvent.type),
      count: typeof latestEvent.count === "number" ? latestEvent.count : null,
      timestamp: eventTimestamp(latestEvent),
    },
  };
}

export function latestPodEvents(events: Record<string, unknown>[]): Map<string, Record<string, unknown>> {
  const byPod = new Map<string, Record<string, unknown>>();
  for (const item of events) {
    const involved = item.involvedObject;
    if (typeof involved !== "object" || involved === null || Array.isArray(involved)) continue;
    const podName = (involved as Record<string, unknown>).name;
    const kind = (involved as Record<string, unknown>).kind;
    if (kind !== "Pod" || typeof podName !== "string") continue;
    const current = byPod.get(podName);
    if (current === undefined || eventTimestamp(item) > eventTimestamp(current)) byPod.set(podName, item);
  }
  return byPod;
}

export function eventTimestamp(item: Record<string, unknown>): string | null {
  const candidate = stringValue(item.eventTime)
    ?? stringValue(item.lastTimestamp)
    ?? stringValue(item.deprecatedLastTimestamp)
    ?? (typeof item.metadata === "object" && item.metadata !== null && !Array.isArray(item.metadata)
      ? stringValue((item.metadata as Record<string, unknown>).creationTimestamp)
      : null);
  return candidate;
}

export function stringValue(value: unknown): string | null {
  return typeof value === "string" && value.length > 0 ? value : null;
}

export function firstWaitingContainer(status: Record<string, unknown> | undefined): { reason: string | null; message: string | null } | null {
  const statuses = Array.isArray(status?.containerStatuses) ? status.containerStatuses : [];
  for (const item of statuses) {
    if (typeof item !== "object" || item === null || Array.isArray(item)) continue;
    const state = (item as Record<string, unknown>).state;
    if (typeof state !== "object" || state === null || Array.isArray(state)) continue;
    const waiting = (state as Record<string, unknown>).waiting;
    if (typeof waiting !== "object" || waiting === null || Array.isArray(waiting)) continue;
    const record = waiting as Record<string, unknown>;
    return {
      reason: typeof record.reason === "string" ? record.reason : null,
      message: typeof record.message === "string" ? record.message : null,
    };
  }
  return null;
}

export function conditionSummary(status: Record<string, unknown> | undefined, type: string): { reason: string | null; message: string | null } | null {
  const conditions = Array.isArray(status?.conditions) ? status.conditions : [];
  for (const item of conditions) {
    if (typeof item !== "object" || item === null || Array.isArray(item)) continue;
    const record = item as Record<string, unknown>;
    if (record.type !== type || record.status === "True") continue;
    return {
      reason: typeof record.reason === "string" ? record.reason : null,
      message: typeof record.message === "string" ? record.message : null,
    };
  }
  return null;
}

export function metadataName(item: Record<string, unknown>): string | null {
  const metadata = item.metadata as Record<string, unknown> | undefined;
  return typeof metadata?.name === "string" ? metadata.name : null;
}

export function manifestObjectSummary(yaml: string): Array<Record<string, string>> {
  const docs = yaml.split(/^---$/mu);
  return docs.map((doc) => {
    const kind = doc.match(/^\s*kind:\s*(.+)$/mu)?.[1]?.trim() ?? "unknown";
    const name = doc.match(/^\s*name:\s*(.+)$/mu)?.[1]?.trim() ?? "unknown";
    return { kind, name };
  });
}

export function imageReference(image: ImageSpec): string {
  return `${image.repository}:${image.tag}`;
}

export function indent(value: string, spaces: number): string {
  const prefix = " ".repeat(spaces);
  return value.split("\n").map((line) => `${prefix}${line}`).join("\n");
}
