#!/usr/bin/env node
// SPEC: PJ2026-01060703 CI/CD branch follower runtime GitOps verify.
// Responsibility: fail publish before Argo when rendered runtime GitOps violates UniDesk overlay gates.
import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
import { createRequire } from "node:module";
import path from "node:path";

const requireFromScript = createRequire(import.meta.url);
const requireFromCwd = createRequire(path.join(process.cwd(), "package.json"));
const YAML = requireYaml();
const repoDir = process.cwd();
const overlay = readOverlay();
const runtimePath = requiredOverlayString("runtimePath");
const runtimeDir = path.resolve(repoDir, runtimePath);
const prometheusOperatorKinds = new Set(["ServiceMonitor", "PrometheusRule", "PodMonitor", "Probe"]);

if (!existsSync(runtimeDir)) {
  fail("runtime-path-missing", { runtimePath });
}

const checks = [];
if (overlay?.observability?.prometheusOperator === false) {
  checks.push("prometheus-operator-disabled");
  const refs = findPrometheusOperatorResources();
  if (refs.length > 0) fail("prometheus-operator-resource-present", { runtimePath, refs: refs.slice(0, 12), refCount: refs.length });
}
if (overlay?.codeAgentRuntime?.enabled && Object.keys(overlay.codeAgentRuntime.providerSecretNames ?? {}).length > 0) {
  checks.push("code-agent-runtime-provider-secret-env");
  verifyCloudApiProviderSecretEnv(overlay.codeAgentRuntime.providerSecretNames);
}
if (overlay?.codeAgentRuntime?.enabled && overlay.codeAgentRuntime.toolSecretNames) {
  checks.push("code-agent-runtime-tool-secret-env");
  verifyCloudApiToolSecretEnv(overlay.codeAgentRuntime.toolSecretNames);
}
if (overlay?.codeAgentRuntime?.enabled && overlay.codeAgentRuntime.kafkaEventBridge?.enabled) {
  checks.push("code-agent-runtime-kafka-event-bridge-enabled");
  verifyCloudApiKafkaEnv(overlay.codeAgentRuntime.kafkaEventBridge);
}

console.error(JSON.stringify({ event: "unidesk-runtime-gitops-verify", ok: true, runtimePath, checks }));

function verifyCloudApiProviderSecretEnv(providerSecretNames) {
  const expected = Object.fromEntries(Object.entries(providerSecretNames).map(([profile, secretName]) => {
    const profileKey = profile.toUpperCase().replace(/[^A-Z0-9]+/gu, "_");
    return [`HWLAB_CODE_AGENT_AGENTRUN_${profileKey}_SECRET_NAME`, String(secretName)];
  }));
  verifyCloudApiExpectedEnv(expected, "code-agent-runtime-provider-secret-env-invalid");
}

function verifyCloudApiToolSecretEnv(toolSecretNames) {
  verifyCloudApiExpectedEnv({
    HWLAB_CODE_AGENT_AGENTRUN_GITHUB_TOOL_SECRET_NAME: String(toolSecretNames.githubPr),
    HWLAB_CODE_AGENT_AGENTRUN_UNIDESK_SSH_TOOL_SECRET_NAME: String(toolSecretNames.unideskSsh),
  }, "code-agent-runtime-tool-secret-env-invalid");
}

function verifyCloudApiExpectedEnv(expected, invalidReason) {
  const workloads = [];
  const mismatches = [];
  for (const file of listYamlFiles(runtimeDir)) {
    for (const document of parseStructuredDocuments(readFileSync(file, "utf8"), file)) {
      const items = isKubernetesList(document) ? document.items : [document];
      for (const item of items) {
        const workloadName = String(item?.metadata?.labels?.["app.kubernetes.io/name"] ?? item?.metadata?.name ?? "");
        if (workloadName !== "hwlab-cloud-api") continue;
        for (const container of item?.spec?.template?.spec?.containers ?? []) {
          if (container?.name !== "hwlab-cloud-api") continue;
          const ref = { file: path.relative(repoDir, file), workload: workloadName, container: container.name };
          workloads.push(ref);
          const env = new Map((container.env ?? []).map((entry) => [entry?.name, entry?.value]));
          for (const [name, value] of Object.entries(expected)) {
            if (env.get(name) !== value) mismatches.push({ ...ref, name, expected: value, actual: env.get(name) ?? null });
          }
        }
      }
    }
  }
  if (workloads.length === 0) fail("code-agent-runtime-workload-missing", { runtimePath, workload: "hwlab-cloud-api" });
  if (mismatches.length > 0) fail(invalidReason, { runtimePath, mismatches: mismatches.slice(0, 12), mismatchCount: mismatches.length });
}

function verifyCloudApiKafkaEnv(kafka) {
  const expected = {
    HWLAB_KAFKA_BOOTSTRAP_SERVERS: String(kafka.bootstrapServers),
    HWLAB_KAFKA_STDIO_TOPIC: String(kafka.stdioTopic),
    HWLAB_KAFKA_AGENTRUN_EVENT_TOPIC: String(kafka.agentRunEventTopic),
    HWLAB_KAFKA_EVENT_TOPIC: String(kafka.hwlabEventTopic),
    HWLAB_KAFKA_CLIENT_ID: String(kafka.clientId),
  };
  const workloads = [];
  const mismatches = [];
  for (const file of listYamlFiles(runtimeDir)) {
    for (const document of parseStructuredDocuments(readFileSync(file, "utf8"), file)) {
      const items = isKubernetesList(document) ? document.items : [document];
      for (const item of items) {
        const workloadName = String(item?.metadata?.labels?.["app.kubernetes.io/name"] ?? item?.metadata?.name ?? "");
        if (workloadName !== "hwlab-cloud-api") continue;
        for (const container of item?.spec?.template?.spec?.containers ?? []) {
          if (container?.name !== "hwlab-cloud-api") continue;
          const ref = { file: path.relative(repoDir, file), workload: workloadName, container: container.name };
          workloads.push(ref);
          const env = new Map((container.env ?? []).map((entry) => [entry?.name, entry?.value]));
          for (const [name, value] of Object.entries(expected)) {
            if (env.get(name) !== value) mismatches.push({ ...ref, name, expected: value, actual: env.get(name) ?? null });
          }
        }
      }
    }
  }
  if (workloads.length === 0) fail("code-agent-runtime-workload-missing", { runtimePath, workload: "hwlab-cloud-api" });
  if (mismatches.length > 0) fail("code-agent-runtime-kafka-env-invalid", { runtimePath, mismatches: mismatches.slice(0, 12), mismatchCount: mismatches.length });
}

function findPrometheusOperatorResources() {
  const refs = [];
  for (const file of listYamlFiles(runtimeDir)) {
    const rel = path.relative(repoDir, file);
    for (const doc of splitYamlDocuments(readFileSync(file, "utf8"))) {
      refs.push(...prometheusOperatorResourceRefs(doc, rel));
    }
  }
  return refs;
}

function splitYamlDocuments(text) {
  return text.split(/^---[ \t]*(?:#.*)?$/mu).map((doc) => doc.trim()).filter(Boolean);
}

function prometheusOperatorResourceRefs(text, file) {
  const jsonRefs = prometheusOperatorJsonResourceRefs(text, file);
  if (jsonRefs !== null) return jsonRefs;
  const api = text.match(/^\s*apiVersion:\s*["']?(monitoring\.coreos\.com\/[^"'\s#]+)["']?\s*(?:#.*)?$/mu);
  const kind = text.match(/^\s*kind:\s*["']?([^"'\s#]+)["']?\s*(?:#.*)?$/mu);
  if (!api || !kind || !prometheusOperatorKinds.has(kind[1])) return [];
  const name = text.match(/^\s*name:\s*["']?([^"'\s#]+)["']?\s*(?:#.*)?$/mu);
  return [{ file, kind: kind[1], name: name ? name[1] : null, container: null }];
}

function prometheusOperatorJsonResourceRefs(text, file) {
  const parsed = parseJsonDocument(text);
  if (parsed === null) return null;
  const items = isKubernetesList(parsed) ? parsed.items : [parsed];
  return items.filter(isPrometheusOperatorResource).map((item) => ({
    file,
    kind: item.kind,
    name: typeof item.metadata?.name === "string" ? item.metadata.name : null,
    container: isKubernetesList(parsed) ? "List.items" : null,
  }));
}

function isPrometheusOperatorResource(value) {
  if (!value || typeof value !== "object" || Array.isArray(value)) return false;
  return typeof value.apiVersion === "string"
    && value.apiVersion.startsWith("monitoring.coreos.com/")
    && typeof value.kind === "string"
    && prometheusOperatorKinds.has(value.kind);
}

function isKubernetesList(value) {
  return value
    && typeof value === "object"
    && !Array.isArray(value)
    && value.apiVersion === "v1"
    && value.kind === "List"
    && Array.isArray(value.items);
}

function parseJsonDocument(text) {
  const trimmed = text.trim();
  if (!trimmed.startsWith("{") && !trimmed.startsWith("[")) return null;
  try {
    return JSON.parse(trimmed);
  } catch {
    return null;
  }
}

function parseStructuredDocuments(text, file) {
  const json = parseJsonDocument(text);
  if (json !== null) return [json];
  const documents = YAML.parseAllDocuments(text);
  const errors = documents.flatMap((document) => document.errors);
  if (errors.length > 0) fail("runtime-yaml-invalid", { file: path.relative(repoDir, file), errors: errors.map((error) => error.message) });
  return documents.map((document) => document.toJS()).filter((document) => document !== null);
}

function listYamlFiles(root) {
  const out = [];
  for (const name of readdirSync(root)) {
    const file = path.join(root, name);
    const stat = statSync(file);
    if (stat.isDirectory()) {
      out.push(...listYamlFiles(file));
    } else if (/\.(ya?ml)$/u.test(name)) {
      out.push(file);
    }
  }
  return out.sort();
}

function readOverlay() {
  const file = process.env.UNIDESK_RUNTIME_GITOPS_OVERLAY_FILE;
  if (file) return JSON.parse(readFileSync(file, "utf8"));
  const encoded = process.env.UNIDESK_RUNTIME_GITOPS_OVERLAY_B64;
  if (!encoded) throw new Error("UNIDESK_RUNTIME_GITOPS_OVERLAY_FILE or UNIDESK_RUNTIME_GITOPS_OVERLAY_B64 is required");
  return JSON.parse(Buffer.from(encoded, "base64").toString("utf8"));
}

function requireYaml() {
  try {
    return requireFromScript("yaml");
  } catch {
    return requireFromCwd("yaml");
  }
}

function requiredOverlayString(name) {
  const value = overlay[name];
  if (typeof value !== "string" || value.length === 0) throw new Error(`overlay.${name} is required`);
  return value;
}

function fail(reason, extra = {}) {
  console.error(JSON.stringify({ event: "unidesk-runtime-gitops-verify", ok: false, reason, ...extra }));
  process.exit(48);
}
