import { spawnSync } from "node:child_process";
import { createHash } from "node:crypto";

function decodeJson(name) {
  const raw = process.env[name] || "";
  return JSON.parse(Buffer.from(raw, "base64").toString("utf8"));
}

function bounded(value, max = 320) {
  if (typeof value !== "string" || value.length === 0) return null;
  const oneLine = value.replace(/\s+/gu, " ").trim();
  return oneLine.length <= max ? oneLine : oneLine.slice(0, max);
}

function runKubectl(args) {
  return spawnSync("kubectl", args, { encoding: "utf8", maxBuffer: 16 * 1024 * 1024 });
}

function labelsOf(value) {
  return value?.metadata?.labels && typeof value.metadata.labels === "object" ? value.metadata.labels : {};
}

function annotationsOf(value) {
  return value?.metadata?.annotations && typeof value.metadata.annotations === "object" ? value.metadata.annotations : {};
}

function jobNameOf(pod) {
  const labels = labelsOf(pod);
  const labelled = labels["batch.kubernetes.io/job-name"] || labels["job-name"];
  if (typeof labelled === "string" && labelled.length > 0) return labelled;
  const owners = Array.isArray(pod?.metadata?.ownerReferences) ? pod.metadata.ownerReferences : [];
  return owners.find((owner) => owner?.kind === "Job" && typeof owner?.name === "string")?.name || null;
}

function matchesPrefix(name, prefixes) {
  return typeof name === "string" && prefixes.some((prefix) => name === prefix || name.startsWith(`${prefix}-`));
}

function uniqueStrings(values) {
  return [...new Set(values.filter((value) => typeof value === "string" && value.length > 0))];
}

function podWaiting(pod) {
  const statuses = [
    ...(Array.isArray(pod?.status?.initContainerStatuses) ? pod.status.initContainerStatuses : []),
    ...(Array.isArray(pod?.status?.containerStatuses) ? pod.status.containerStatuses : []),
  ];
  const waiting = statuses.flatMap((status) => {
    const state = status?.state?.waiting;
    if (!state || typeof state.reason !== "string") return [];
    return [{ source: "container", name: status?.name || null, reason: state.reason, message: bounded(state.message), valuesPrinted: false }];
  });
  const conditions = Array.isArray(pod?.status?.conditions) ? pod.status.conditions : [];
  for (const condition of conditions) {
    if (condition?.status !== "False" || typeof condition.reason !== "string") continue;
    waiting.push({ source: "condition", name: condition.type || null, reason: condition.reason, message: bounded(condition.message), valuesPrinted: false });
  }
  return waiting;
}

function warningEvents(namespace, podName) {
  const fieldSelector = `involvedObject.kind=Pod,involvedObject.name=${podName},type=Warning`;
  const result = runKubectl(["-n", namespace, "get", "events", "--field-selector", fieldSelector, "-o", "json"]);
  if (result.status !== 0) {
    return { ok: false, items: [], error: bounded(result.stderr || result.stdout), valuesPrinted: false };
  }
  try {
    const payload = JSON.parse(result.stdout || "{}");
    const items = (Array.isArray(payload.items) ? payload.items : []).map((event) => ({
      source: "event",
      name: event?.metadata?.name || null,
      reason: event?.reason || null,
      message: bounded(event?.message),
      count: Number.isFinite(event?.count) ? event.count : null,
      valuesPrinted: false,
    }));
    return { ok: true, items: items.slice(0, 8), omittedCount: Math.max(0, items.length - 8), valuesPrinted: false };
  } catch (error) {
    return { ok: false, items: [], error: bounded(error instanceof Error ? error.message : String(error)), valuesPrinted: false };
  }
}

function targetKey(namespace, name) {
  return `${namespace}/${name}`;
}

function inspectTargetSecrets(targets) {
  const grouped = new Map();
  for (const target of targets) {
    const ref = target?.targetRef || {};
    const key = targetKey(ref.namespace, ref.name);
    const current = grouped.get(key) || { namespace: ref.namespace, name: ref.name, entries: [] };
    current.entries.push({ id: target.id, key: ref.key, desiredEncodedFingerprint: target.desiredEncodedFingerprint });
    grouped.set(key, current);
  }
  const results = [];
  for (const group of grouped.values()) {
    const desiredKeys = uniqueStrings(group.entries.map((entry) => entry.key));
    const conflictingKeys = desiredKeys.filter((key) => new Set(group.entries.filter((entry) => entry.key === key).map((entry) => entry.desiredEncodedFingerprint)).size !== 1);
    const query = runKubectl(["-n", group.namespace, "get", "secret", group.name, "--ignore-not-found=true", "-o", "json"]);
    if (query.status !== 0) {
      results.push({
        namespace: group.namespace,
        name: group.name,
        ids: uniqueStrings(group.entries.map((entry) => entry.id)),
        desiredKeys,
        queryOk: false,
        present: null,
        presentDesiredKeys: [],
        absentDesiredKeys: desiredKeys,
        mutations: [],
        error: bounded(query.stderr || query.stdout),
        valuesPrinted: false,
      });
      continue;
    }
    if (conflictingKeys.length > 0) {
      results.push({
        namespace: group.namespace,
        name: group.name,
        ids: uniqueStrings(group.entries.map((entry) => entry.id)),
        desiredKeys,
        queryOk: false,
        present: null,
        presentDesiredKeys: [],
        absentDesiredKeys: [],
        mutations: [],
        error: `conflicting desired encoded fingerprints for keys: ${conflictingKeys.join(",")}`,
        valuesPrinted: false,
      });
      continue;
    }
    const raw = (query.stdout || "").trim();
    let secret = null;
    try {
      secret = raw.length > 0 ? JSON.parse(raw) : null;
    } catch (error) {
      results.push({
        namespace: group.namespace,
        name: group.name,
        ids: uniqueStrings(group.entries.map((entry) => entry.id)),
        desiredKeys,
        queryOk: false,
        present: null,
        presentDesiredKeys: [],
        absentDesiredKeys: desiredKeys,
        mutations: [],
        error: bounded(error instanceof Error ? error.message : String(error)),
        valuesPrinted: false,
      });
      continue;
    }
    const data = secret?.data && typeof secret.data === "object" ? secret.data : {};
    const presentDesiredKeys = desiredKeys.filter((key) => Object.prototype.hasOwnProperty.call(data, key) && typeof data[key] === "string" && data[key].length > 0);
    const absentDesiredKeys = desiredKeys.filter((key) => !presentDesiredKeys.includes(key));
    const items = group.entries.map((entry) => {
      const raw = typeof data[entry.key] === "string" && data[entry.key].length > 0 ? data[entry.key] : null;
      const runtimeEncodedFingerprint = raw === null ? null : `sha256:${createHash("sha256").update(raw).digest("hex")}`;
      const changeKind = secret === null
        ? "create-secret"
        : raw === null
          ? "add-key"
          : runtimeEncodedFingerprint === entry.desiredEncodedFingerprint
            ? "no-op"
            : "change-key";
      return {
        id: entry.id,
        key: entry.key,
        desiredEncodedFingerprint: entry.desiredEncodedFingerprint,
        runtimeEncodedFingerprint,
        keyPresent: raw !== null,
        changeKind,
        valuesPrinted: false,
      };
    });
    const mutations = items.filter((item) => item.changeKind !== "no-op");
    results.push({
      namespace: group.namespace,
      name: group.name,
      ids: uniqueStrings(group.entries.map((entry) => entry.id)),
      desiredKeys,
      queryOk: true,
      present: secret !== null,
      presentDesiredKeys,
      absentDesiredKeys,
      items,
      mutations,
      mutationCount: mutations.length,
      noOp: mutations.length === 0,
      mutationWouldCreateSecret: secret === null,
      mutationWouldAddKeys: uniqueStrings(mutations.filter((item) => item.changeKind === "add-key").map((item) => item.key)),
      mutationWouldChangeKeys: uniqueStrings(mutations.filter((item) => item.changeKind === "change-key").map((item) => item.key)),
      valuesPrinted: false,
    });
  }
  return results;
}

function selectedTarget(targetsBySecret, namespace, name) {
  return targetsBySecret.get(targetKey(namespace, name)) || null;
}

function affectedKeys(target, requestedKeys) {
  if (target === null || target.queryOk !== true) return [];
  const mutationKeys = uniqueStrings(target.mutations.map((item) => item.key));
  if (requestedKeys.length === 0) return mutationKeys;
  return requestedKeys.filter((key) => mutationKeys.includes(key));
}

function podSecretDependencies(pod, targetsBySecret) {
  const namespace = pod?.metadata?.namespace || process.env.NAMESPACE;
  const dependencies = [];
  const add = (source, name, optional, requestedKeys) => {
    if (typeof name !== "string" || name.length === 0) return;
    const target = selectedTarget(targetsBySecret, namespace, name);
    const keys = affectedKeys(target, requestedKeys);
    if (target === null || keys.length === 0) return;
    const mutations = target.mutations.filter((item) => keys.includes(item.key));
    dependencies.push({
      source,
      namespace,
      name,
      optional: optional === true,
      requiredKeys: requestedKeys,
      affectedDesiredKeys: keys,
      mutationKinds: uniqueStrings(mutations.map((item) => item.changeKind)),
      currentSecretPresent: target.present,
      reason: "nonterminal-runner-provider-secret-mutation",
      valuesPrinted: false,
    });
  };
  for (const volume of Array.isArray(pod?.spec?.volumes) ? pod.spec.volumes : []) {
    if (volume?.secret) {
      const items = Array.isArray(volume.secret.items) ? volume.secret.items.map((item) => item?.key) : [];
      add("volume-secret", volume.secret.secretName, volume.secret.optional, uniqueStrings(items));
    }
    const sources = Array.isArray(volume?.projected?.sources) ? volume.projected.sources : [];
    for (const source of sources) {
      if (!source?.secret) continue;
      const items = Array.isArray(source.secret.items) ? source.secret.items.map((item) => item?.key) : [];
      add("projected-secret", source.secret.name, source.secret.optional, uniqueStrings(items));
    }
  }
  const containers = [
    ...(Array.isArray(pod?.spec?.initContainers) ? pod.spec.initContainers : []),
    ...(Array.isArray(pod?.spec?.containers) ? pod.spec.containers : []),
    ...(Array.isArray(pod?.spec?.ephemeralContainers) ? pod.spec.ephemeralContainers : []),
  ];
  for (const container of containers) {
    for (const env of Array.isArray(container?.env) ? container.env : []) {
      const ref = env?.valueFrom?.secretKeyRef;
      if (ref) add("env-secret-key-ref", ref.name, ref.optional, uniqueStrings([ref.key]));
    }
    for (const envFrom of Array.isArray(container?.envFrom) ? container.envFrom : []) {
      const ref = envFrom?.secretRef;
      if (ref) add("env-from-secret-ref", ref.name, ref.optional, []);
    }
  }
  return dependencies;
}

const namespace = process.env.NAMESPACE || "";
const targets = decodeJson("TARGETS_JSON_B64");
const matchLabels = decodeJson("MATCH_LABELS_JSON_B64");
const jobNamePrefixes = decodeJson("JOB_NAME_PREFIXES_JSON_B64");
const selector = Object.entries(matchLabels).map(([key, value]) => `${key}=${value}`).join(",");
const podArgs = ["-n", namespace, "get", "pods"];
if (selector.length > 0) podArgs.push("-l", selector);
podArgs.push("-o", "json");
const podQuery = runKubectl(podArgs);
let podPayload = { items: [] };
let podQueryError = null;
if (podQuery.status === 0) {
  try {
    podPayload = JSON.parse(podQuery.stdout || "{}");
  } catch (error) {
    podQueryError = bounded(error instanceof Error ? error.message : String(error));
  }
} else {
  podQueryError = bounded(podQuery.stderr || podQuery.stdout);
}

const targetSecrets = inspectTargetSecrets(targets);
const targetsBySecret = new Map(targetSecrets.map((target) => [targetKey(target.namespace, target.name), target]));
const allPods = Array.isArray(podPayload.items) ? podPayload.items : [];
const runnerPods = allPods.filter((pod) => {
  const jobName = jobNameOf(pod);
  return matchesPrefix(jobName, jobNamePrefixes);
});
const nonTerminalPods = runnerPods.filter((pod) => !["Succeeded", "Failed"].includes(pod?.status?.phase) && typeof pod?.metadata?.deletionTimestamp !== "string");
const activationRisks = [];
for (const pod of nonTerminalPods) {
  const dependencies = podSecretDependencies(pod, targetsBySecret);
  if (dependencies.length === 0) continue;
  const events = warningEvents(namespace, pod?.metadata?.name || "");
  const waiting = [...podWaiting(pod), ...events.items];
  const annotations = annotationsOf(pod);
  activationRisks.push({
    pod: pod?.metadata?.name || null,
    jobName: jobNameOf(pod),
    phase: pod?.status?.phase || null,
    createdAt: pod?.metadata?.creationTimestamp || null,
    runId: annotations["agentrun.pikastech.local/run-id"] || null,
    commandId: annotations["agentrun.pikastech.local/command-id"] || null,
    runnerJobId: annotations["agentrun.pikastech.local/runner-job-id"] || null,
    runnerId: annotations["agentrun.pikastech.local/runner-id"] || null,
    dependencies,
    waiting: waiting.slice(0, 8),
    waitingOmittedCount: Math.max(0, waiting.length - 8),
    waitingReasons: uniqueStrings(waiting.map((item) => item.reason)).slice(0, 8),
    warningEventsAvailable: events.ok,
    classification: "nonterminal-runner-required-provider-secret-missing",
    valuesPrinted: false,
  });
}

const targetSecretObservationComplete = targets.length > 0 && targetSecrets.every((target) => target.queryOk === true);
const runnerObservationComplete = podQuery.status === 0 && podQueryError === null;
const allObservationsComplete = targetSecretObservationComplete && runnerObservationComplete;
const providerSecretMutationCount = targetSecrets.reduce((total, target) => total + Number(target.mutationCount || 0), 0);
const providerSecretDataNoOp = targetSecretObservationComplete && providerSecretMutationCount === 0;
const providerSecretMutationAuthorized = false;
const ok = providerSecretDataNoOp;
console.log(JSON.stringify({
  ok,
  status: ok ? "ready-no-op" : "blocked",
  reason: !targetSecretObservationComplete
    ? "provider-secret-target-observation-incomplete"
    : providerSecretMutationCount > 0
      ? "provider-secret-activation-fence-unavailable"
      : null,
  observedAt: new Date().toISOString(),
  target: { namespace, labelSelector: selector || null, jobNamePrefixes, valuesPrinted: false },
  targetSecretCount: targetSecrets.length,
  targetSecrets,
  podObservation: {
    queryOk: runnerObservationComplete,
    error: podQueryError,
    selectedPodCount: allPods.length,
    runnerPodCount: runnerPods.length,
    nonTerminalRunnerPodCount: nonTerminalPods.length,
    valuesPrinted: false,
  },
  activationRiskCount: activationRisks.length,
  activationRisks: activationRisks.slice(0, 8),
  activationRiskOmittedCount: Math.max(0, activationRisks.length - 8),
  requiresManagerFence: providerSecretMutationCount > 0,
  targetSecretObservationComplete,
  runnerObservationComplete,
  allObservationsComplete,
  providerSecretMutationCount,
  providerSecretDataNoOp,
  providerSecretMutationAuthorized,
  activationAuthority: {
    owner: "agentrun-manager-provider-activation-fence",
    atomicFenceAvailable: false,
    admissionFenceAvailable: false,
    secretCasAvailable: false,
    noOpOnly: true,
    diagnosticRunnerObservationOnly: true,
    valuesPrinted: false,
  },
  queryComplete: targetSecretObservationComplete,
  mutation: false,
  runtimeSecretValuesDecoded: false,
  valuesPrinted: false,
}));
