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

// Moved mechanically from scripts/src/artifact-registry.ts:1319-1658 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 { k3sGuardShellLines } from "../k3s-target-guard";
import { composeRuntimeEnvValue } from "../runtime-env";

import type { ArtifactConsumerSpec, ArtifactConsumerTarget, ArtifactDeployEnvironment, ArtifactRegistryOptions, SupportedArtifactConsumerService } from "./types";
import { artifactConsumerSpecs } from "./catalog";
import { supportedArtifactConsumerServices } from "./types";

export function artifactRegistryCommandTail(result: CommandResult): Record<string, unknown> {
  return {
    command: result.command.length > 7 ? [...result.command.slice(0, 7), "<readonly-script>"] : result.command,
    exitCode: result.exitCode,
    signal: result.signal,
    timedOut: result.timedOut,
    stdoutTail: result.stdout.slice(-4000),
    stderrTail: result.stderr.slice(-4000),
  };
}

export function commandTail(result: CommandResult): Record<string, unknown> {
  return artifactRegistryCommandTail(result);
}

export function artifactConsumerSpec(serviceId: string, environment: ArtifactDeployEnvironment | null): ArtifactConsumerSpec | null {
  const key = environment === null || environment === "prod" ? serviceId : `${environment}:${serviceId}`;
  const explicit = artifactConsumerSpecs[key];
  if (explicit !== undefined) return explicit;
  const shared = artifactConsumerSpecs[serviceId];
  return shared ?? null;
}

export function supportedArtifactConsumers(): Array<{ environment: ArtifactDeployEnvironment; serviceId: SupportedArtifactConsumerService; kind: ArtifactConsumerSpec["kind"] }> {
  return Object.values(artifactConsumerSpecs).flatMap((spec) => Object.keys(spec.targets).map((environment) => ({
    environment: environment as ArtifactDeployEnvironment,
    serviceId: spec.serviceId,
    kind: spec.kind,
  })));
}

export function artifactConsumerTarget(spec: ArtifactConsumerSpec, environment: ArtifactDeployEnvironment | null): ArtifactConsumerTarget | null {
  return spec.targets[environment ?? "prod"] ?? null;
}

export function unsupportedService(serviceId: string, options: ArtifactRegistryOptions): Record<string, unknown> {
  return {
    ok: false,
    supported: false,
    error: "unsupported",
    serviceId,
    environment: options.environment ?? "prod",
    providerId: options.providerId,
    reason: "No standardized D601 registry CD consumer is implemented for this service.",
    supportedServices: supportedArtifactConsumerServices,
    supportedConsumers: supportedArtifactConsumers(),
    policy: "unsupported services must not silently fall back to maintenance-channel source builds or legacy direct deployment",
  };
}

export function unsupportedEnvironment(spec: ArtifactConsumerSpec, options: ArtifactRegistryOptions): Record<string, unknown> {
  const environment = options.environment ?? "prod";
  const codeQueueGuard = spec.serviceId === "code-queue" ? codeQueueSelfBootstrapGuard(environment) : undefined;
  const reason = spec.serviceId === "code-queue" && environment === "prod"
    ? spec.prodLiveBlockReason ?? "Code Queue production artifact deploy, rollout and manifest mutation are intentionally unsupported in this phase."
    : `No standardized ${environment} registry artifact consumer is implemented for ${spec.serviceId}.`;
  return {
    ok: false,
    supported: false,
    error: "unsupported-environment",
    serviceId: spec.serviceId,
    environment,
    providerId: options.providerId,
    reason,
    supportedEnvironments: Object.keys(spec.targets),
    requiresSupervisorApproval: spec.serviceId === "code-queue",
    selfBootstrapGuard: codeQueueGuard,
    affectedRuntime: spec.serviceId === "code-queue" ? codeQueueAffectedRuntime(environment, false) : undefined,
    excludedTargets: spec.serviceId === "code-queue" ? codeQueueExcludedTargets(environment) : undefined,
    policy: "artifact CD must not silently fall back to maintenance-channel source builds or legacy direct deployment",
  };
}

export function artifactConsumerLivePolicy(spec: ArtifactConsumerSpec, environment: ArtifactDeployEnvironment): "enabled" | "supervisor-only" | "unsupported" {
  return environment === "prod" ? spec.prodLiveApply : spec.devLiveApply ?? "enabled";
}

export function artifactConsumerLiveBlockReason(spec: ArtifactConsumerSpec, environment: ArtifactDeployEnvironment): string | null {
  if (spec.runtimeVerificationBlockReason !== undefined) return spec.runtimeVerificationBlockReason;
  return environment === "prod" ? spec.prodLiveBlockReason ?? null : spec.devLiveBlockReason ?? null;
}

export function codeQueueExcludedTargets(environment: ArtifactDeployEnvironment): Record<string, unknown>[] {
  const excluded: Record<string, unknown>[] = [
    {
      namespace: "unidesk",
      deployments: ["code-queue", "code-queue-read", "code-queue-write", "d601-provider-egress-proxy", "d601-tcp-egress-gateway"],
      reason: "production Code Queue execution-plane Deployments are outside this artifact consumer and must not be mutated by dry-run or self-bootstrap automation.",
    },
    {
      operations: ["restart scheduler", "restart runner", "interrupt active tasks", "cancel active tasks"],
      reason: "artifact CD planning must not alter scheduler/runner lifecycle or active task control state.",
    },
  ];
  if (environment === "dev") {
    excluded.push({
      namespace: "unidesk-dev",
      nonDryRunMutation: "requires explicit supervisor or human authorization outside the running Code Queue task",
      reason: "DEV is the only Code Queue artifact consumer target, but self-bootstrap automation may produce dry-run evidence only.",
    });
  }
  return excluded;
}

export function codeQueueAffectedRuntime(environment: ArtifactDeployEnvironment, targetPlanned: boolean): Record<string, unknown> {
  return {
    dryRunMutation: false,
    plannedTarget: targetPlanned && environment === "dev"
      ? {
        namespace: "unidesk-dev",
        deployments: ["code-queue-scheduler-dev", "code-queue-read-dev", "code-queue-write-dev", "d601-dev-provider-egress-proxy"],
      }
      : null,
    productionNamespaceAffected: false,
    productionSchedulerRunnerAffected: false,
    activeTaskControlAffected: false,
    activeTaskInterruptCancelAffected: false,
  };
}

export function codeQueueSelfBootstrapGuard(environment: ArtifactDeployEnvironment): Record<string, unknown> {
  return {
    check: "code-queue-self-bootstrap-guard",
    serviceId: "code-queue",
    selfBootstrapBlocked: true,
    requiresSupervisorApproval: true,
    actorBoundary: "a running Code Queue task may publish contract evidence and dry-run plans only; it must not deploy Code Queue itself",
    devApply: "requires explicit supervisor or human authorization outside the running Code Queue task",
    prodApply: "unsupported until a separate supervisor-approved production Code Queue CD design exists",
    allowedWithoutApproval: [
      "CI artifact publication",
      "deploy plan",
      "deploy apply --dry-run",
      "artifact-registry deploy-service --dry-run",
    ],
    forbiddenActions: [
      "self-deploy Code Queue from Code Queue",
      "run a non-dry-run DEV apply from Code Queue",
      "production namespace mutation",
      "production manifest mutation",
      "scheduler or runner restart",
      "active task interrupt",
      "active task cancel",
    ],
    environment,
  };
}

export function codeQueueMgrSelfBootstrapGuard(environment: ArtifactDeployEnvironment, requiresSupervisorApproval: boolean): Record<string, unknown> {
  return {
    check: "code-queue-mgr-control-plane-guard",
    serviceId: "code-queue-mgr",
    selfBootstrapBlocked: true,
    requiresSupervisorApproval,
    actorBoundary: "dry-run is allowed, but a running Code Queue task must not replace the Code Queue control-plane sidecar as part of delivering Code Queue itself",
    targetScope: "main-server Compose service code-queue-mgr / container code-queue-mgr-backend only",
    prodApply: "requires explicit supervisor confirmation",
    environment,
    forbiddenActions: [
      "mutate D601 Code Queue scheduler",
      "mutate D601 Code Queue runner",
      "restart active tasks",
      "interrupt active tasks",
      "cancel active tasks",
    ],
  };
}

export function authBrokerCredentialMountGuard(environment: ArtifactDeployEnvironment, requiresSupervisorApproval: boolean): Record<string, unknown> {
  return {
    check: "auth-broker-credential-mount-guard",
    serviceId: "auth-broker",
    requiresSupervisorApproval,
    actorBoundary: "dry-run and contract evidence are allowed, but live broker startup needs explicit review of credential reference mounting and private exposure",
    targetScope: "main-server Compose profile auth-broker / container auth-broker-backend only",
    composeProfileRequired: "auth-broker",
    publicPortAllowed: false,
    registryCredentialsProxied: false,
    deployCredentialsProxied: false,
    environment,
    forbiddenActions: [
      "write real GitHub token into config.json, deploy.json, or docker-compose.yml",
      "publish auth-broker on a public port",
      "restart backend-core, provider-gateway, or Code Queue as part of broker dry-run registration",
      "grant registry, deploy, database, k3s, provider token, or host SSH permissions",
    ],
  };
}

export function artifactConsumerSelfBootstrapGuard(
  spec: ArtifactConsumerSpec,
  environment: ArtifactDeployEnvironment,
  requiresSupervisorApproval: boolean,
): Record<string, unknown> | undefined {
  if (spec.serviceId === "code-queue") return codeQueueSelfBootstrapGuard(environment);
  if (spec.serviceId === "code-queue-mgr") return codeQueueMgrSelfBootstrapGuard(environment, requiresSupervisorApproval);
  if (spec.serviceId === "auth-broker") return authBrokerCredentialMountGuard(environment, requiresSupervisorApproval);
  return undefined;
}

export function artifactConsumerLiveBlock(spec: ArtifactConsumerSpec, options: ArtifactRegistryOptions): Record<string, unknown> | null {
  const environment = options.environment ?? "prod";
  const livePolicy = artifactConsumerLivePolicy(spec, environment);
  const liveReason = artifactConsumerLiveBlockReason(spec, environment);
  if (spec.runtimeVerification === "blocked") {
    return {
      ok: false,
      supported: false,
      liveApplyAllowed: false,
      error: "runtime-verification-blocked",
      serviceId: spec.serviceId,
      environment,
      providerId: options.providerId,
      reason: spec.runtimeVerificationBlockReason ?? `${spec.serviceId} does not yet satisfy strict runtime deploy commit verification.`,
      requiredBeforeLiveApply: [
        "runtime Compose env injects deploy commit/requestedCommit metadata",
        "service health reports deploy.commit and deploy.requestedCommit for strict verification",
      ],
      policy: "artifact CD must not accept a healthy old service or silently fall back to legacy rebuild paths",
    };
  }
  if (livePolicy === "enabled") return null;
  if (livePolicy === "supervisor-only") {
    return {
      ok: false,
      supported: true,
      liveApplyAllowed: false,
      error: "supervisor-confirmation-required",
      serviceId: spec.serviceId,
      environment,
      providerId: options.providerId,
      reason: liveReason ?? `${spec.serviceId} ${environment} artifact apply requires supervisor confirmation.`,
      requiresSupervisorApproval: true,
      selfBootstrapGuard: artifactConsumerSelfBootstrapGuard(spec, environment, true),
      affectedRuntime: spec.serviceId === "code-queue" ? codeQueueAffectedRuntime(environment, true) : undefined,
      excludedTargets: spec.serviceId === "code-queue" ? codeQueueExcludedTargets(environment) : undefined,
      dryRunCommandShape: `bun scripts/cli.ts artifact-registry deploy-service --env ${environment} --service ${spec.serviceId} --commit <full-sha> --dry-run`,
      policy: "worker automation must not perform live apply for this supervisor-gated artifact consumer",
    };
  }
  return {
    ok: false,
    supported: false,
    liveApplyAllowed: false,
    error: "artifact-consumer-blocked",
    serviceId: spec.serviceId,
    environment,
    providerId: options.providerId,
    reason: liveReason ?? `${spec.serviceId} does not yet satisfy the artifact consumer runtime verification contract.`,
    requiresSupervisorApproval: spec.serviceId === "code-queue",
    selfBootstrapGuard: spec.serviceId === "code-queue" ? codeQueueSelfBootstrapGuard(environment) : undefined,
    affectedRuntime: spec.serviceId === "code-queue" ? codeQueueAffectedRuntime(environment, false) : undefined,
    excludedTargets: spec.serviceId === "code-queue" ? codeQueueExcludedTargets(environment) : undefined,
    requiredBeforeLiveApply: [
      "CI can publish a commit-pinned image with matching service id, source repo, source commit, and Dockerfile labels",
      "runtime Compose env injects deploy commit/requestedCommit metadata",
      "service health reports deploy.commit and deploy.requestedCommit for strict verification",
    ],
    policy: "do not silently fall back to server rebuild, dirty worktrees, mutable tags, or source builds on the runtime target",
  };
}

export function artifactImageRef(options: ArtifactRegistryOptions, spec: ArtifactConsumerSpec, commit: string): string {
  return `127.0.0.1:${options.port}/${(options.dryRun ? options.deployJsonService?.artifact?.repository : undefined) ?? spec.registryRepository}:${commit}`;
}

export function sourceRepoFor(options: ArtifactRegistryOptions, spec: ArtifactConsumerSpec): string {
  return options.sourceRepoExplicit ? options.sourceRepo : spec.sourceRepo ?? options.sourceRepo;
}

export function deployRefFor(options: ArtifactRegistryOptions, spec: ArtifactConsumerSpec): string {
  const target = artifactConsumerTarget(spec, options.environment);
  return options.deployRef ?? (options.dryRun ? options.deployJsonService?.consumer?.targetRef : undefined) ?? target?.deployRef ?? `deploy.json#environments.${options.environment ?? "prod"}.services.${spec.serviceId}`;
}

export function deployJsonServiceForOptions(options: ArtifactRegistryOptions, spec: ArtifactConsumerSpec, environment: ArtifactDeployEnvironment): DeployJsonServiceContract | null {
  if (!options.dryRun) return null;
  if (options.deployJsonService !== null) return options.deployJsonService;
  if (environment === "dev" && (spec.serviceId === "decision-center" || spec.serviceId === "mdtodo")) {
    return readDeployJsonServiceContractFromFile(environment, spec.serviceId);
  }
  return null;
}

export function targetImageFor(options: ArtifactRegistryOptions, target: ArtifactConsumerTarget): string {
  return (options.dryRun ? options.deployJsonService?.consumer?.target.stableImage : undefined) ?? target.targetImage;
}

export function targetCommitImageFor(options: ArtifactRegistryOptions, target: ArtifactConsumerTarget, commit: string): string {
  const stableImage = options.dryRun ? options.deployJsonService?.consumer?.target.stableImage : undefined;
  return stableImage === undefined ? target.targetCommitImage(commit) : deployJsonCommitImage(stableImage, commit);
}

export function registryRepositoryFor(options: ArtifactRegistryOptions, spec: ArtifactConsumerSpec): string {
  return (options.dryRun ? options.deployJsonService?.artifact?.repository : undefined) ?? spec.registryRepository;
}

export function artifactRegistryDeployJsonMirrors(
  options: ArtifactRegistryOptions,
  spec: ArtifactConsumerSpec,
  target: ArtifactConsumerTarget,
): DeployJsonExecutorMirror[] {
  const mirrors: DeployJsonExecutorMirror[] = [
    {
      surface: "artifact-registry-executor",
      artifact: {
        kind: "source-build",
        repository: spec.registryRepository,
        tag: "commitId",
      },
      consumer: {
        kind: spec.kind === "d601-k3s" ? "d601-k3s-managed" : spec.kind,
        noRuntimeSourceBuild: true,
        target: target.k3s === undefined ? undefined : {
          namespace: target.k3s.namespace,
          deployment: target.k3s.deploymentName,
          service: target.k3s.serviceName,
          containerName: target.k3s.containerName,
          stableImage: target.targetImage,
          manifestRepoPath: target.k3s.manifestRepoPath,
        },
      },
      runtime: target.k3s === undefined ? undefined : {
        containerPort: target.k3s.servicePort,
        servicePort: target.k3s.servicePort,
        healthPath: target.k3s.healthPath,
        health: options.deployJsonService?.runtime?.health === undefined ? undefined : {
          deployMetadataRequired: true,
        },
      },
    },
  ];
  if (options.deployJsonService !== null) {
    const manifestMirror = k3sManifestExecutorMirror(options.deployJsonService);
    if (manifestMirror !== null) mirrors.push(manifestMirror);
  }
  return mirrors;
}
