// SPEC: PJ2026-01060307 控制面模块化 draft-2026-06-25-p0. remote 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:1659-1933 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 type { ArtifactRegistryCommandRuntime, ArtifactRegistryOptions } from "./types";
import { renderBundle, safeName, shellQuote } from "./bundle";
import { artifactRegistryCommandTail } from "./consumer";
import { classifyProviderSshCommandFailure, isLocalProvider, providerSshCommandFailureScopes, readonlyRemoteCommandShape, registryRecommendedAction } from "./status";

export function runRemoteScript(options: ArtifactRegistryOptions, script: string, timeoutMs = options.timeoutMs, runtime: ArtifactRegistryCommandRuntime = {}): CommandResult {
  if (runtime.runRemoteScriptForTest !== undefined) return runtime.runRemoteScriptForTest(options, script, timeoutMs);
  if (isLocalProvider(options.providerId)) {
    return runCommand(["bash", "-lc", script], repoRoot, { timeoutMs });
  }
  const command = [process.execPath, "scripts/cli.ts", "ssh", options.providerId, "argv", "bash", "-lc", script];
  return runCommand(command, repoRoot, { timeoutMs });
}

export function combineCommandResults(command: string[], parts: CommandResult[]): CommandResult {
  const failed = parts.find((part) => part.exitCode !== 0 || part.timedOut) ?? parts.at(-1) ?? null;
  return {
    command,
    cwd: repoRoot,
    exitCode: failed === null ? 0 : failed.exitCode,
    signal: failed?.signal ?? null,
    timedOut: parts.some((part) => part.timedOut),
    stdout: parts.map((part) => part.stdout).filter((part) => part.length > 0).join("\n"),
    stderr: parts.map((part) => part.stderr).filter((part) => part.length > 0).join("\n"),
  };
}

export function downloadRemoteFile(options: ArtifactRegistryOptions, remotePath: string, localPath: string, timeoutMs = options.timeoutMs): CommandResult {
  const transferRuntimeTimeoutMs = Math.max(timeoutMs * 4, 60 * 60_000);
  const command = [
    process.execPath,
    "scripts/cli.ts",
    "ssh",
    options.providerId,
    "download",
    "--inactivity-timeout-ms",
    String(timeoutMs),
    remotePath,
    localPath,
  ];
  const stdoutFile = process.env.UNIDESK_JOB_STDOUT_FILE;
  const stderrFile = process.env.UNIDESK_JOB_STDERR_FILE;
  if (!stdoutFile || !stderrFile) return runCommand(command, repoRoot, { timeoutMs: transferRuntimeTimeoutMs });
  return runCommand([
    "bash",
    "-lc",
    `set -o pipefail; "$@" > >(tee -a ${shellQuote(stdoutFile)}) 2> >(tee -a ${shellQuote(stderrFile)} >&2)`,
    "unidesk-artifact-download",
    ...command,
  ], repoRoot, { timeoutMs: transferRuntimeTimeoutMs });
}

export async function runRemoteScriptBackground(
  options: ArtifactRegistryOptions,
  script: string,
  timeoutMs: number,
  name: string,
): Promise<CommandResult> {
  const token = `${Date.now().toString(36)}-${Math.random().toString(16).slice(2, 8)}`;
  const prefix = `/tmp/unidesk-artifact-${safeName(options.serviceId ?? "service")}-${safeName(name)}-${token}`;
  const logFile = `${prefix}.log`;
  const doneFile = `${prefix}.done`;
  const wrapped = [
    `bash -lc ${shellQuote(script)}`,
    "code=$?",
    `printf '%s\\n' "$code" > ${shellQuote(doneFile)}`,
    "exit \"$code\"",
  ].join("; ");
  const launchScript = [
    `rm -f ${shellQuote(logFile)} ${shellQuote(doneFile)}`,
    `nohup bash -lc ${shellQuote(wrapped)} > ${shellQuote(logFile)} 2>&1 < /dev/null & echo $!`,
  ].join("\n");
  const launch = runRemoteScript(options, launchScript, 30_000);
  const pid = launch.stdout.trim().split(/\n/u).pop()?.trim() ?? "";
  if (launch.exitCode !== 0 || launch.timedOut || !/^\d+$/u.test(pid)) {
    return combineCommandResults(["artifact-registry", "remote-background", name], [launch]);
  }

  const startedAt = Date.now();
  let latest: CommandResult | null = null;
  while (Date.now() - startedAt < timeoutMs) {
    await Bun.sleep(2_000);
    const poll = runRemoteScript(options, [
      `if [ -f ${shellQuote(doneFile)} ]; then printf 'SENTINEL:%s\\n' "$(cat ${shellQuote(doneFile)} 2>/dev/null || true)"; else echo RUNNING; fi`,
      `tail -n 160 ${shellQuote(logFile)} 2>/dev/null | tr -d '\\000' | LC_ALL=C sed 's/[^[:print:]\t]//g' || true`,
    ].join("; "), 30_000);
    latest = poll;
    if (poll.exitCode !== 0 || poll.timedOut) continue;
    const stdout = poll.stdout.trimEnd();
    const [head = "", ...rest] = stdout.split("\n");
    if (!head.startsWith("SENTINEL:")) continue;
    const code = Number(head.slice("SENTINEL:".length).trim());
    return {
      command: ["artifact-registry", "remote-background", name],
      cwd: repoRoot,
      exitCode: Number.isInteger(code) ? code : 1,
      signal: null,
      timedOut: false,
      stdout: [
        `remote_background_name=${name}`,
        `remote_background_pid=${pid}`,
        `remote_background_log=${logFile}`,
        ...rest,
      ].join("\n"),
      stderr: poll.stderr,
    };
  }

  return {
    command: ["artifact-registry", "remote-background", name],
    cwd: repoRoot,
    exitCode: 124,
    signal: null,
    timedOut: true,
    stdout: [
      `remote_background_name=${name}`,
      `remote_background_pid=${pid}`,
      `remote_background_log=${logFile}`,
      latest?.stdout ?? "",
    ].join("\n"),
    stderr: latest?.stderr ?? `remote background script ${name} did not finish within ${timeoutMs}ms`,
  };
}

export function readonlyCommandFailureResult(
  options: ArtifactRegistryOptions,
  command: CommandResult,
  action: "status" | "health",
): Record<string, unknown> {
  const bundle = renderBundle(options);
  const failureClassification = classifyProviderSshCommandFailure(command);
  return {
    ok: false,
    readonly: true,
    installed: false,
    healthy: false,
    decision: failureClassification === "local-docker-required" ? "infra-blocked" : "retryable-transient",
    retryable: failureClassification !== "ssh-helper-command-shape-incompatible",
    failureClassification,
    recommendedAction: registryRecommendedAction(failureClassification),
    remoteCommandShape: readonlyRemoteCommandShape(action, options),
    healthyScopes: [],
    failedScopes: providerSshCommandFailureScopes(failureClassification),
    runtimeApiHealthy: false,
    checks: {},
    expected: {
      endpoint: `http://${options.host}:${options.port}`,
      image: options.image,
      paths: bundle.paths,
    },
    command: artifactRegistryCommandTail(command),
  };
}

export function remoteFrontendHostFromEnv(env: NodeJS.ProcessEnv = process.env): string | null {
  for (const key of ["UNIDESK_MAIN_SERVER_IP", "UNIDESK_MAIN_SERVER_HOST", "CODE_QUEUE_DEV_CONTAINER_MASTER_HOST"]) {
    const value = env[key]?.trim() ?? "";
    if (value.length === 0) continue;
    if (value === "localhost" || value === "127.0.0.1" || value === "::1") continue;
    return value.replace(/\/+$/u, "");
  }
  if (env.CODE_QUEUE_SERVICE_ROLE || env.CODE_QUEUE_INSTANCE_ID || env.KUBERNETES_SERVICE_HOST) {
    const publicHost = readConfig().network.publicHost.trim();
    if (publicHost.length > 0 && publicHost !== "localhost" && publicHost !== "127.0.0.1" && publicHost !== "::1") return publicHost;
  }
  return null;
}

export function controlPlaneMissingResult(
  options: ArtifactRegistryOptions,
  action: "status" | "health",
  localResult: Record<string, unknown>,
  remoteResult: Record<string, unknown> | null,
  remoteCommand: CommandResult | null,
  remoteHost: string | null,
): Record<string, unknown> {
  const bundle = renderBundle(options);
  return {
    ok: false,
    readonly: true,
    installed: false,
    healthy: false,
    decision: "infra-blocked",
    retryable: true,
    runnerDisposition: "infra-blocked",
    failureClassification: "control-plane-missing",
    recommendedAction: registryRecommendedAction("control-plane-missing"),
    remoteCommandShape: readonlyRemoteCommandShape(action, options),
    healthyScopes: [],
    failedScopes: providerSshCommandFailureScopes("control-plane-missing"),
    runtimeApiHealthy: false,
    checks: {},
    expected: {
      endpoint: `http://${options.host}:${options.port}`,
      image: options.image,
      paths: bundle.paths,
    },
    controlPlane: {
      preferred: "remote-frontend",
      remoteFallbackAttempted: remoteHost !== null,
      remoteFallbackUsed: false,
      remoteHost,
      localBackendCoreMissing: true,
      classification: "control-plane-missing",
      retryCommand: remoteHost === null
        ? `bun scripts/cli.ts --main-server-ip <host> artifact-registry ${action} --target ${options.targetId}`
        : `bun scripts/cli.ts --main-server-ip ${remoteHost} artifact-registry ${action} --target ${options.targetId}`,
    },
    localObservation: localResult,
    remoteObservation: remoteResult,
    remoteCommand: remoteCommand === null ? null : artifactRegistryCommandTail(remoteCommand),
  };
}

export function parsedCliData(stdout: string): Record<string, unknown> | null {
  if (stdout.trim().length === 0) return null;
  try {
    const parsed = JSON.parse(stdout) as unknown;
    if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) return null;
    const record = parsed as Record<string, unknown>;
    const data = record.data;
    return typeof data === "object" && data !== null && !Array.isArray(data) ? data as Record<string, unknown> : record;
  } catch {
    return null;
  }
}

export function unwrapRemoteArtifactRegistryResult(data: Record<string, unknown>): Record<string, unknown> | null {
  const directResult = data.result;
  if (typeof directResult === "object" && directResult !== null && !Array.isArray(directResult)) return directResult as Record<string, unknown>;
  const nested = data.data;
  if (typeof nested === "object" && nested !== null && !Array.isArray(nested)) {
    const nestedRecord = nested as Record<string, unknown>;
    const nestedResult = nestedRecord.result;
    if (typeof nestedResult === "object" && nestedResult !== null && !Array.isArray(nestedResult)) return nestedResult as Record<string, unknown>;
  }
  return null;
}

export function annotateRemoteReadonlyResult(
  result: Record<string, unknown>,
  localResult: Record<string, unknown>,
  remoteHost: string,
  command: CommandResult,
): Record<string, unknown> {
  return {
    ...result,
    controlPlane: {
      preferred: "remote-frontend",
      remoteFallbackAttempted: true,
      remoteFallbackUsed: true,
      remoteHost,
      localBackendCoreMissing: true,
      classification: null,
    },
    localObservation: localResult,
    remoteCommand: artifactRegistryCommandTail(command),
  };
}

export function annotateRemoteFirstReadonlyResult(
  result: Record<string, unknown>,
  remoteHost: string,
  command: CommandResult,
): Record<string, unknown> {
  return {
    ...result,
    controlPlane: {
      preferred: "remote-frontend",
      remoteFallbackAttempted: false,
      remoteFallbackUsed: true,
      remoteFirst: true,
      remoteHost,
      localBackendCoreMissing: null,
      classification: null,
    },
    remoteCommand: artifactRegistryCommandTail(command),
  };
}
