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

// Moved mechanically from scripts/src/artifact-registry.ts:2117-2231 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 { ArtifactRegistryOptions, RenderedFile, RuntimeSecretPresence, RuntimeSecretRequirement } from "./types";
import { renderBundle, rootExecPrelude, shellQuote } from "./bundle";
import { commandTail } from "./consumer";
import { runReadonlyStatus } from "./readonly";
import { runRemoteScript } from "./remote";
import { plan } from "./status";

export function remoteWriteFileCommand(item: RenderedFile): string {
  const encoded = Buffer.from(item.content, "utf8").toString("base64");
  const rootOwned = item.path.startsWith("/etc/");
  return [
    `target=${shellQuote(item.path)}`,
    "tmp=$(mktemp /tmp/unidesk-artifact-registry.XXXXXX)",
    "trap 'rm -f \"$tmp\"' EXIT",
    `printf %s ${shellQuote(encoded)} | base64 -d > "$tmp"`,
    `if [ ${rootOwned ? "1" : "0"} = 1 ]; then`,
    "  root_exec mkdir -p \"$(dirname \"$target\")\"",
    `  root_exec install -m ${shellQuote(item.mode)} "$tmp" "$target"`,
    "else",
    "  mkdir -p \"$(dirname \"$target\")\"",
    `  install -m ${shellQuote(item.mode)} "$tmp" "$target"`,
    "fi",
    `echo ${shellQuote(`artifact_registry_file_written path=${item.path} sha256=${item.sha256}`)}`,
  ].join("\n");
}

export function install(options: ArtifactRegistryOptions): Record<string, unknown> {
  const bundle = renderBundle(options);
  const script = [
    "set -euo pipefail",
    "command -v docker >/dev/null",
    "docker compose version >/dev/null",
    "command -v systemctl >/dev/null",
    rootExecPrelude(),
    `mkdir -p ${shellQuote(bundle.paths.baseDir)} ${shellQuote(bundle.paths.storage)}`,
    ...bundle.files.map(remoteWriteFileCommand),
    "root_exec systemctl daemon-reload",
    `root_exec systemctl enable --now ${shellQuote(options.unitName)}`,
    "sleep 2",
    `curl -fsS http://${options.host}:${options.port}/v2/ >/dev/null`,
  ].join("\n");
  const command = runRemoteScript(options, script, Math.max(options.timeoutMs, 120_000));
  const status = runReadonlyStatus(options, true);
  return {
    ok: command.exitCode === 0 && !command.timedOut && status.ok === true,
    dryRun: false,
    mutation: true,
    providerId: options.providerId,
    render: {
      paths: bundle.paths,
      files: bundle.files.map((item) => ({ path: item.path, mode: item.mode, sha256: item.sha256 })),
    },
    installCommand: commandTail(command),
    health: status,
  };
}

export function installDryRun(options: ArtifactRegistryOptions): Record<string, unknown> {
  const bundle = renderBundle(options);
  return {
    ok: true,
    dryRun: true,
    readonly: true,
    mutation: false,
    providerId: options.providerId,
    plan: plan(options),
    render: bundle,
    intendedRemoteActions: [
      `mkdir -p ${options.baseDir} ${options.storageDir}`,
      `write ${bundle.paths.config}`,
      `write ${bundle.paths.compose}`,
      `write ${bundle.paths.unit}`,
      "systemctl daemon-reload",
      `systemctl enable --now ${options.unitName}`,
      `curl -fsS http://${options.host}:${options.port}/v2/`,
    ],
    note: "Dry run only; no D601 runtime files or services were changed.",
  };
}

export function composeLockScript(innerScript: string): string {
  const lockDir = rootPath(".state", "locks");
  const lockPath = rootPath(".state", "locks", "server-compose.lock");
  return [
    "set -euo pipefail",
    `mkdir -p ${shellQuote(lockDir)}`,
    `echo ${shellQuote(`compose_lock_wait ${lockPath}`)}`,
    `flock ${shellQuote(lockPath)} bash -lc ${shellQuote(innerScript)}`,
  ].join("; ");
}

export function findEnvValueLength(raw: string, key: string): number {
  for (const line of raw.split(/\r?\n/u)) {
    if (!line.trim() || line.trimStart().startsWith("#")) continue;
    const index = line.indexOf("=");
    if (index <= 0) continue;
    if (line.slice(0, index) !== key) continue;
    let value = line.slice(index + 1);
    if (value.startsWith("\"") && value.endsWith("\"")) {
      try {
        value = JSON.parse(value) as string;
      } catch {
        value = value.slice(1, -1);
      }
    }
    return value.length;
  }
  return 0;
}

export function runtimeSecretPresenceFromEnvText(envText: string, requirements: RuntimeSecretRequirement[]): RuntimeSecretPresence[] {
  return requirements.map((requirement) => {
    const length = findEnvValueLength(envText, requirement.sourceEnvName);
    return {
      sourceEnvName: requirement.sourceEnvName,
      containerEnvName: requirement.containerEnvName,
      present: length > 0,
      length,
    };
  });
}
