// SPEC: PJ2026-01060307 控制面模块化 draft-2026-06-25-p0. readonly module for scripts/src/artifact-registry.ts.
// SPEC: PJ2026-01060308 cicd-yaml-targets draft-2026-06-25-cicd-yaml-targets.

// Moved mechanically from scripts/src/artifact-registry.ts:1934-2116 for #903.

import { createHash } from "node:crypto";
import { existsSync, readFileSync, rmSync, writeFileSync } from "node:fs";
import { join } from "node:path";
import { runCommand, type CommandResult } from "../command";
import { readConfig, type UniDeskConfig, repoRoot, rootPath } from "../config";
import { startJob } from "../jobs";
import {
  compareDeployJsonExecutorMirrors,
  deployJsonCommitImage,
  deployJsonDriftResult,
  deployJsonSourceOfTruth,
  hasDeployJsonExecutorContract,
  k3sManifestExecutorMirror,
  parseDeployJsonServiceContractBase64,
  readDeployJsonServiceContractFromFile,
  type DeployJsonExecutorMirror,
  type DeployJsonServiceContract,
} from "../deploy-json-contract";
import { d601K3sGuardShellLines } from "../d601-k3s-guard";
import { composeRuntimeEnvValue } from "../runtime-env";

import { artifactRegistryOptionsTargetSummary, type ArtifactRegistryCommandRuntime, type ArtifactRegistryOptions, type ArtifactRegistryReadonlyProbe } from "./types";
import { renderBundle } from "./bundle";
import { commandTail } from "./consumer";
import { annotateRemoteFirstReadonlyResult, annotateRemoteReadonlyResult, controlPlaneMissingResult, parsedCliData, readonlyCommandFailureResult, remoteFrontendHostFromEnv, runRemoteScript, unwrapRemoteArtifactRegistryResult } from "./remote";
import { asBool, parseKeyValueOutput, readonlyRemoteCommandShape, registryHealthDecision, statusScript } from "./status";

export function artifactRegistryReadonlyAutoRemotePlan(
  action: "status" | "health",
  options: ArtifactRegistryOptions,
  env: NodeJS.ProcessEnv = process.env,
): Record<string, unknown> {
  const remoteHost = remoteFrontendHostFromEnv(env);
  return {
    enabled: remoteHost !== null,
    action,
    providerId: options.providerId,
    host: remoteHost,
    transport: "frontend",
    command: remoteHost === null ? null : `bun scripts/cli.ts --main-server-ip ${remoteHost} artifact-registry ${action} --target ${options.targetId}`,
    failureClassification: remoteHost === null ? "control-plane-missing" : null,
  };
}

export function runReadonlyStatusWithRemoteFallback(options: ArtifactRegistryOptions, healthMode: boolean, runtime: ArtifactRegistryCommandRuntime = {}): Record<string, unknown> {
  const action = healthMode ? "health" : "status";
  const remoteHost = remoteFrontendHostFromEnv(runtime.env ?? process.env);
  const shouldPreferRemote = remoteHost !== null && (
    Boolean((runtime.env ?? process.env).CODE_QUEUE_SERVICE_ROLE)
    || Boolean((runtime.env ?? process.env).CODE_QUEUE_INSTANCE_ID)
    || Boolean((runtime.env ?? process.env).KUBERNETES_SERVICE_HOST)
    || Boolean((runtime.env ?? process.env).UNIDESK_ARTIFACT_REGISTRY_REMOTE_FIRST)
  );
  if (shouldPreferRemote) {
    const remoteResult = runReadonlyStatusViaRemoteFrontend(options, action, remoteHost, null, runtime);
    if (remoteResult !== null) return remoteResult;
  }
  const localResult = runReadonlyStatus(options, healthMode, runtime);
  if (localResult.failureClassification !== "local-docker-required") return localResult;
  if (remoteHost === null) return controlPlaneMissingResult(options, action, localResult, null, null, remoteHost);
  const remoteResult = runReadonlyStatusViaRemoteFrontend(options, action, remoteHost, localResult, runtime);
  if (remoteResult !== null) return remoteResult;
  return controlPlaneMissingResult(options, action, localResult, null, null, remoteHost);
}

export function runReadonlyStatusViaRemoteFrontend(
  options: ArtifactRegistryOptions,
  action: "status" | "health",
  remoteHost: string,
  localResult: Record<string, unknown> | null,
  runtime: ArtifactRegistryCommandRuntime,
): Record<string, unknown> | null {
  const remoteCommandArgs = [
    process.execPath,
    rootPath("scripts", "cli.ts"),
    "--main-server-ip",
    remoteHost,
    "artifact-registry",
    action,
    "--target",
    options.targetId,
    "--timeout-ms",
    String(options.timeoutMs),
  ];
  const remoteTimeoutMs = Math.max(options.timeoutMs + 15_000, 45_000);
  const remoteCommand = runtime.runCliForTest === undefined
    ? runCommand(remoteCommandArgs, repoRoot, { timeoutMs: remoteTimeoutMs })
    : runtime.runCliForTest(remoteCommandArgs, remoteTimeoutMs);
  const parsed = parsedCliData(remoteCommand.stdout);
  const remoteResult = parsed === null ? null : unwrapRemoteArtifactRegistryResult(parsed);
  if (remoteCommand.exitCode === 0 && !remoteCommand.timedOut && remoteResult !== null) {
    return localResult === null
      ? annotateRemoteFirstReadonlyResult(remoteResult, remoteHost, remoteCommand)
      : annotateRemoteReadonlyResult(remoteResult, localResult, remoteHost, remoteCommand);
  }
  if (localResult !== null) return controlPlaneMissingResult(options, action, localResult, remoteResult, remoteCommand, remoteHost);
  return null;
}

export function statusFromValues(options: ArtifactRegistryOptions, values: Record<string, string>, command: CommandResult, healthMode: boolean): Record<string, unknown> {
  const commandOk = command.exitCode === 0 && !command.timedOut;
  const bundle = renderBundle(options);
  const hashes = Object.fromEntries(bundle.files.map((item) => [item.path, item.sha256]));
  const checks = {
    systemctlAvailable: asBool(values.systemctl_available),
    dockerAvailable: asBool(values.docker_available),
    curlAvailable: asBool(values.curl_available),
    unitExists: asBool(values.unit_exists),
    unitActive: values.unit_active === "active",
    unitEnabled: values.unit_enabled === "enabled",
    composeExists: asBool(values.compose_exists),
    configExists: asBool(values.config_exists),
    storageExists: asBool(values.storage_exists),
    containerRunning: asBool(values.container_running),
    loopbackOnly: asBool(values.loopback_only),
    v2Ok: values.v2_http_code === "200",
    imageMatches: asBool(values.image_matches),
    configHashMatches: asBool(values.config_hash_matches),
    composeHashMatches: asBool(values.compose_hash_matches),
    unitHashMatches: asBool(values.unit_hash_matches),
  };
  const installed = checks.unitExists || checks.composeExists || checks.configExists || checks.storageExists || checks.containerRunning;
  const healthy = commandOk
    && checks.systemctlAvailable
    && checks.dockerAvailable
    && checks.unitExists
    && checks.unitActive
    && checks.composeExists
    && checks.configExists
    && checks.storageExists
    && checks.containerRunning
    && checks.loopbackOnly
    && checks.v2Ok
    && checks.imageMatches
    && checks.configHashMatches
    && checks.composeHashMatches
    && checks.unitHashMatches;
  const decision = registryHealthDecision(checks, commandOk);
  return {
    ok: healthMode ? healthy : commandOk,
    readonly: true,
    installed,
    healthy,
    ...decision,
    target: artifactRegistryOptionsTargetSummary(options),
    remoteCommandShape: readonlyRemoteCommandShape(healthMode ? "health" : "status", options),
    checks,
    observed: {
      unit: { path: bundle.paths.unit, active: values.unit_active, enabled: values.unit_enabled },
      compose: { path: bundle.paths.compose, sha256: values.compose_hash ?? null },
      config: { path: bundle.paths.config, sha256: values.config_hash ?? null },
      storage: { path: bundle.paths.storage },
      container: {
        name: options.containerName,
        running: values.container_running,
        status: values.container_status,
        image: values.container_image,
        restartPolicy: values.container_restart_policy,
      },
      listener: {
        count: Number(values.listener_count ?? 0),
        badCount: Number(values.bad_listener_count ?? 0),
        loopbackOnly: checks.loopbackOnly,
      },
      registryApi: { v2HttpCode: values.v2_http_code },
    },
    expected: {
      unitHash: hashes[bundle.paths.unit] ?? "",
      composeHash: hashes[bundle.paths.compose] ?? "",
      configHash: hashes[bundle.paths.config] ?? "",
      image: options.image,
      endpoint: `http://${options.host}:${options.port}`,
    },
    command: commandTail(command),
  };
}

export function runReadonlyStatus(options: ArtifactRegistryOptions, healthMode: boolean, runtime: ArtifactRegistryCommandRuntime = {}): Record<string, unknown> {
  const bundle = renderBundle(options);
  const script = statusScript(options, bundle);
  const result = runRemoteScript(options, script, options.timeoutMs, runtime);
  if (result.exitCode !== 0 || result.timedOut) {
    return readonlyCommandFailureResult(options, result, healthMode ? "health" : "status");
  }
  return statusFromValues(options, parseKeyValueOutput(result.stdout), result, healthMode);
}

export function buildArtifactRegistryReadonlyProbe(action: "status" | "health", options: ArtifactRegistryOptions): ArtifactRegistryReadonlyProbe {
  const bundle = renderBundle(options);
  const healthMode = action === "health";
  return {
    action,
    providerId: options.providerId,
    script: statusScript(options, bundle),
    timeoutMs: options.timeoutMs,
    healthMode,
    options,
    remoteCommandShape: readonlyRemoteCommandShape(action, options),
  };
}

export function artifactRegistryReadonlyResultFromCommand(
  probe: ArtifactRegistryReadonlyProbe,
  command: CommandResult,
): Record<string, unknown> {
  if (command.exitCode !== 0 || command.timedOut) {
    return readonlyCommandFailureResult(probe.options, command, probe.action);
  }
  return statusFromValues(probe.options, parseKeyValueOutput(command.stdout), command, probe.healthMode);
}
