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

// Moved mechanically from scripts/src/deploy.ts:2840-3031 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 { k3sGuardShellLines, defaultNativeKubeconfig } from "../k3s-target-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 { DeployManifestService, DeployOptions, ServiceRuntimeState, StepResult } from "./types";
import { applyK8sScript, cleanupLegacyDirectCodeQueueScript, rolloutK8sScript, stampK8sScript, verifyK8sImagesScript } from "./health";
import { asRecord, compactTail, elapsedMs, isUnideskRepo, nowIso, parseFullCommit, progressLine } from "./options";
import { dockerBuildTimeoutMs, targetExportDir, targetIsMain, targetWorkDir } from "./paths";
import { healthVerify, manifestEnvironmentForService, pushStep, readRuntimeState, step } from "./remote";
import { buildDirectImageScript, buildImageScript, codeQueueSourcePreflightScript, composeDeployScript, ensureNativeK3sScript, imageLabelVerifyScript, importK3sImageScript, patchDevK3sManifestScript, prepareSourceScript, syncDevFrontendAuthScript, syncK8sControlManifestsScript, syncSourceScript } from "./scripts";
import { isTargetSideMaintenanceDeployBlocked, isDevArtifactConsumerService, isDevK3sDeployService, isDirectComposeDeployMode, unsupportedReason } from "./service-plan";

export async function ensureGithubSshIdentityStep(config: UniDeskConfig, service: UniDeskMicroserviceConfig): Promise<StepResult> {
  const startedAt = nowIso();
  const startedMs = Date.now();
  progressLine("github-ssh-identity", "start", { serviceId: service.id, providerId: service.providerId });
  try {
    const result = await ensureGithubSshIdentityForProvider(config, service.providerId);
    progressLine("github-ssh-identity", result.ok ? "succeeded" : "failed", {
      serviceId: service.id,
      providerId: service.providerId,
      elapsedMs: elapsedMs(startedMs),
      fingerprint: result.fingerprint,
      seededFromLocal: result.seededFromLocal,
      detail: result.ok ? result.detail : compactTail(result.detail, 1200),
    });
    return {
      step: "github-ssh-identity",
      ok: result.ok,
      detail: result.detail,
      startedAt,
      finishedAt: nowIso(),
      raw: result.raw,
    };
  } catch (error) {
    const detail = error instanceof Error ? error.message : String(error);
    progressLine("github-ssh-identity", "failed", {
      serviceId: service.id,
      providerId: service.providerId,
      elapsedMs: elapsedMs(startedMs),
      detail: compactTail(detail, 1200),
    });
    return { step: "github-ssh-identity", ok: false, detail, startedAt, finishedAt: nowIso(), raw: null };
  }
}

export async function runDevArtifactConsumerService(
  service: UniDeskMicroserviceConfig,
  desired: DeployManifestService,
  options: DeployOptions,
  before: ServiceRuntimeState,
): Promise<Record<string, unknown>> {
  const commit = options.commitOverride ?? desired.commitId;
  const artifactArgs = [
    "deploy-service",
    "--env", "dev",
    "--service", service.id,
    "--commit", commit,
    "--source-repo", desired.repo,
    "--timeout-ms", String(options.timeoutMs),
    "--provider-id", options.providerId,
    "--run-now",
    ...(options.dryRun ? ["--dry-run"] : []),
  ];
  progressLine("dev-artifact-cd", options.dryRun ? "dry-run" : "reconcile", { serviceId: service.id, commit });
  const result = await runArtifactRegistryCommand(artifactArgs);
  const ok = asRecord(result)?.ok === true;
  return {
    ok,
    action: ok ? "deployed" : "failed",
    environment: "dev",
    executor: "d601-registry-artifact-consumer",
    resolvedCommit: commit,
    before,
    after: result,
    steps: [{
      step: "artifact-registry-consumer",
      ok,
      detail: JSON.stringify(result),
      startedAt: nowIso(),
      finishedAt: nowIso(),
      raw: result,
    }],
  };
}

export async function applyOneService(config: UniDeskConfig, service: UniDeskMicroserviceConfig, desired: DeployManifestService, options: DeployOptions): Promise<Record<string, unknown>> {
  const steps: StepResult[] = [];
  const startedAt = nowIso();
  const reason = unsupportedReason(service);
  if (reason !== null) return { ok: false, serviceId: service.id, skipped: true, reason, steps };
  const before = await readRuntimeState(config, service, desired);
  if (!options.force && before.upToDate) return { ok: true, serviceId: service.id, action: "noop", before, steps };

  if (manifestEnvironmentForService(service) === "dev" && isDevArtifactConsumerService(service)) {
    const artifact = await runDevArtifactConsumerService(service, desired, options, before);
    return {
      ...artifact,
      before,
      serviceId: service.id,
      startedAt,
      finishedAt: nowIso(),
    };
  }

  if (options.dryRun) return { ok: true, serviceId: service.id, action: "would-deploy", before, steps };

  if (!options.dryRun && isTargetSideMaintenanceDeployBlocked(service)) {
    return {
      ok: false,
      serviceId: service.id,
      skipped: true,
      reason: `D601 target-side deployment is allowed only for k3sctl-adapter as a control-bridge maintenance path; artifact consumers must use registry CD. ${service.id} is not enabled. Use ci run-dev-e2e for smoke verification.`,
      steps,
    };
  }

  const runId = `${Date.now().toString(36)}-${Math.random().toString(16).slice(2, 8)}`;
  const exportDir = targetExportDir(service, runId);
  if (!targetIsMain(service) && isUnideskRepo(desired.repo)) {
    const identity = await ensureGithubSshIdentityStep(config, service);
    if (!pushStep(steps, identity)) return { ok: false, serviceId: service.id, startedAt, finishedAt: nowIso(), before, steps };
  }
  const prepare = await step(config, service, "prepare-source", prepareSourceScript(service, desired, exportDir), targetIsMain(service) ? repoRoot : "/home/ubuntu", Math.min(options.timeoutMs, 180_000), !targetIsMain(service));
  if (!pushStep(steps, prepare)) return { ok: false, serviceId: service.id, startedAt, finishedAt: nowIso(), before, steps };
  const resolvedCommit = parseFullCommit(prepare.detail) || parseFullCommit(JSON.stringify(prepare.raw));
  if (resolvedCommit.length !== 40) {
    const failure = { step: "resolve-commit", ok: false, detail: "prepare-source did not expose a full 40-char commit", startedAt: nowIso(), finishedAt: nowIso(), raw: prepare };
    steps.push(failure);
    return { ok: false, serviceId: service.id, startedAt, finishedAt: nowIso(), before, steps };
  }

  const sync = await step(config, service, "sync-source", syncSourceScript(service, exportDir), targetIsMain(service) ? repoRoot : "/home/ubuntu", 90_000, !targetIsMain(service));
  if (!pushStep(steps, sync)) return { ok: false, serviceId: service.id, startedAt, finishedAt: nowIso(), resolvedCommit, before, steps };

  const codeQueueSourcePreflight = codeQueueSourcePreflightScript(service);
  if (codeQueueSourcePreflight.length > 0) {
    const sourcePreflight = await step(config, service, "code-queue-hostpath-source-preflight", codeQueueSourcePreflight, targetWorkDir(service), 60_000, !targetIsMain(service));
    if (!pushStep(steps, sourcePreflight)) return { ok: false, serviceId: service.id, startedAt, finishedAt: nowIso(), resolvedCommit, before, steps };
  }

  const controlManifestSyncScript = syncK8sControlManifestsScript(service);
  if (controlManifestSyncScript.length > 0) {
    const controlManifestStep = isDevK3sDeployService(service) ? "verify-target-k3s-manifest" : "sync-k3s-control-manifests";
    const controlManifestSync = await step(config, service, controlManifestStep, controlManifestSyncScript, targetIsMain(service) ? repoRoot : "/home/ubuntu", 90_000, !targetIsMain(service));
    if (!pushStep(steps, controlManifestSync)) return { ok: false, serviceId: service.id, startedAt, finishedAt: nowIso(), resolvedCommit, before, steps };
  }

  if (isDevK3sDeployService(service)) {
    const patchManifest = await step(config, service, "patch-dev-k3s-manifest", patchDevK3sManifestScript(service, desired, resolvedCommit), targetWorkDir(service), 60_000, false);
    if (!pushStep(steps, patchManifest)) return { ok: false, serviceId: service.id, startedAt, finishedAt: nowIso(), resolvedCommit, before, steps };
  }

  if (isDevK3sDeployService(service) && service.id === "frontend") {
    const authSync = await step(config, service, "sync-dev-frontend-auth", syncDevFrontendAuthScript(config), "/home/ubuntu", 60_000, false);
    if (!pushStep(steps, authSync)) return { ok: false, serviceId: service.id, startedAt, finishedAt: nowIso(), resolvedCommit, before, steps };
  }

  const buildScript = isDirectComposeDeployMode(service)
    ? buildDirectImageScript(service, desired, resolvedCommit)
    : buildImageScript(service, desired, resolvedCommit);
  const build = await step(config, service, "docker-build", buildScript, targetWorkDir(service), dockerBuildTimeoutMs(service, options), !targetIsMain(service));
  if (!pushStep(steps, build)) return { ok: false, serviceId: service.id, startedAt, finishedAt: nowIso(), resolvedCommit, before, steps };

  if (service.deployment.mode === "k3sctl-managed") {
    const nativeK3s = await step(config, service, "ensure-native-k3s", ensureNativeK3sScript(), "/home/ubuntu", 600_000, true);
    if (!pushStep(steps, nativeK3s)) return { ok: false, serviceId: service.id, startedAt, finishedAt: nowIso(), resolvedCommit, before, steps };
    const imageImport = await step(config, service, "import-k3s-image", importK3sImageScript(service), targetWorkDir(service), Math.min(options.timeoutMs, 900_000), true);
    if (!pushStep(steps, imageImport)) return { ok: false, serviceId: service.id, startedAt, finishedAt: nowIso(), resolvedCommit, before, steps };
    const apply = await step(config, service, "kubectl-apply", applyK8sScript(service), targetWorkDir(service), 60_000, false);
    if (!pushStep(steps, apply)) return { ok: false, serviceId: service.id, startedAt, finishedAt: nowIso(), resolvedCommit, before, steps };
    const k8sImages = await step(config, service, "k8s-image-preflight", verifyK8sImagesScript(service), targetWorkDir(service), 60_000, false);
    if (!pushStep(steps, k8sImages)) return { ok: false, serviceId: service.id, startedAt, finishedAt: nowIso(), resolvedCommit, before, steps };
    const stamp = await step(config, service, "stamp-deploy-commit", stampK8sScript(service, desired, resolvedCommit), targetWorkDir(service), 60_000, false);
    if (!pushStep(steps, stamp)) return { ok: false, serviceId: service.id, startedAt, finishedAt: nowIso(), resolvedCommit, before, steps };
    const rollout = await step(config, service, "rollout", rolloutK8sScript(service), targetWorkDir(service), 240_000, true);
    if (!pushStep(steps, rollout)) return { ok: false, serviceId: service.id, startedAt, finishedAt: nowIso(), resolvedCommit, before, steps };
    const legacyCleanupScript = cleanupLegacyDirectCodeQueueScript(service);
    if (legacyCleanupScript.length > 0) {
      const cleanup = await step(config, service, "legacy-direct-cleanup", legacyCleanupScript, targetWorkDir(service), 90_000, false);
      if (!pushStep(steps, cleanup)) return { ok: false, serviceId: service.id, startedAt, finishedAt: nowIso(), resolvedCommit, before, steps };
    }
  } else {
    const deploy = await step(config, service, "compose-up", composeDeployScript(service, desired, resolvedCommit), targetIsMain(service) ? repoRoot : targetWorkDir(service), 180_000, !targetIsMain(service));
    if (!pushStep(steps, deploy)) return { ok: false, serviceId: service.id, startedAt, finishedAt: nowIso(), resolvedCommit, before, steps };
    const imageVerify = await step(config, service, "image-label-verify", imageLabelVerifyScript(service, resolvedCommit), targetIsMain(service) ? repoRoot : targetWorkDir(service), 60_000, false);
    if (!pushStep(steps, imageVerify)) return { ok: false, serviceId: service.id, startedAt, finishedAt: nowIso(), resolvedCommit, before, steps };
  }

  const health = await healthVerify(config, service, desired, resolvedCommit, 90_000);
  steps.push(health);
  return {
    ok: health.ok,
    serviceId: service.id,
    action: "deployed",
    startedAt,
    finishedAt: nowIso(),
    resolvedCommit,
    before,
    after: health.raw,
    steps,
  };
}
