// SPEC: PJ2026-01060307 控制面模块化 draft-2026-06-25-p0. artifact-jobs module for scripts/src/deploy.ts.

// Moved mechanically from scripts/src/deploy.ts:3390-3527 for #903.

import { createHash } from "node:crypto";
import { existsSync, mkdirSync, readFileSync } from "node:fs";
import { join, resolve } from "node:path";
import { pathToFileURL } from "node:url";
import { runCommand } from "../command";
import { type UniDeskConfig, type UniDeskMicroserviceConfig, repoRoot, rootPath } from "../config";
import { ensureGithubSshIdentityForProvider } from "../deploy-ssh-identity";
import { baiduNetdiskRuntimeSecretRequirements, runtimeSecretContractFromEnvText, type RuntimeSecretContract, runArtifactRegistryCommand } from "../artifact-registry";
import { startJob } from "../jobs";
import { coreInternalFetch } from "../microservices";
import { codeQueueSourceImportPreflight, codeQueueSourceSubdir } from "../code-queue-source-guard";
import { d601K3sGuardShellLines, d601NativeKubeconfig } from "../d601-k3s-guard";
import { composeRuntimeEnvValue } from "../runtime-env";
import {
  compareDeployJsonExecutorMirrors,
  deployJsonCommitImage,
  deployJsonDriftResult,
  deployJsonSourceOfTruth,
  encodeDeployJsonServiceContract,
  hasDeployJsonExecutorContract,
  k3sManifestExecutorMirror,
  parseDeployJsonServiceContract,
  type DeployJsonExecutorMirror,
  type DeployJsonServiceContract,
} from "../deploy-json-contract";

import type { DeployManifest, DeployManifestService, DeployOptions } from "./types";
import { applyOneService } from "./apply-service";
import { prodArtifactLiveApplyBlockedResult, prodArtifactUnsupportedResult, selectedEnvironmentServices } from "./check-plan";
import { asRecord, nowIso, progressLine } from "./options";
import { selectServices } from "./service-plan";
import { deployEnvironmentTargets, prodArtifactConsumerServiceIds, prodArtifactLiveApplyBlockedServiceIds } from "./types";

export async function runArtifactConsumerApplyNow(
  manifest: DeployManifest,
  options: DeployOptions,
  environment: "dev" | "prod",
  selected: DeployManifestService[],
): Promise<Record<string, unknown>> {
  const startedAt = nowIso();
  const results = [];
  for (const service of selected) {
    const commit = options.commitOverride ?? service.commitId;
    const artifactArgs = [
      "deploy-service",
      "--service", service.id,
      "--commit", commit,
      "--source-repo", service.repo,
      "--deploy-ref", `${deployEnvironmentTargets[environment].gitRef}:deploy.json#environments.${environment}.services.${service.id}`,
      ...(options.dryRun && hasDeployJsonExecutorContract(service) ? ["--deploy-json-service", encodeDeployJsonServiceContract(service)] : []),
      "--env", environment,
      "--timeout-ms", String(options.timeoutMs),
      "--provider-id", options.providerId,
      "--run-now",
      ...(options.dryRun ? ["--dry-run"] : []),
    ];
    progressLine(`${environment}-artifact-cd`, options.dryRun ? "dry-run" : "reconcile", { serviceId: service.id, commit });
    results.push(await runArtifactRegistryCommand(artifactArgs));
    const latest = results.at(-1) as Record<string, unknown>;
    if (latest.ok !== true) break;
  }
  return {
    ok: results.every((result) => asRecord(result)?.ok === true),
    action: "apply",
    environment,
    executor: "d601-registry-artifact-consumer",
    dryRun: options.dryRun,
    startedAt,
    finishedAt: nowIso(),
    results,
  };
}

export async function runProdArtifactApplyNow(manifest: DeployManifest, options: DeployOptions): Promise<Record<string, unknown>> {
  const selected = selectedEnvironmentServices(manifest, options.serviceId);
  const unsupported = selected.filter((service) => !prodArtifactConsumerServiceIds.has(service.id));
  if (unsupported.length > 0) return prodArtifactUnsupportedResult(unsupported);
  if (!options.dryRun) {
    const blocked = selected.filter((service) => prodArtifactLiveApplyBlockedServiceIds.has(service.id));
    if (blocked.length > 0) return prodArtifactLiveApplyBlockedResult(blocked);
  }
  return runArtifactConsumerApplyNow(manifest, options, "prod", selected);
}

export function prodArtifactApplyJob(args: string[], options: DeployOptions): Record<string, unknown> {
  if (!options.runNow && options.dryRun) {
    throw new Error("deploy apply --env prod --dry-run should run in the foreground so the plan is visible immediately");
  }
  const runArgs = args.includes("--run-now") ? args : [...args, "--run-now"];
  if (options.serviceId === null) {
    throw new Error("deploy apply --env prod without --service is not allowed for mixed artifact consumers; use --service to avoid applying supervisor-gated services accidentally");
  }
  if (prodArtifactLiveApplyBlockedServiceIds.has(options.serviceId)) {
    return prodArtifactLiveApplyBlockedResult([{
      id: options.serviceId,
      repo: "",
      commitId: options.commitOverride ?? "",
    }]);
  }
  const command = [process.execPath, rootPath("scripts", "cli.ts"), "deploy", ...runArgs];
  const source = `${deployEnvironmentTargets.prod.gitRef}:deploy.json#environments.prod`;
  const job = startJob("deploy_prod_artifact_apply", command, `Deploy prod artifact consumer from ${source}${options.serviceId === null ? "" : ` service=${options.serviceId}`}`);
  return {
    ok: true,
    mode: "async-job",
    executor: "d601-registry-artifact-consumer",
    job,
    statusCommand: `bun scripts/cli.ts job status ${job.id}`,
    tailCommand: `bun scripts/cli.ts job status ${job.id} --tail-bytes 30000`,
    note: "Production CD continues in the background: D601 registry commit-pinned image check, pull/import, rollout, image-label and live health commit verification.",
  };
}

export function devArtifactApplyJob(args: string[], options: DeployOptions): Record<string, unknown> {
  if (!options.runNow && options.dryRun) {
    throw new Error("deploy apply --env dev --dry-run should run in the foreground so the plan is visible immediately");
  }
  const runArgs = args.includes("--run-now") ? args : [...args, "--run-now"];
  const command = [process.execPath, rootPath("scripts", "cli.ts"), "deploy", ...runArgs];
  const source = `${deployEnvironmentTargets.dev.gitRef}:deploy.json#environments.dev`;
  const job = startJob("deploy_dev_artifact_apply", command, `Validate dev artifact consumer from ${source}${options.serviceId === null ? "" : ` service=${options.serviceId}`}`);
  return {
    ok: true,
    mode: "async-job",
    executor: "d601-registry-artifact-consumer",
    job,
    statusCommand: `bun scripts/cli.ts job status ${job.id}`,
    tailCommand: `bun scripts/cli.ts job status ${job.id} --tail-bytes 30000`,
    note: "Dev artifact consumer continues in the background: D601 registry commit-pinned image check, pull/import, rollout or validation, image-label and live health commit verification.",
  };
}

export async function runApplyNow(config: UniDeskConfig, manifest: DeployManifest, options: DeployOptions): Promise<Record<string, unknown>> {
  const selected = selectServices(config, manifest, options.serviceId);
  const startedAt = nowIso();
  const results = [];
  for (const item of selected) {
    progressLine("service", "reconcile", { serviceId: item.config.id, providerId: item.config.providerId, mode: item.config.deployment.mode });
    results.push(await applyOneService(config, item.config, item.desired, options));
    const latest = results.at(-1) as Record<string, unknown>;
    if (latest.ok !== true) break;
  }
  return {
    ok: results.every((result) => result.ok === true),
    action: "apply",
    file: options.file,
    dryRun: options.dryRun,
    startedAt,
    finishedAt: nowIso(),
    results,
  };
}

export function applyJob(config: UniDeskConfig, args: string[], options: DeployOptions): Record<string, unknown> {
  const runArgs = args.includes("--run-now") ? args : [...args, "--run-now"];
  const command = [process.execPath, rootPath("scripts", "cli.ts"), "deploy", ...runArgs];
  const source = options.environment === null ? options.file : `${deployEnvironmentTargets[options.environment].gitRef}:deploy.json#environments.${options.environment}`;
  const job = startJob("deploy_apply", command, `Reconcile services from ${source}${options.serviceId === null ? "" : ` service=${options.serviceId}`}`);
  return {
    ok: true,
    mode: "async-job",
    job,
    statusCommand: `bun scripts/cli.ts job status ${job.id}`,
    tailCommand: `bun scripts/cli.ts job status ${job.id} --tail-bytes 30000`,
    note: "Deployment continues in the background: target-side fetch, one-shot proxied build, deploy, stamp, and live health verification.",
    configProject: config.project.name,
  };
}
