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

// Moved mechanically from scripts/src/ci.ts:1899-1968 for #903.

import { randomUUID } from "node:crypto";
import { existsSync, mkdtempSync, readFileSync, rmSync } from "node:fs";
import { tmpdir } from "node:os";
import { join, posix as posixPath } from "node:path";
import { blockedCatalogArtifactIds, catalogSummary, findCiCatalogArtifact, loadCiCatalog, supportedSourceBuildArtifactIds, type CiCatalogArtifact, type CiSourceBuildCatalogArtifact, type CiUpstreamImageCatalogArtifact } from "../ci-catalog";
import { runCommand } from "../command";
import { type UniDeskConfig, repoRoot, rootPath } from "../config";
import { ensureGithubSshIdentityForProvider, gitSshHttpConnectProxySource } from "../deploy-ssh-identity";
import { jobWithTail, listJobs, readJob, startJob } from "../jobs";
import { coreInternalFetch } from "../microservices";
import {
  artifactRegistryReadonlyResultFromCommand,
  buildArtifactRegistryReadonlyProbe,
  parseArtifactRegistryOptions,
  type ArtifactRegistryReadonlyProbe,
} from "../artifact-registry";
import { k3sGuardShellLines, defaultNativeKubeconfig } from "../k3s-target-guard";
import { runSshCommandCapture } from "../ssh";
import { kubernetesWatchOneEventShellFunction } from "../kubernetes-watch";

import type { DispatchResult, PipelineRunCondition } from "./types";
import { status } from "./cleanup";
import { ciTargetGuardShellLines, shellQuote } from "./options";
import { dispatchSsh, runRemoteKubectl, runRemoteKubectlRaw, uploadRemoteBase64 } from "./remote";
import { ciTarget } from "./types";

export async function remoteCreatePipelineRun(manifest: string, target = ciTarget(null)): Promise<string> {
  const encoded = Buffer.from(manifest, "utf8").toString("base64");
  const token = randomUUID().replace(/-/gu, "").slice(0, 12);
  const b64Path = `/tmp/unidesk-ci-pipelinerun-${token}.b64`;
  const upload = await uploadRemoteBase64(b64Path, encoded, target);
  if (!upload.ok) throw new Error(`failed to upload PipelineRun manifest: ${upload.stderr || upload.stdout}`);
  const result = await runRemoteKubectl([
    "tmp=$(mktemp /tmp/unidesk-ci-run.XXXXXX.yaml)",
    `b64_path=${shellQuote(b64Path)}`,
    "trap 'rm -f \"$tmp\" \"$b64_path\"' EXIT",
    "base64 -d \"$b64_path\" > \"$tmp\"",
    "kubectl create -f \"$tmp\" -o jsonpath='{.metadata.name}'",
  ].join("\n"), 60_000, 45_000, target);
  return result.stdout.trim();
}

export async function waitForPipelineRun(name: string, waitMs: number, target = ciTarget(null)): Promise<DispatchResult | null> {
  if (waitMs <= 0) return null;
  const command = [
    "set -euo pipefail",
    ...ciTargetGuardShellLines(target),
    kubernetesWatchOneEventShellFunction(),
    `deadline=$((SECONDS + ${Math.ceil(waitMs / 1000)}))`,
    `printf 'waiting_pipelinerun=%s\\n' ${shellQuote(name)}`,
    `condition="$(kubectl get pipelinerun/${shellQuote(name)} -n unidesk-ci -o jsonpath='{range .status.conditions[?(@.type==\"Succeeded\")]}{.status}{\"\\t\"}{.reason}{\"\\t\"}{.message}{end}' 2>/dev/null || true)"`,
    "while :; do",
    "  case \"$condition\" in",
    "    True*)",
    "      printf 'condition=%s\\n' \"$condition\"",
    `      kubectl get taskrun -n unidesk-ci -l tekton.dev/pipelineRun=${shellQuote(name)} --no-headers 2>/dev/null || true`,
    "      exit 0",
    "      ;;",
    "    False*)",
    "      printf 'condition=%s\\n' \"$condition\"",
    `      kubectl get taskrun,pod -n unidesk-ci -l tekton.dev/pipelineRun=${shellQuote(name)} 2>/dev/null || true`,
    "      exit 1",
    "      ;;",
    "  esac",
    "  remaining=$((deadline - SECONDS))",
    "  [ \"$remaining\" -gt 0 ] || break",
    "  set +e",
    `  event="$(unidesk_watch_one_event kubectl get pipelinerun/${shellQuote(name)} -n unidesk-ci --watch-only --request-timeout="\${remaining}s" -o name)"`,
    "  watch_status=$?",
    "  set -e",
    "  [ \"$watch_status\" -eq 0 ] || exit \"$watch_status\"",
    "  [ -n \"$event\" ] || break",
    `  condition="$(kubectl get pipelinerun/${shellQuote(name)} -n unidesk-ci -o jsonpath='{range .status.conditions[?(@.type==\"Succeeded\")]}{.status}{\"\\t\"}{.reason}{\"\\t\"}{.message}{end}' 2>/dev/null || true)"`,
    "done",
    `echo "Timed out waiting for pipelinerun/${name}" >&2`,
    `kubectl get pipelinerun/${shellQuote(name)} -n unidesk-ci -o json`,
    "exit 124",
  ].join("\n");
  return dispatchSsh(command, waitMs + 30_000, waitMs + 20_000, true, target);
}

export async function readPipelineRunCondition(name: string, target = ciTarget(null)): Promise<PipelineRunCondition> {
  const result = await runRemoteKubectlRaw([
    "set -euo pipefail",
    `condition="$(kubectl get pipelinerun/${shellQuote(name)} -n unidesk-ci -o jsonpath='{range .status.conditions[?(@.type==\"Succeeded\")]}{.status}{\"\\t\"}{.reason}{\"\\t\"}{.message}{end}' 2>/dev/null || true)"`,
    "printf '%s\\n' \"$condition\"",
  ].join("\n"), 30_000, 15_000, target);
  const [status = "", reason = "", ...messageParts] = result.stdout.trim().split("\t");
  const message = messageParts.join("\t");
  return {
    ok: status === "True" ? true : status === "False" ? false : null,
    status,
    reason,
    message,
    query: {
      ok: result.ok,
      status: result.status,
      exitCode: result.exitCode,
      stdoutTail: result.stdout.slice(-1200),
      stderrTail: result.stderr.slice(-1200),
    },
  };
}

export function pipelineRunWaitSucceeded(wait: DispatchResult | null, condition: PipelineRunCondition | null): boolean {
  if (wait === null) return true;
  if (condition?.ok === true) return true;
  if (condition?.ok === false) return false;
  return wait.ok || wait.exitCode === 0 || wait.stdout.includes("condition=True\tSucceeded\t");
}
