// SPEC: PJ2026-01060307 控制面模块化 draft-2026-06-25-p0. retirement module for scripts/src/hwlab-legacy-runtime.ts.

// Moved mechanically from scripts/src/hwlab-legacy-runtime.ts:5525-5845 for #903.

import { chmodSync, existsSync, mkdirSync, readFileSync, rmSync, statSync, writeFileSync } from "node:fs";
import { dirname, join } from "node:path";
import { createHash, randomBytes } from "node:crypto";
import { repoRoot, rootPath, type Config } from "../config";
import { runCommand } from "../command";
import { cancelJob, listJobs, readJob, startJob } from "../jobs";
import { hwlabRequiredNoProxyEntries, hwlabRuntimeLaneConfigPath, hwlabRuntimeLaneIds, hwlabRuntimeLaneSpec, isHwlabRuntimeLane, type HwlabRuntimeLane, type HwlabRuntimeLaneSpec } from "../hwlab-node-lanes";

import type { LegacyRuntimeLegacyRetirementOptions } from "./types";
import { k8sMetadataName, parseSectionJsonArray } from "./observability";
import { statusText } from "./pr-monitor";
import { compactCommandResult, legacyRuntimeK3s, isCommandSuccess, nested, record, shellQuote } from "./remote";
import { ARGO_NAMESPACE, DEV_APP, DEV_NAMESPACE, LEGACY_RUNTIME_PROVIDER, PROD_APP, PROD_NAMESPACE, DEFAULT_RUNTIME_LANE_APP, DEFAULT_RUNTIME_LANE_RUNTIME_NAMESPACE } from "./types";
import { parseShellSections, shellSectionOk } from "./runtime-status";

export function legacyLegacyRuntimeRetirementStatePath(): string {
  return rootPath(".state", "hwlab-g14", "legacy-g14-retirement.json");
}

export function legacyLegacyRuntimeRetirementApplications(): string[] {
  return [DEV_APP, PROD_APP];
}

export function legacyLegacyRuntimeRetirementNamespaces(): string[] {
  return [DEV_NAMESPACE, PROD_NAMESPACE];
}

export function protectedLegacyRuntimeRuntimeApplications(): string[] {
  return [DEFAULT_RUNTIME_LANE_APP, hwlabRuntimeLaneSpec("v03").app];
}

export function protectedLegacyRuntimeRuntimeNamespaces(): string[] {
  return [DEFAULT_RUNTIME_LANE_RUNTIME_NAMESPACE, hwlabRuntimeLaneSpec("v03").runtimeNamespace];
}

export function readLegacyLegacyRuntimeRetirementMarker(): Record<string, unknown> | null {
  const path = legacyLegacyRuntimeRetirementStatePath();
  if (!existsSync(path)) return null;
  try {
    return record(JSON.parse(readFileSync(path, "utf8")) as unknown);
  } catch (error) {
    return { status: "unreadable", path, error: error instanceof Error ? error.message : String(error) };
  }
}

export function writeLegacyLegacyRuntimeRetirementMarker(status: string, reason: string, details: Record<string, unknown> = {}): Record<string, unknown> {
  const path = legacyLegacyRuntimeRetirementStatePath();
  mkdirSync(dirname(path), { recursive: true });
  const previous = readLegacyLegacyRuntimeRetirementMarker();
  const marker = {
    status,
    reason,
    requestedAt: typeof previous?.requestedAt === "string" ? previous.requestedAt : new Date().toISOString(),
    updatedAt: new Date().toISOString(),
    legacyApplications: legacyLegacyRuntimeRetirementApplications(),
    legacyNamespaces: legacyLegacyRuntimeRetirementNamespaces(),
    protectedApplications: protectedLegacyRuntimeRuntimeApplications(),
    protectedNamespaces: protectedLegacyRuntimeRuntimeNamespaces(),
    ...details,
  };
  writeFileSync(path, `${JSON.stringify(marker, null, 2)}\n`, "utf8");
  return { ...marker, path };
}

export function legacyLegacyRuntimeRetirementBlocksMonitor(): Record<string, unknown> | null {
  const marker = readLegacyLegacyRuntimeRetirementMarker();
  if (marker === null) {
    return {
      status: "retired-by-contract",
      path: legacyLegacyRuntimeRetirementStatePath(),
      reason: "legacy G14 DEV/PROD monitor is retired; inspect retirement status for live cluster evidence",
      next: { status: "bun scripts/cli.ts hwlab g14 retirement status", defaultRuntimeLaneMonitor: "bun scripts/cli.ts hwlab g14 monitor-prs --lane v02", v03Monitor: "bun scripts/cli.ts hwlab g14 monitor-prs --lane v03" },
    };
  }
  const status = String(marker.status ?? "");
  return status === "retiring" || status === "retired" || status === "failed" || status === "unreadable" ? marker : null;
}

export function k8sMetadataNamespace(item: Record<string, unknown>): string | null {
  const namespace = record(item.metadata).namespace;
  return typeof namespace === "string" && namespace.length > 0 ? namespace : null;
}

export function applicationSummary(item: Record<string, unknown>): Record<string, unknown> {
  return {
    name: k8sMetadataName(item),
    namespace: k8sMetadataNamespace(item) ?? ARGO_NAMESPACE,
    deletionTimestamp: record(item.metadata).deletionTimestamp ?? null,
    sync: nested(item, ["status", "sync", "status"]) ?? null,
    health: nested(item, ["status", "health", "status"]) ?? null,
    revision: nested(item, ["status", "sync", "revision"]) ?? null,
  };
}

export function namespaceSummary(item: Record<string, unknown>): Record<string, unknown> {
  return {
    name: k8sMetadataName(item),
    phase: nested(item, ["status", "phase"]) ?? null,
    deletionTimestamp: record(item.metadata).deletionTimestamp ?? null,
  };
}

export function parseNamespacedResourcePreview(text: string): Record<string, unknown>[] {
  const groups: Record<string, string[]> = {};
  let current = "";
  for (const line of text.split(/\r?\n/u)) {
    const trimmed = line.trim();
    if (trimmed.length === 0) continue;
    const namespaceMatch = /^__namespace\t(.+)$/u.exec(trimmed);
    if (namespaceMatch !== null) {
      current = namespaceMatch[1] ?? "";
      if (current.length > 0) groups[current] = [];
      continue;
    }
    if (current.length > 0) groups[current]?.push(trimmed);
  }
  return Object.entries(groups).map(([namespace, items]) => ({
    namespace,
    count: items.length,
    preview: items.slice(0, 40),
    truncated: items.length > 40,
  }));
}

export function legacyLegacyRuntimeRetirementStatusScript(): string {
  const legacyApps = legacyLegacyRuntimeRetirementApplications().map(shellQuote).join(" ");
  const legacyNamespaces = legacyLegacyRuntimeRetirementNamespaces().map(shellQuote).join(" ");
  const protectedApps = protectedLegacyRuntimeRuntimeApplications().map(shellQuote).join(" ");
  const protectedNamespaces = protectedLegacyRuntimeRuntimeNamespaces().map(shellQuote).join(" ");
  return [
    "set +e",
    "section() {",
    "  name=\"$1\"",
    "  shift",
    "  printf '__UNIDESK_SECTION_BEGIN__ %s\\n' \"$name\"",
    "  \"$@\"",
    "  code=$?",
    "  printf '\\n__UNIDESK_SECTION_END__ %s exit=%s\\n' \"$name\" \"$code\"",
    "}",
    `section legacyApplications kubectl get application -n ${shellQuote(ARGO_NAMESPACE)} ${legacyApps} --ignore-not-found=true -o json`,
    `section legacyNamespaces kubectl get namespace ${legacyNamespaces} --ignore-not-found=true -o json`,
    `section protectedApplications kubectl get application -n ${shellQuote(ARGO_NAMESPACE)} ${protectedApps} --ignore-not-found=true -o json`,
    `section protectedNamespaces kubectl get namespace ${protectedNamespaces} --ignore-not-found=true -o json`,
    "section legacyResources sh -lc " + shellQuote([
      `for ns in ${legacyNamespaces}; do`,
      "  printf '__namespace\\t%s\\n' \"$ns\"",
      "  kubectl get deploy,statefulset,svc,pod,pvc,ingress,configmap,secret -n \"$ns\" -o name --ignore-not-found=true 2>/dev/null | sed -n '1,160p'",
      "done",
    ].join("\n")),
  ].join("\n");
}

export function legacyLegacyRuntimeRetirementStatus(): Record<string, unknown> {
  const result = legacyRuntimeK3s(["sh", "--", legacyLegacyRuntimeRetirementStatusScript()], 60_000);
  const sections = parseShellSections(statusText(result));
  const legacyApplications = parseSectionJsonArray(sections.legacyApplications).map(applicationSummary);
  const legacyNamespaces = parseSectionJsonArray(sections.legacyNamespaces).map(namespaceSummary);
  const protectedApplications = parseSectionJsonArray(sections.protectedApplications).map(applicationSummary);
  const protectedNamespaces = parseSectionJsonArray(sections.protectedNamespaces).map(namespaceSummary);
  const terminating = legacyApplications.some((item) => item.deletionTimestamp !== null)
    || legacyNamespaces.some((item) => item.deletionTimestamp !== null || item.phase === "Terminating");
  const retired = legacyApplications.length === 0 && legacyNamespaces.length === 0;
  const retirementState = retired ? "retired" : terminating ? "terminating" : "active";
  const protectedAppNames = new Set(protectedApplications.map((item) => item.name).filter((name): name is string => typeof name === "string"));
  const protectedNamespaceNames = new Set(protectedNamespaces.map((item) => item.name).filter((name): name is string => typeof name === "string"));
  const missingProtectedApplications = protectedLegacyRuntimeRuntimeApplications().filter((name) => !protectedAppNames.has(name));
  const missingProtectedNamespaces = protectedLegacyRuntimeRuntimeNamespaces().filter((name) => !protectedNamespaceNames.has(name));
  const sectionsOk =
    shellSectionOk(sections.legacyApplications) &&
    shellSectionOk(sections.legacyNamespaces) &&
    shellSectionOk(sections.protectedApplications) &&
    shellSectionOk(sections.protectedNamespaces) &&
    shellSectionOk(sections.legacyResources);
  return {
    ok: isCommandSuccess(result) && sectionsOk,
    command: "hwlab g14 retirement status",
    provider: LEGACY_RUNTIME_PROVIDER,
    argoNamespace: ARGO_NAMESPACE,
    retirementState,
    retired,
    legacy: {
      applications: legacyApplications,
      namespaces: legacyNamespaces,
      resources: parseNamespacedResourcePreview(String(sections.legacyResources?.stdout ?? "")),
    },
    protected: {
      applications: protectedApplications,
      namespaces: protectedNamespaces,
      missingApplications: missingProtectedApplications,
      missingNamespaces: missingProtectedNamespaces,
      ok: missingProtectedApplications.length === 0 && missingProtectedNamespaces.length === 0,
    },
    marker: readLegacyLegacyRuntimeRetirementMarker(),
    result: compactCommandResult(result),
    next: retired
      ? { verifyDefaultRuntimeLane: "bun scripts/cli.ts hwlab g14 control-plane status --lane v02", verifyV03: "bun scripts/cli.ts hwlab nodes control-plane status --node G14 --lane v03" }
      : { plan: "bun scripts/cli.ts hwlab g14 retirement plan", execute: "bun scripts/cli.ts hwlab g14 retirement execute --confirm" },
  };
}

export function legacyLegacyRuntimeRetirementPlan(status = legacyLegacyRuntimeRetirementStatus()): Record<string, unknown> {
  return {
    ok: status.ok === true,
    command: "hwlab g14 retirement plan",
    mode: "dry-run",
    mutation: false,
    reason: "retire legacy G14 DEV/PROD runtime and support surface without touching v0.2/v0.3",
    destructiveTargets: {
      applications: legacyLegacyRuntimeRetirementApplications().map((name) => ({ apiVersion: "argoproj.io/v1alpha1", kind: "Application", namespace: ARGO_NAMESPACE, name })),
      namespaces: legacyLegacyRuntimeRetirementNamespaces().map((name) => ({ apiVersion: "v1", kind: "Namespace", name })),
    },
    protectedTargets: {
      applications: protectedLegacyRuntimeRuntimeApplications().map((name) => ({ apiVersion: "argoproj.io/v1alpha1", kind: "Application", namespace: ARGO_NAMESPACE, name })),
      namespaces: protectedLegacyRuntimeRuntimeNamespaces().map((name) => ({ apiVersion: "v1", kind: "Namespace", name })),
    },
    localSupportActions: [
      { action: "write-retirement-marker", path: legacyLegacyRuntimeRetirementStatePath() },
      { action: "cancel-active-local-job", jobName: "hwlab_g14_pr_monitor" },
      { action: "block-new-monitor-starts", command: "bun scripts/cli.ts hwlab g14 monitor-prs --lane g14" },
    ],
    status,
    next: { execute: "bun scripts/cli.ts hwlab g14 retirement execute --confirm" },
  };
}

export function legacyLegacyRuntimeRecordRolloutRetired(): Record<string, unknown> {
  return {
    ok: false,
    command: "hwlab g14 record-rollout",
    phase: "retired",
    degradedReason: "legacy-g14-dev-prod-retired",
    message: "Legacy G14 DEV rollout recording is retired. Use active runtime lane closeout/status evidence instead.",
    retirement: readLegacyLegacyRuntimeRetirementMarker(),
    next: {
      retirementStatus: "bun scripts/cli.ts hwlab g14 retirement status",
      defaultRuntimeLaneStatus: "bun scripts/cli.ts hwlab g14 control-plane status --lane v02",
      v03Status: "bun scripts/cli.ts hwlab nodes control-plane status --node G14 --lane v03",
    },
  };
}

export function cancelLegacyLegacyRuntimeMonitorJobs(dryRun: boolean): Record<string, unknown> {
  const candidates = listJobs()
    .filter((job) => job.name === "hwlab_g14_pr_monitor" && (job.status === "queued" || job.status === "running"))
    .map((job) => ({ id: job.id, name: job.name, status: job.status, createdAt: job.createdAt, runnerPid: job.runnerPid ?? null }));
  if (dryRun) return { ok: true, dryRun: true, candidates, actions: candidates.map((job) => ({ action: "would-cancel", ...job })) };
  const actions = candidates.map((job) => cancelJob(job.id));
  return { ok: actions.every((action) => record(action).ok !== false), dryRun: false, candidates, actions };
}

export function legacyLegacyRuntimeRetirementDeleteScript(): string {
  const legacyApps = legacyLegacyRuntimeRetirementApplications();
  const legacyNamespaces = legacyLegacyRuntimeRetirementNamespaces();
  return [
    "set +e",
    "overall=0",
    "printf '__UNIDESK_SECTION_BEGIN__ deleteApplications\\n'",
    ...legacyApps.map((app) => [
      `kubectl delete application -n ${shellQuote(ARGO_NAMESPACE)} ${shellQuote(app)} --ignore-not-found=true --wait=false`,
      "code=$?",
      "[ \"$code\" -eq 0 ] || overall=1",
      `printf 'application\\t%s\\texit=%s\\n' ${shellQuote(app)} "$code"`,
    ].join("\n")),
    "printf '__UNIDESK_SECTION_END__ deleteApplications exit=0\\n'",
    "printf '__UNIDESK_SECTION_BEGIN__ deleteNamespaces\\n'",
    ...legacyNamespaces.map((namespace) => [
      `kubectl delete namespace ${shellQuote(namespace)} --ignore-not-found=true --wait=false`,
      "code=$?",
      "[ \"$code\" -eq 0 ] || overall=1",
      `printf 'namespace\\t%s\\texit=%s\\n' ${shellQuote(namespace)} "$code"`,
    ].join("\n")),
    "printf '__UNIDESK_SECTION_END__ deleteNamespaces exit=0\\n'",
    "exit \"$overall\"",
  ].join("\n");
}

export function waitForLegacyLegacyRuntimeRetirement(timeoutSeconds: number): Record<string, unknown> {
  const startedAtMs = Date.now();
  let lastStatus: Record<string, unknown> = {};
  let attempts = 0;
  while (Date.now() - startedAtMs <= timeoutSeconds * 1000) {
    attempts += 1;
    lastStatus = legacyLegacyRuntimeRetirementStatus();
    if (lastStatus.retired === true) {
      return { ok: true, attempts, elapsedMs: Date.now() - startedAtMs, status: lastStatus };
    }
    const wait = runCommand(["sleep", "5"], repoRoot, { timeoutMs: 7_000 });
    if (wait.exitCode !== 0) break;
  }
  return { ok: false, attempts, elapsedMs: Date.now() - startedAtMs, status: lastStatus, degradedReason: "legacy-g14-retirement-not-complete-before-timeout" };
}

export function runLegacyLegacyRuntimeRetirement(options: LegacyRuntimeLegacyRetirementOptions): Record<string, unknown> {
  const before = legacyLegacyRuntimeRetirementStatus();
  if (options.action === "status") return before;
  if (options.action === "plan" || options.dryRun) return { ...legacyLegacyRuntimeRetirementPlan(before), command: `hwlab g14 retirement ${options.action}`, reason: options.reason };

  const markerBeforeDelete = writeLegacyLegacyRuntimeRetirementMarker("retiring", options.reason, { command: "hwlab g14 retirement execute --confirm" });
  const localJobs = cancelLegacyLegacyRuntimeMonitorJobs(false);
  const deleteResult = legacyRuntimeK3s(["sh", "--", legacyLegacyRuntimeRetirementDeleteScript()], Math.max(60_000, options.timeoutSeconds * 1000));
  const deleteSections = parseShellSections(statusText(deleteResult));
  const afterDelete = legacyLegacyRuntimeRetirementStatus();
  const wait = options.wait ? waitForLegacyLegacyRuntimeRetirement(options.timeoutSeconds) : null;
  const finalStatus = wait === null ? afterDelete : record(wait.status);
  const liveMutationOk = isCommandSuccess(deleteResult);
  const finalRetired = finalStatus.retired === true;
  const marker = writeLegacyLegacyRuntimeRetirementMarker(finalRetired ? "retired" : liveMutationOk ? "retiring" : "failed", options.reason, {
    command: "hwlab g14 retirement execute --confirm",
    deleteExitCode: deleteResult.exitCode,
    finalRetirementState: finalStatus.retirementState ?? null,
  });
  return {
    ok: liveMutationOk && (options.wait ? finalRetired : finalStatus.retirementState !== "active"),
    command: "hwlab g14 retirement execute",
    mode: "confirmed-retirement",
    mutation: true,
    reason: options.reason,
    before,
    markerBeforeDelete,
    localJobs,
    delete: {
      ok: liveMutationOk,
      applications: String(deleteSections.deleteApplications?.stdout ?? "").split(/\r?\n/u).filter(Boolean),
      namespaces: String(deleteSections.deleteNamespaces?.stdout ?? "").split(/\r?\n/u).filter(Boolean),
      result: compactCommandResult(deleteResult),
    },
    afterDelete,
    wait,
    finalStatus,
    marker,
    next: finalRetired
      ? { verifyDefaultRuntimeLane: "bun scripts/cli.ts hwlab g14 control-plane status --lane v02", verifyV03: "bun scripts/cli.ts hwlab nodes control-plane status --node G14 --lane v03" }
      : { status: "bun scripts/cli.ts hwlab g14 retirement status" },
  };
}
