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

// Moved mechanically from scripts/src/ci.ts:1668-1898 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 type { CiPublishBackendCoreOptions, CiPublishUserServiceArtifactOptions } from "./types";
import { repoConnectivityProbeUrl, repoNeedsGithubSshIdentity, repoSshUrl, requireRepoRelativePath, safePathToken, shellQuote } from "./options";
import { runRemoteBackground } from "./remote";
import { defaultCiProviderId, providerGatewayWsEgressProxyUrl } from "./types";

export async function prepareBackendCoreArtifactSource(config: UniDeskConfig, options: CiPublishBackendCoreOptions): Promise<Record<string, unknown>> {
  const sourceRoot = "/home/ubuntu/.unidesk/ci/backend-core-artifacts";
  const sourceHostPath = options.sourceHostPath;
  const repoCache = "/home/ubuntu/.unidesk/ci/git/unidesk.git";
  const repoFetchUrl = repoSshUrl(options.repoUrl);
  const sshIdentity = repoNeedsGithubSshIdentity(repoFetchUrl) ? await ensureGithubSshIdentityForProvider(config, defaultCiProviderId) : null;
  if (sshIdentity !== null && !sshIdentity.ok) throw new Error(sshIdentity.detail);
  const proxyPython = gitSshHttpConnectProxySource();
  const dockerfile = requireRepoRelativePath(options.dockerfile, "CI.json.artifacts.backend-core.source.dockerfile");
  const script = [
    "set -euo pipefail",
    `commit=${shellQuote(options.commit)}`,
    `repo_url=${shellQuote(options.repoUrl)}`,
    `repo_fetch_url=${shellQuote(repoFetchUrl)}`,
    `dockerfile=${shellQuote(dockerfile)}`,
    `source_root=${shellQuote(sourceRoot)}`,
    `source_dir=${shellQuote(sourceHostPath)}`,
    `repo_cache=${shellQuote(repoCache)}`,
    `proxy_url=${shellQuote(providerGatewayWsEgressProxyUrl)}`,
    "mkdir -p \"$(dirname \"$repo_cache\")\" \"$source_root\"",
    "export HTTP_PROXY=\"$proxy_url\" HTTPS_PROXY=\"$proxy_url\" ALL_PROXY=\"$proxy_url\"",
    "export NO_PROXY=\"localhost,127.0.0.1,::1,host.docker.internal,.svc,.cluster.local,kubernetes.default.svc\"",
    "curl -fsSI --max-time 20 -x \"$proxy_url\" https://github.com >/dev/null",
    "git_ssh_proxy=/tmp/unidesk-git-ssh-http-connect.py",
    "cat > \"$git_ssh_proxy\" <<'UNIDESK_GIT_SSH_PROXY'",
    proxyPython,
    "UNIDESK_GIT_SSH_PROXY",
    "chmod 700 \"$git_ssh_proxy\"",
    "export UNIDESK_GIT_SSH_HTTP_PROXY=\"$proxy_url\"",
    "export GIT_SSH_COMMAND=\"ssh -o BatchMode=yes -o IdentitiesOnly=yes -o StrictHostKeyChecking=yes -o UserKnownHostsFile=$HOME/.ssh/known_hosts -i $HOME/.ssh/id_ed25519 -o 'ProxyCommand=$git_ssh_proxy %h %p'\"",
    "echo backend_core_artifact_source_proxy=provider-gateway-ws-egress:$proxy_url",
    "echo backend_core_artifact_repo_fetch_url=$repo_fetch_url",
    "if [ ! -d \"$repo_cache\" ]; then git clone --mirror \"$repo_fetch_url\" \"$repo_cache\"; fi",
    "git -C \"$repo_cache\" remote set-url origin \"$repo_fetch_url\"",
    "git -C \"$repo_cache\" fetch --no-tags origin \"$commit\" || git -C \"$repo_cache\" fetch --no-tags origin '+refs/heads/*:refs/remotes/origin/*'",
    "resolved=$(git -C \"$repo_cache\" rev-parse --verify \"$commit^{commit}\")",
    "test \"$resolved\" = \"$commit\" || { echo \"backend_core_artifact_resolved_commit_mismatch=$resolved expected=$commit\" >&2; exit 1; }",
    "git -C \"$repo_cache\" cat-file -e \"$commit:$dockerfile\"",
    "git -C \"$repo_cache\" cat-file -e \"$commit:src/components/backend-core/src\"",
    "tmp_dir=\"$source_root/.tmp-$commit-$$\"",
    "rm -rf \"$tmp_dir\"",
    "mkdir -p \"$tmp_dir\"",
    "git -C \"$repo_cache\" archive \"$commit\" | tar -x -C \"$tmp_dir\"",
    "printf '%s\\n' \"$commit\" > \"$tmp_dir/.unidesk-source-commit\"",
    "printf '%s\\n' \"$repo_url\" > \"$tmp_dir/.unidesk-source-repo\"",
    "rm -rf \"$source_dir\"",
    "mv \"$tmp_dir\" \"$source_dir\"",
    "test -f \"$source_dir/$dockerfile\"",
    "test -d \"$source_dir/src/components/backend-core/src\"",
    "echo backend_core_artifact_source_host_path=$source_dir",
  ].join("\n");
  const result = await runRemoteBackground("prepare-backend-core-source", script, 300_000);
  if (!result.ok) throw new Error(`failed to prepare backend-core source on D601: ${result.stderr || result.stdout || JSON.stringify(result.raw)}`);
  return {
    ok: true,
    mode: repoNeedsGithubSshIdentity(repoFetchUrl) ? "d601-host-git-ssh-export" : "d601-host-git-https-export",
    providerId: defaultCiProviderId,
    repoUrl: options.repoUrl,
    repoFetchUrl,
    commit: options.commit,
    sourceHostPath,
    dockerfile,
    identity: sshIdentity === null ? null : {
      fingerprint: sshIdentity.fingerprint,
      seededFromLocal: sshIdentity.seededFromLocal,
    },
    stdoutTail: result.stdout.slice(-4000),
  };
}

export async function prepareUserServiceArtifactSource(config: UniDeskConfig, options: CiPublishUserServiceArtifactOptions): Promise<Record<string, unknown>> {
  const sourceRoot = `/home/ubuntu/.unidesk/ci/user-service-artifacts/${options.serviceId}`;
  const sourceHostPath = options.sourceHostPath;
  const repoCache = `/home/ubuntu/.unidesk/ci/git/${safePathToken(options.serviceId)}.git`;
  const repoFetchUrl = repoSshUrl(options.repoUrl);
  const repoProbeUrl = repoConnectivityProbeUrl(repoFetchUrl);
  const sshIdentity = repoNeedsGithubSshIdentity(repoFetchUrl) ? await ensureGithubSshIdentityForProvider(config, defaultCiProviderId) : null;
  if (sshIdentity !== null && !sshIdentity.ok) throw new Error(sshIdentity.detail);
  const proxyPython = gitSshHttpConnectProxySource();
  const script = [
    "set -euo pipefail",
    `service_id=${shellQuote(options.serviceId)}`,
    `commit=${shellQuote(options.commit)}`,
    `repo_url=${shellQuote(options.repoUrl)}`,
    `repo_fetch_url=${shellQuote(repoFetchUrl)}`,
    `repo_probe_url=${shellQuote(repoProbeUrl)}`,
    `dockerfile=${shellQuote(options.dockerfile)}`,
    `source_root=${shellQuote(sourceRoot)}`,
    `source_dir=${shellQuote(sourceHostPath)}`,
    `repo_cache=${shellQuote(repoCache)}`,
    `proxy_url=${shellQuote(providerGatewayWsEgressProxyUrl)}`,
    "mkdir -p \"$(dirname \"$repo_cache\")\" \"$source_root\"",
    "export HTTP_PROXY=\"$proxy_url\" HTTPS_PROXY=\"$proxy_url\" ALL_PROXY=\"$proxy_url\"",
    "export NO_PROXY=\"localhost,127.0.0.1,::1,host.docker.internal,.svc,.cluster.local,kubernetes.default.svc\"",
    "curl -fsSI --max-time 20 -x \"$proxy_url\" \"$repo_probe_url\" >/dev/null",
    "git_ssh_proxy=/tmp/unidesk-git-ssh-http-connect.py",
    "cat > \"$git_ssh_proxy\" <<'UNIDESK_GIT_SSH_PROXY'",
    proxyPython,
    "UNIDESK_GIT_SSH_PROXY",
    "chmod 700 \"$git_ssh_proxy\"",
    "export UNIDESK_GIT_SSH_HTTP_PROXY=\"$proxy_url\"",
    "export GIT_SSH_COMMAND=\"ssh -o BatchMode=yes -o IdentitiesOnly=yes -o StrictHostKeyChecking=yes -o UserKnownHostsFile=$HOME/.ssh/known_hosts -i $HOME/.ssh/id_ed25519 -o 'ProxyCommand=$git_ssh_proxy %h %p'\"",
    "echo user_service_artifact_source_proxy=provider-gateway-ws-egress:$proxy_url",
    "echo user_service_artifact_repo_fetch_url=$repo_fetch_url",
    "echo user_service_artifact_repo_probe_url=$repo_probe_url",
    "echo user_service_artifact_service_id=$service_id",
    "if [ ! -d \"$repo_cache\" ]; then git clone --mirror \"$repo_fetch_url\" \"$repo_cache\"; fi",
    "git -C \"$repo_cache\" remote set-url origin \"$repo_fetch_url\"",
    "git -C \"$repo_cache\" fetch --no-tags origin \"$commit\" || git -C \"$repo_cache\" fetch --no-tags origin '+refs/heads/*:refs/remotes/origin/*'",
    "resolved=$(git -C \"$repo_cache\" rev-parse --verify \"$commit^{commit}\")",
    "test \"$resolved\" = \"$commit\" || { echo \"user_service_artifact_resolved_commit_mismatch=$resolved expected=$commit\" >&2; exit 1; }",
    "git -C \"$repo_cache\" cat-file -e \"$commit:$dockerfile\"",
    "tmp_dir=\"$source_root/.tmp-$commit-$$\"",
    "rm -rf \"$tmp_dir\"",
    "mkdir -p \"$tmp_dir\"",
    "git -C \"$repo_cache\" archive \"$commit\" | tar -x -C \"$tmp_dir\"",
    "printf '%s\\n' \"$commit\" > \"$tmp_dir/.unidesk-source-commit\"",
    "printf '%s\\n' \"$repo_url\" > \"$tmp_dir/.unidesk-source-repo\"",
    "printf '%s\\n' \"$service_id\" > \"$tmp_dir/.unidesk-service-id\"",
    "printf '%s\\n' \"$dockerfile\" > \"$tmp_dir/.unidesk-dockerfile\"",
    "rm -rf \"$source_dir\"",
    "mv \"$tmp_dir\" \"$source_dir\"",
    "test -f \"$source_dir/$dockerfile\"",
    "echo user_service_artifact_source_host_path=$source_dir",
  ].join("\n");
  const result = await runRemoteBackground(`prepare-${options.serviceId}-source`, script, 300_000);
  if (!result.ok) throw new Error(`failed to prepare ${options.serviceId} source on D601: ${result.stderr || result.stdout || JSON.stringify(result.raw)}`);
  return {
    ok: true,
    mode: repoNeedsGithubSshIdentity(repoFetchUrl) ? "d601-host-git-ssh-export" : "d601-host-git-https-export",
    providerId: defaultCiProviderId,
    repoUrl: options.repoUrl,
    repoFetchUrl,
    repoProbeUrl,
    commit: options.commit,
    serviceId: options.serviceId,
    dockerfile: options.dockerfile,
    sourceHostPath,
    identity: sshIdentity === null ? null : {
      fingerprint: sshIdentity.fingerprint,
      seededFromLocal: sshIdentity.seededFromLocal,
    },
    stdoutTail: result.stdout.slice(-4000),
  };
}

export async function prepareClaudeqqArtifactSource(config: UniDeskConfig, options: CiPublishUserServiceArtifactOptions): Promise<Record<string, unknown>> {
  const sourceRoot = `/home/ubuntu/.unidesk/ci/user-service-artifacts/${options.serviceId}`;
  const sourceHostPath = options.sourceHostPath;
  const repoCache = "/home/ubuntu/.unidesk/ci/git/claudeqq-agent-skills.git";
  const repoFetchUrl = options.repoUrl;
  const assets = [
    {
      relativePath: "claudeqq/Dockerfile",
      sourcePath: rootPath("src/components/microservices/claudeqq/Dockerfile"),
      label: "Dockerfile",
    },
    {
      relativePath: "claudeqq/unidesk-adapter.cjs",
      sourcePath: rootPath("src/components/microservices/claudeqq/adapter.js"),
      label: "unidesk-adapter.cjs",
    },
  ];
  for (const asset of assets) {
    if (!existsSync(asset.sourcePath)) throw new Error(`claudeqq artifact asset missing: ${asset.sourcePath}`);
  }
  const overlayCommands = assets.flatMap((asset) => {
    const encoded = Buffer.from(readFileSync(asset.sourcePath, "utf8"), "utf8").toString("base64");
    return [
      `mkdir -p "$tmp_dir/$(dirname ${shellQuote(asset.relativePath)})"`,
      `printf %s ${shellQuote(encoded)} | base64 -d > "$tmp_dir/${asset.relativePath}"`,
      `printf 'user_service_artifact_overlay=%s\\n' ${shellQuote(asset.label)}`,
    ];
  });
  const script = [
    "set -euo pipefail",
    `service_id=${shellQuote(options.serviceId)}`,
    `commit=${shellQuote(options.commit)}`,
    `repo_url=${shellQuote(options.repoUrl)}`,
    `repo_fetch_url=${shellQuote(repoFetchUrl)}`,
    `dockerfile=${shellQuote(options.dockerfile)}`,
    `source_root=${shellQuote(sourceRoot)}`,
    `source_dir=${shellQuote(sourceHostPath)}`,
    `repo_cache=${shellQuote(repoCache)}`,
    `proxy_url=${shellQuote(providerGatewayWsEgressProxyUrl)}`,
    "mkdir -p \"$(dirname \"$repo_cache\")\" \"$source_root\"",
    "export HTTP_PROXY=\"$proxy_url\" HTTPS_PROXY=\"$proxy_url\" ALL_PROXY=\"$proxy_url\"",
    "export NO_PROXY=\"localhost,127.0.0.1,::1,host.docker.internal,.svc,.cluster.local,kubernetes.default.svc\"",
    "curl -fsSI --max-time 20 -x \"$proxy_url\" https://gitee.com >/dev/null",
    "echo user_service_artifact_source_proxy=provider-gateway-ws-egress:$proxy_url",
    "echo user_service_artifact_repo_fetch_url=$repo_fetch_url",
    "echo user_service_artifact_service_id=$service_id",
    "if [ ! -d \"$repo_cache\" ]; then git clone --mirror \"$repo_fetch_url\" \"$repo_cache\"; fi",
    "git -C \"$repo_cache\" remote set-url origin \"$repo_fetch_url\"",
    "git -C \"$repo_cache\" fetch --no-tags origin \"$commit\" || git -C \"$repo_cache\" fetch --no-tags origin '+refs/heads/*:refs/remotes/origin/*'",
    "resolved=$(git -C \"$repo_cache\" rev-parse --verify \"$commit^{commit}\")",
    "test \"$resolved\" = \"$commit\" || { echo \"user_service_artifact_resolved_commit_mismatch=$resolved expected=$commit\" >&2; exit 1; }",
    "git -C \"$repo_cache\" cat-file -e \"$commit:claudeqq/scripts/src/server_ts/package.json\"",
    "git -C \"$repo_cache\" cat-file -e \"$commit:claudeqq/scripts/src/server_ts/src\"",
    "tmp_dir=\"$source_root/.tmp-$commit-$$\"",
    "rm -rf \"$tmp_dir\"",
    "mkdir -p \"$tmp_dir\"",
    "git -C \"$repo_cache\" archive \"$commit\" claudeqq | tar -x -C \"$tmp_dir\"",
    ...overlayCommands,
    "printf '%s\\n' \"$commit\" > \"$tmp_dir/.unidesk-source-commit\"",
    "printf '%s\\n' \"$repo_url\" > \"$tmp_dir/.unidesk-source-repo\"",
    "printf '%s\\n' \"$service_id\" > \"$tmp_dir/.unidesk-service-id\"",
    "printf '%s\\n' \"$dockerfile\" > \"$tmp_dir/.unidesk-dockerfile\"",
    "rm -rf \"$source_dir\"",
    "mv \"$tmp_dir\" \"$source_dir\"",
    "test -f \"$source_dir/$dockerfile\"",
    "test -f \"$source_dir/claudeqq/unidesk-adapter.cjs\"",
    "test -d \"$source_dir/claudeqq/scripts/src/server_ts/src\"",
    "echo user_service_artifact_source_host_path=$source_dir",
  ].join("\n");
  const result = await runRemoteBackground(`prepare-${options.serviceId}-source`, script, 300_000);
  if (!result.ok) throw new Error(`failed to prepare ${options.serviceId} source on D601: ${result.stderr || result.stdout || JSON.stringify(result.raw)}`);
  return {
    ok: true,
    mode: "d601-host-gitee-https-export-with-unidesk-overlay",
    providerId: defaultCiProviderId,
    repoUrl: options.repoUrl,
    repoFetchUrl,
    commit: options.commit,
    serviceId: options.serviceId,
    dockerfile: options.dockerfile,
    sourceHostPath,
    stdoutTail: result.stdout.slice(-4000),
  };
}
