import { createHash, randomBytes } from "node:crypto";
import { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
import { dirname } from "node:path";
import type { UniDeskConfig } from "./config";
import { rootPath } from "./config";
import { runCommand } from "./command";
import { parseEnvFile } from "./secrets";
import {
  hostProxyGuestStatusScript,
  prepareHostProxyGuestBundle,
  type HostProxyGuestBundle,
} from "./platform-infra-host-proxy";
import {
  K3S_CLUSTER_CONFIG_LABEL,
  readK3sClusterConfig,
  type K3sClusterTarget,
} from "./platform-infra-k3s-cluster-config";

type Action = "plan" | "registry-credential-init" | "apply" | "status" | "smoke" | "registry-smoke";

interface Options {
  action: Action;
  targetId?: string;
  confirm: boolean;
}

interface RegistrySecret {
  username: string;
  password: string;
  passwordHash: string;
  fingerprint: string;
}

export async function runPlatformInfraK3sClusterCommand(
  _config: UniDeskConfig,
  args: string[],
): Promise<Record<string, unknown>> {
  const options = parseOptions(args);
  const { target } = readK3sClusterConfig(options.targetId);
  if (options.action === "plan") return plan(target);
  if (options.action === "registry-credential-init") return registryCredentialInit(target, options.confirm);
  if (options.action === "status") return status(target);
  if (options.action === "smoke") return smoke(target);
  if (options.action === "registry-smoke") return registrySmoke(target, options.confirm);
  if (!options.confirm) return { ...plan(target), mode: "dry-run" };
  return apply(target);
}

function parseOptions(args: string[]): Options {
  const action = args[0] ?? "plan";
  const allowed: Action[] = ["plan", "registry-credential-init", "apply", "status", "smoke", "registry-smoke"];
  if (!allowed.includes(action as Action)) {
    throw new Error("platform-infra k3s-cluster usage: plan|registry-credential-init|apply|status|smoke|registry-smoke [--target <id>] [--confirm]");
  }
  return {
    action: action as Action,
    targetId: option(args, "--target") ?? undefined,
    confirm: args.includes("--confirm"),
  };
}

function plan(target: K3sClusterTarget): Record<string, unknown> {
  return {
    ok: true,
    mutation: false,
    targetId: target.id,
    source: K3S_CLUSTER_CONFIG_LABEL,
    topology: "standalone-single-node",
    clusterName: target.clusterName,
    node: {
      id: target.node.id,
      nodeName: target.node.nodeName,
      bootstrapConfigRef: target.node.bootstrapConfigRef,
      hostProxyConfigRef: target.node.hostProxyConfigRef,
      controlRoute: target.node.windowsRoute,
      labels: target.node.labels,
    },
    k3s: {
      version: target.node.k3sVersion,
      writeKubeconfigMode: target.k3s.writeKubeconfigMode,
      maxPods: target.k3s.maxPods,
      disableComponents: target.k3s.disableComponents,
      registryMirror: {
        source: target.k3s.registryMirror.source,
        endpoint: target.k3s.registryMirror.endpoint,
        auth: registryAuthSummary(target),
      },
      registryPullSmoke: target.k3s.registryPullSmoke,
    },
    next: {
      registryCredentialInit: `bun scripts/cli.ts platform-infra k3s-cluster registry-credential-init --target ${target.id} --confirm`,
      apply: `bun scripts/cli.ts platform-infra k3s-cluster apply --target ${target.id} --confirm`,
      status: `bun scripts/cli.ts platform-infra k3s-cluster status --target ${target.id}`,
      smoke: `bun scripts/cli.ts platform-infra k3s-cluster smoke --target ${target.id}`,
      registrySmoke: `bun scripts/cli.ts platform-infra k3s-cluster registry-smoke --target ${target.id} --confirm`,
    },
  };
}

function registryCredentialInit(target: K3sClusterTarget, confirm: boolean): Record<string, unknown> {
  const auth = target.k3s.registryMirror.auth;
  const path = auth.sourceRef;
  const keys = [auth.usernameKey, auth.passwordKey, auth.passwordHashKey];
  if (existsSync(path)) {
    const source = readFileSync(path, "utf8");
    const values = parseEnvFile(source);
    for (const key of keys) {
      if (typeof values[key] !== "string" || values[key]!.length === 0) {
        throw new Error(`${path} contains a partial artifact registry credential; refusing to overwrite`);
      }
    }
    const normalized = normalizeRegistryCredentialSource(source, auth.passwordHashKey, values[auth.passwordHashKey]!);
    if (normalized === source || !confirm) {
      return {
        ok: true,
        mutation: false,
        mode: normalized === source ? "already-present" : "dry-run-normalize-compose-literal",
        targetId: target.id,
        registryAuth: registryAuthSummary(target),
        valuesPrinted: false,
      };
    }
    writeFileSync(path, normalized, { encoding: "utf8", mode: 0o600 });
    chmodSync(path, 0o600);
    return {
      ok: true,
      mutation: true,
      mode: "normalized-compose-literal",
      targetId: target.id,
      registryAuth: registryAuthSummary(target),
      valuesPrinted: false,
    };
  }
  if (!confirm) {
    return { ok: true, mutation: false, mode: "dry-run", targetId: target.id, sourceRef: path, keys, valuesPrinted: false };
  }
  const credential = createRegistryCredential();
  mkdirSync(dirname(path), { recursive: true, mode: 0o700 });
  writeFileSync(path, [
    `${auth.usernameKey}=${credential.username}`,
    `${auth.passwordKey}=${credential.password}`,
    `${auth.passwordHashKey}='${credential.passwordHash}'`,
    "",
  ].join("\n"), { encoding: "utf8", mode: 0o600 });
  chmodSync(path, 0o600);
  return { ok: true, mutation: true, targetId: target.id, registryAuth: registryAuthSummary(target), valuesPrinted: false };
}

function apply(target: K3sClusterTarget): Record<string, unknown> {
  const secret = readRegistrySecret(target);
  const proxyBundle = prepareHostProxyGuestBundle(target.node.hostProxyTargetId);
  const desired = standaloneDesired(target, secret, proxyBundle.desiredFingerprint);
  const before = runNode(
    target,
    standaloneConvergenceScript(target, desired.desiredHash, proxyBundle.serviceName),
    false,
    60_000,
  );
  const beforeData = parseFirstJson(before.stdout);
  if (before.exitCode === 0 && beforeData.ok === true) {
    return {
      ok: true,
      mutation: false,
      mode: "already-converged",
      targetId: target.id,
      topology: "standalone-single-node",
      status: beforeData,
      result: { convergence: compact(before) },
      valuesPrinted: false,
    };
  }
  const proxy = runNodeHostProxy(target, proxyBundle, 240_000);
  requireSuccess(proxy, "standalone-host-proxy-apply");
  const server = runNode(target, standaloneApplyScript(target, secret, desired), true, 300_000);
  requireSuccess(server, "standalone-k3s-apply");
  const converged = waitForStatus(target, 180_000);
  requireSuccess(converged, "standalone-k3s-status");
  const data = parseFirstJson(converged.stdout);
  if (data.ok !== true) throw new Error(`standalone cluster did not converge: ${bounded(JSON.stringify(data))}`);
  return {
    ok: true,
    mutation: true,
    targetId: target.id,
    topology: "standalone-single-node",
    status: data,
    result: {
      convergenceBefore: compact(before),
      hostProxy: compact(proxy),
      server: compact(server),
      status: compact(converged),
    },
    valuesPrinted: false,
  };
}

function status(target: K3sClusterTarget): Record<string, unknown> {
  const result = runNode(target, standaloneStatusScript(target), false, 60_000);
  const data = parseFirstJson(result.stdout);
  return {
    ok: result.exitCode === 0 && data.ok === true,
    mutation: false,
    targetId: target.id,
    topology: "standalone-single-node",
    status: data,
    result: { exitCode: result.exitCode, timedOut: result.timedOut },
  };
}

function smoke(target: K3sClusterTarget): Record<string, unknown> {
  const current = status(target);
  const data = record(current.status);
  return {
    ...current,
    action: "standalone-empty-cluster-smoke",
    checks: {
      singleNodeReady: data.nodeReady === true && data.nodeCount === 1,
      serverOnly: data.serverActive === true && data.oldAgentActive === false,
      systemDeploymentsReady: data.systemDeploymentCount === data.systemReadyDeploymentCount,
      businessPodCountZero: data.businessPodCount === 0,
    },
  };
}

function registrySmoke(target: K3sClusterTarget, confirm: boolean): Record<string, unknown> {
  if (!confirm) {
    return {
      ok: true,
      mutation: false,
      mode: "dry-run",
      targetId: target.id,
      nodeName: target.node.nodeName,
      registry: {
        source: target.k3s.registryMirror.source,
        endpoint: target.k3s.registryMirror.endpoint,
        auth: registryAuthSummary(target),
      },
      smoke: target.k3s.registryPullSmoke,
    };
  }
  const secret = readRegistrySecret(target);
  const result = runNode(
    target,
    registrySmokeScript(target, secret),
    false,
    (target.k3s.registryPullSmoke.timeoutSeconds + 60) * 1000,
  );
  const checks = parseFirstJson(result.stdout);
  return {
    ok: result.exitCode === 0 && checks.ok === true,
    mutation: true,
    targetId: target.id,
    nodeName: target.node.nodeName,
    checks,
    result: compact(result),
    valuesPrinted: false,
  };
}

function standaloneDesired(
  target: K3sClusterTarget,
  secret: RegistrySecret,
  proxyFingerprint: string,
): { exec: string; desiredHash: string } {
  const disableArgs = target.k3s.disableComponents.flatMap((component) => ["--disable", component]);
  const labels = Object.entries(target.node.labels).flatMap(([key, value]) => ["--node-label", `${key}=${value}`]);
  const exec = [
    "server",
    ...disableArgs,
    "--cluster-init",
    "--node-name", target.node.nodeName,
    ...labels,
    "--write-kubeconfig-mode", target.k3s.writeKubeconfigMode,
    "--kubelet-arg", `max-pods=${target.k3s.maxPods}`,
  ].map(shellToken).join(" ");
  const desiredHash = hash(JSON.stringify({
    version: target.node.k3sVersion,
    exec,
    registryEndpoint: target.k3s.registryMirror.endpoint,
    registryUsername: secret.username,
    registryPassword: secret.password,
    proxyFingerprint,
  }));
  return { exec, desiredHash };
}

function standaloneApplyScript(
  target: K3sClusterTarget,
  secret: RegistrySecret,
  desired: { exec: string; desiredHash: string },
): string {
  const { exec, desiredHash } = desired;
  return `set -eu
desired_hash=${shQuote(desiredHash)}
desired_hash_file=/etc/unidesk/k3s-standalone-desired.sha256
install -d -m 0700 /etc/rancher/k3s /etc/unidesk
cat >/etc/rancher/k3s/registries.yaml.candidate <<'EOF'
mirrors:
  "${target.k3s.registryMirror.source}":
    endpoint:
      - "${target.k3s.registryMirror.endpoint}"
configs:
  "${new URL(target.k3s.registryMirror.endpoint).host}":
    auth:
      username: ${JSON.stringify(secret.username)}
      password: ${JSON.stringify(secret.password)}
EOF
chmod 0600 /etc/rancher/k3s/registries.yaml.candidate
if ! cmp -s /etc/rancher/k3s/registries.yaml.candidate /etc/rancher/k3s/registries.yaml; then
  mv /etc/rancher/k3s/registries.yaml.candidate /etc/rancher/k3s/registries.yaml
else
  rm -f /etc/rancher/k3s/registries.yaml.candidate
fi
if [ "$(cat "$desired_hash_file" 2>/dev/null || true)" = "$desired_hash" ] && systemctl is-active --quiet k3s; then
  exit 0
fi
systemctl stop k3s >/dev/null 2>&1 || true
derived_passwd=/var/lib/rancher/k3s/server/cred/passwd
if [ -f "$derived_passwd" ]; then
  mv "$derived_passwd" "$derived_passwd.unidesk-pre-restart-$(date -u +%Y%m%dT%H%M%SZ)-$$"
fi
install -m 0755 /tmp/k3s /usr/local/bin/k3s
chmod 0755 /tmp/install-k3s.sh
INSTALL_K3S_SKIP_DOWNLOAD=true INSTALL_K3S_SKIP_START=true INSTALL_K3S_VERSION=${shQuote(target.node.k3sVersion)} INSTALL_K3S_EXEC=${shQuote(exec)} sh /tmp/install-k3s.sh
printf '%s\\n' "$desired_hash" >"$desired_hash_file"
chmod 0600 "$desired_hash_file"
systemctl enable k3s >/dev/null
systemctl start --no-block k3s
`;
}

function standaloneConvergenceScript(
  target: K3sClusterTarget,
  desiredHash: string,
  proxyServiceName: string,
): string {
  return `set -eu
active=false; systemctl is-active --quiet k3s && active=true
agent_active=false; systemctl is-active --quiet k3s-agent && agent_active=true
proxy_active=false; systemctl is-active --quiet ${shellToken(proxyServiceName)} && proxy_active=true
desired_current=false; [ "$(cat /etc/unidesk/k3s-standalone-desired.sha256 2>/dev/null || true)" = ${shQuote(desiredHash)} ] && desired_current=true
ready="$(k3s kubectl get node ${shQuote(target.node.nodeName)} -o jsonpath='{.status.conditions[?(@.type=="Ready")].status}' 2>/dev/null || true)"
node_count="$(k3s kubectl get nodes --no-headers 2>/dev/null | wc -l | tr -d ' ')"
business_pods="$(k3s kubectl get pods -A --no-headers 2>/dev/null | awk '$1 != "kube-system" {count++} END {print count+0}')"
deployments="$(k3s kubectl get deployments -A --no-headers 2>/dev/null | awk '$1 == "kube-system" {count++} END {print count+0}')"
ready_deployments="$(k3s kubectl get deployments -A --no-headers 2>/dev/null | awk '$1 == "kube-system" {split($3, ready, "/"); if (ready[1] == ready[2] && ready[2] != "0" && $5 == ready[2]) count++} END {print count+0}')"
python3 - "$active" "$agent_active" "$proxy_active" "$desired_current" "$ready" "$node_count" "$business_pods" "$deployments" "$ready_deployments" <<'PY'
import json, sys
active, agent, proxy, desired, ready, nodes, business, deployments, ready_deployments = sys.argv[1:]
ok = (
    active == "true"
    and agent == "false"
    and proxy == "true"
    and desired == "true"
    and ready == "True"
    and nodes == "1"
    and business == "0"
    and deployments == ready_deployments
)
print(json.dumps({
    "ok": ok,
    "serverActive": active == "true",
    "oldAgentActive": agent == "true",
    "hostProxyActive": proxy == "true",
    "desiredHashCurrent": desired == "true",
    "nodeReady": ready == "True",
    "nodeCount": int(nodes or 0),
    "businessPodCount": int(business or 0),
    "systemDeploymentCount": int(deployments or 0),
    "systemReadyDeploymentCount": int(ready_deployments or 0),
}, separators=(",", ":")))
PY
`;
}

function standaloneStatusScript(target: K3sClusterTarget): string {
  return `set -eu
active=false; systemctl is-active --quiet k3s && active=true
agent_active=false; systemctl is-active --quiet k3s-agent && agent_active=true
ready="$(k3s kubectl get node ${shQuote(target.node.nodeName)} -o jsonpath='{.status.conditions[?(@.type=="Ready")].status}' 2>/dev/null || true)"
node_count="$(k3s kubectl get nodes --no-headers 2>/dev/null | wc -l | tr -d ' ')"
business_pods="$(k3s kubectl get pods -A --no-headers 2>/dev/null | awk '$1 != "kube-system" {count++} END {print count+0}')"
deployments="$(k3s kubectl get deployments -A --no-headers 2>/dev/null | awk '$1 == "kube-system" {count++} END {print count+0}')"
ready_deployments="$(k3s kubectl get deployments -A --no-headers 2>/dev/null | awk '$1 == "kube-system" {split($3, ready, "/"); if (ready[1] == ready[2] && ready[2] != "0" && $5 == ready[2]) count++} END {print count+0}')"
python3 - "$active" "$agent_active" "$ready" "$node_count" "$business_pods" "$deployments" "$ready_deployments" <<'PY'
import json, sys
active, agent, ready, nodes, business, deployments, ready_deployments = sys.argv[1:]
ok = (
    active == "true"
    and agent == "false"
    and ready == "True"
    and nodes == "1"
    and business == "0"
    and deployments == ready_deployments
)
print(json.dumps({
    "ok": ok,
    "serverActive": active == "true",
    "oldAgentActive": agent == "true",
    "nodeReady": ready == "True",
    "nodeCount": int(nodes or 0),
    "businessPodCount": int(business or 0),
    "systemDeploymentCount": int(deployments or 0),
    "systemReadyDeploymentCount": int(ready_deployments or 0),
}, separators=(",", ":")))
PY
`;
}

function registrySmokeScript(target: K3sClusterTarget, secret: RegistrySecret): string {
  const smoke = target.k3s.registryPullSmoke;
  const endpoint = new URL(target.k3s.registryMirror.endpoint);
  const imagePath = smoke.image.slice(target.k3s.registryMirror.source.length + 1);
  const separator = imagePath.lastIndexOf(":");
  if (separator < 1) throw new Error(`registry smoke image must use a tag: ${smoke.image}`);
  const repository = imagePath.slice(0, separator);
  const tag = imagePath.slice(separator + 1);
  return `set -eu
tmp="$(mktemp -d)"
trap 'rm -rf "$tmp"' EXIT
cat >"$tmp/netrc" <<'EOF'
machine ${endpoint.hostname}
login ${secret.username}
password ${secret.password}
EOF
chmod 0600 "$tmp/netrc"
base=${shQuote(target.k3s.registryMirror.endpoint)}
unauth_status="$(curl -sS -o /dev/null -w '%{http_code}' --connect-timeout 5 --max-time 20 "$base/v2/" || true)"
auth_status="$(curl -sS --netrc-file "$tmp/netrc" -o /dev/null -w '%{http_code}' --connect-timeout 5 --max-time 20 "$base/v2/" || true)"
curl -fsS --netrc-file "$tmp/netrc" -D "$tmp/headers" -o "$tmp/manifest.json" -H 'Accept: application/vnd.docker.distribution.manifest.v2+json' "$base/v2/${repository}/manifests/${tag}"
manifest_digest="$(tr -d '\\r' <"$tmp/headers" | awk 'tolower($1)=="docker-content-digest:" {print $2}' | tail -1)"
layer_digest="$(python3 - "$tmp/manifest.json" <<'PY'
import json, sys
print(json.load(open(sys.argv[1], encoding="utf-8"))["layers"][0]["digest"])
PY
)"
range_status="$(curl -sS --netrc-file "$tmp/netrc" -H 'Range: bytes=0-1023' -o "$tmp/range.bin" -w '%{http_code}' "$base/v2/${repository}/blobs/$layer_digest" || true)"
range_bytes="$(wc -c <"$tmp/range.bin" | tr -d ' ')"
write_status="$(curl -sS --netrc-file "$tmp/netrc" -X POST -o /dev/null -w '%{http_code}' "$base/v2/unidesk-registry-smoke/blobs/uploads/" || true)"
k3s crictl pull ${shQuote(smoke.image)} >"$tmp/pull.log" 2>&1
k3s crictl inspecti ${shQuote(smoke.image)} >"$tmp/inspect.json"
grep -F ${shQuote(smoke.expectedDigest)} "$tmp/inspect.json" >/dev/null
node_ready="$(k3s kubectl get node ${shQuote(target.node.nodeName)} -o jsonpath='{.status.conditions[?(@.type=="Ready")].status}')"
python3 - "$unauth_status" "$auth_status" "$manifest_digest" "$range_status" "$range_bytes" "$write_status" "$node_ready" <<'PY'
import json, sys
unauth, auth, digest, range_status, range_bytes, write_status, ready = sys.argv[1:]
checks = {
    "unauthenticatedRejected": unauth == "401",
    "authenticatedV2": auth == "200",
    "manifestDigest": digest == ${JSON.stringify(smoke.expectedDigest)},
    "blobRange": range_status == "206" and int(range_bytes or 0) == 1024,
    "writesRejected": write_status in {"403", "405"},
    "imagePulled": True,
    "nodeReady": ready == "True",
}
print(json.dumps({"ok": all(checks.values()), "checks": checks, "valuesPrinted": False}, separators=(",", ":")))
PY
`;
}

function waitForStatus(target: K3sClusterTarget, timeoutMs: number) {
  const deadline = Date.now() + timeoutMs;
  let current = runNode(target, standaloneStatusScript(target), false, 55_000);
  while ((current.exitCode !== 0 || parseFirstJson(current.stdout).ok !== true) && Date.now() < deadline) {
    runCommand(["sleep", "3"], rootPath(), { timeoutMs: 5_000 });
    current = runNode(target, standaloneStatusScript(target), false, 55_000);
  }
  return current;
}

function runNode(target: K3sClusterTarget, script: string, copyArtifacts: boolean, timeoutMs: number) {
  const encoded = Buffer.from(script, "utf8").toString("base64");
  const keyPath = `${target.node.stateDir}\\id_ed25519`;
  const guest = `${target.node.guestUser}@${target.node.guestAddress}`;
  if (!copyArtifacts) {
    const lines = [
      "$ErrorActionPreference='Stop'",
      `$key=${psQuote(keyPath)}`,
      `$guest=${psQuote(guest)}`,
      `$payload=${psQuote(encoded)}`,
      "$start=[Diagnostics.ProcessStartInfo]::new()",
      "$start.FileName='ssh.exe'",
      '$start.Arguments="-i `"$key`" -o BatchMode=yes -o StrictHostKeyChecking=accept-new -o ServerAliveInterval=15 $guest `"sudo sh -s`""',
      "$start.UseShellExecute=$false",
      "$start.RedirectStandardInput=$true",
      "$start.RedirectStandardOutput=$true",
      "$start.RedirectStandardError=$true",
      "$process=[Diagnostics.Process]::new()",
      "$process.StartInfo=$start",
      "if(!$process.Start()){throw 'node-inline-ssh-start-failed'}",
      "$stdout=$process.StandardOutput.ReadToEndAsync()",
      "$stderr=$process.StandardError.ReadToEndAsync()",
      "$bytes=[Convert]::FromBase64String($payload)",
      "$process.StandardInput.BaseStream.Write($bytes,0,$bytes.Length)",
      "$process.StandardInput.Close()",
      "$process.WaitForExit()",
      "[Console]::Out.Write($stdout.Result)",
      "[Console]::Error.Write($stderr.Result)",
      "exit $process.ExitCode",
    ];
    return runCommand(["trans", target.node.windowsRoute, "ps"], rootPath(), { input: `${lines.join("\n")}\n`, timeoutMs });
  }
  const remoteScript = `/tmp/unidesk-k3s-cluster-${target.id}.sh`;
  const lines = [
    "$ErrorActionPreference='Stop'",
    `$key=${psQuote(keyPath)}`,
    `$guest=${psQuote(guest)}`,
    `$temp=Join-Path $env:TEMP ${psQuote(`unidesk-k3s-cluster-${target.id}.sh`)}`,
    `[IO.File]::WriteAllBytes($temp,[Convert]::FromBase64String(${psQuote(encoded)}))`,
    `$binary=${psQuote(`${target.node.stateDir}\\k3s`)}`,
    `$installer=${psQuote(`${target.node.stateDir}\\install-k3s.sh`)}`,
    "& scp.exe -i $key -o BatchMode=yes -o StrictHostKeyChecking=accept-new $binary \"$($guest):/tmp/k3s\"",
    "if($LASTEXITCODE -ne 0){throw 'node-k3s-binary-scp-failed'}",
    "& scp.exe -i $key -o BatchMode=yes -o StrictHostKeyChecking=accept-new $installer \"$($guest):/tmp/install-k3s.sh\"",
    "if($LASTEXITCODE -ne 0){throw 'node-k3s-installer-scp-failed'}",
    `& scp.exe -i $key -o BatchMode=yes -o StrictHostKeyChecking=accept-new $temp "$($guest):${remoteScript}"`,
    "if($LASTEXITCODE -ne 0){throw 'node-script-scp-failed'}",
    `& ssh.exe -i $key -o BatchMode=yes -o StrictHostKeyChecking=accept-new -o ServerAliveInterval=15 $guest ${psQuote(`sudo sh ${remoteScript}`)}`,
    "$rc=$LASTEXITCODE",
    "Remove-Item -Force -LiteralPath $temp -ErrorAction SilentlyContinue",
    `& ssh.exe -i $key -o BatchMode=yes $guest ${psQuote(`rm -f ${remoteScript}`)} | Out-Null`,
    "exit $rc",
  ];
  return runCommand(["trans", target.node.windowsRoute, "ps"], rootPath(), { input: `${lines.join("\n")}\n`, timeoutMs });
}

function runNodeHostProxy(target: K3sClusterTarget, bundle: HostProxyGuestBundle, timeoutMs: number) {
  const windowsArchive = `${target.node.stateDir}\\host-proxy-${bundle.targetId}.tar.gz`;
  const guestArchive = `/tmp/unidesk-host-proxy-${bundle.targetId}.tar.gz`;
  const remoteScript = `/tmp/unidesk-host-proxy-${bundle.targetId}.sh`;
  const script = `${bundle.prepareScript}
remote_archive_sha="$(sha256sum ${shQuote(bundle.remoteArchivePath)} 2>/dev/null | awk '{print $1}' || true)"
if [ "$remote_archive_sha" != ${shQuote(bundle.archiveSha256)} ]; then
  install -m 0644 ${shQuote(guestArchive)} ${shQuote(bundle.remoteArchivePath)}
fi
${bundle.applyScript}
${hostProxyGuestStatusScript(bundle.targetId)}
`;
  const encoded = Buffer.from(script, "utf8").toString("base64");
  const lines = [
    "$ErrorActionPreference='Stop'",
    `$key=${psQuote(`${target.node.stateDir}\\id_ed25519`)}`,
    `$guest=${psQuote(`${target.node.guestUser}@${target.node.guestAddress}`)}`,
    `$archive=${psQuote(windowsArchive)}`,
    `$temp=Join-Path $env:TEMP ${psQuote(`unidesk-host-proxy-${bundle.targetId}.sh`)}`,
    `[IO.File]::WriteAllBytes($temp,[Convert]::FromBase64String(${psQuote(encoded)}))`,
    `if(!(Test-Path -LiteralPath $archive)){`,
    `  & curl.exe -fL --retry 2 --connect-timeout 15 --max-time 180 -o $archive ${psQuote(bundle.upstreamUrl)}`,
    "  if($LASTEXITCODE -ne 0){throw 'host-proxy-artifact-download-failed'}",
    "}",
    `& scp.exe -i $key -o BatchMode=yes -o StrictHostKeyChecking=accept-new $archive "$($guest):${guestArchive}"`,
    "if($LASTEXITCODE -ne 0){throw 'host-proxy-artifact-scp-failed'}",
    `& scp.exe -i $key -o BatchMode=yes $temp "$($guest):${remoteScript}"`,
    "if($LASTEXITCODE -ne 0){throw 'host-proxy-script-scp-failed'}",
    `& ssh.exe -i $key -o BatchMode=yes $guest ${psQuote(`sudo sh ${remoteScript}`)}`,
    "$rc=$LASTEXITCODE",
    "Remove-Item -Force -LiteralPath $temp -ErrorAction SilentlyContinue",
    "exit $rc",
  ];
  return runCommand(["trans", target.node.windowsRoute, "ps"], rootPath(), { input: `${lines.join("\n")}\n`, timeoutMs });
}

function readRegistrySecret(target: K3sClusterTarget): RegistrySecret {
  const auth = target.k3s.registryMirror.auth;
  if (!existsSync(auth.sourceRef)) throw new Error(`${auth.sourceRef} is missing; run registry-credential-init --confirm`);
  const values = parseEnvFile(readFileSync(auth.sourceRef, "utf8"));
  const required = (key: string, minimumLength: number) => {
    const value = values[key];
    if (typeof value !== "string" || value.length < minimumLength) throw new Error(`${auth.sourceRef}.${key} is missing or invalid`);
    return value;
  };
  const material = {
    username: required(auth.usernameKey, 1),
    password: required(auth.passwordKey, 32),
    passwordHash: required(auth.passwordHashKey, 32),
  };
  return { ...material, fingerprint: hash(Object.entries(material).sort().map(([key, value]) => `${key}=${value}`).join("\n")) };
}

function registryAuthSummary(target: K3sClusterTarget): Record<string, unknown> {
  const auth = target.k3s.registryMirror.auth;
  if (!existsSync(auth.sourceRef)) {
    return { sourceRef: auth.sourceRef, presence: false, fingerprint: null, valuesPrinted: false };
  }
  try {
    const secret = readRegistrySecret(target);
    return {
      sourceRef: auth.sourceRef,
      presence: true,
      fingerprint: secret.fingerprint,
      keys: [auth.usernameKey, auth.passwordKey, auth.passwordHashKey],
      valuesPrinted: false,
    };
  } catch (error) {
    return {
      sourceRef: auth.sourceRef,
      presence: true,
      fingerprint: null,
      error: error instanceof Error ? error.message : String(error),
      valuesPrinted: false,
    };
  }
}

function normalizeRegistryCredentialSource(source: string, passwordHashKey: string, passwordHash: string): string {
  const lines = source.split(/\r?\n/u).map((line) => {
    const match = /^([A-Za-z_][A-Za-z0-9_]*)=/u.exec(line);
    return match?.[1] === passwordHashKey ? `${passwordHashKey}='${passwordHash}'` : line;
  });
  while (lines.at(-1) === "") lines.pop();
  return `${lines.join("\n")}\n`;
}

function createRegistryCredential(): { username: string; password: string; passwordHash: string } {
  const username = "unidesk-pull";
  const password = randomBytes(32).toString("base64url");
  const result = runCommand(
    ["docker", "run", "--rm", "-i", "caddy:2.10.2-alpine", "sh", "-c", 'IFS= read -r password; caddy hash-password --algorithm bcrypt --plaintext "$password"'],
    rootPath(),
    { input: `${password}\n`, timeoutMs: 60_000 },
  );
  if (result.exitCode !== 0 || !/^\$2[aby]\$\d{2}\$[./A-Za-z0-9]{53}$/u.test(result.stdout.trim())) {
    throw new Error("failed to generate artifact registry bcrypt credential");
  }
  return { username, password, passwordHash: result.stdout.trim() };
}

function requireSuccess(result: ReturnType<typeof runCommand>, stage: string): void {
  if (result.exitCode !== 0) throw new Error(`${stage} failed: ${bounded(result.stderr || result.stdout)}`);
}

function compact(result: ReturnType<typeof runCommand>): Record<string, unknown> {
  return {
    exitCode: result.exitCode,
    timedOut: result.timedOut,
    stdout: bounded(result.stdout, 2_000),
    stderr: bounded(result.stderr, 2_000),
  };
}

function parseFirstJson(value: string): Record<string, unknown> {
  const complete = value.trim();
  if (complete.startsWith("{")) {
    try { return JSON.parse(complete) as Record<string, unknown>; } catch { /* continue */ }
  }
  for (const line of value.split(/\r?\n/u)) {
    const text = line.trim();
    if (!text.startsWith("{")) continue;
    try { return JSON.parse(text) as Record<string, unknown>; } catch { /* continue */ }
  }
  return { ok: false, error: "remote-json-missing", output: bounded(value) };
}

function record(value: unknown): Record<string, any> {
  return typeof value === "object" && value !== null && !Array.isArray(value)
    ? value as Record<string, any>
    : {};
}

function option(args: string[], name: string): string | null {
  const index = args.indexOf(name);
  if (index < 0) return null;
  const value = args[index + 1];
  if (!value || value.startsWith("--")) throw new Error(`${name} requires a value`);
  return value;
}

function shQuote(value: string): string {
  return `'${value.replaceAll("'", `'"'"'`)}'`;
}

function shellToken(value: string): string {
  return /^[A-Za-z0-9._:/=@+-]+$/u.test(value) ? value : shQuote(value);
}

function psQuote(value: string): string {
  return `'${value.replaceAll("'", "''")}'`;
}

function hash(value: string): string {
  return createHash("sha256").update(value).digest("hex");
}

function bounded(value: string, max = 4_000): string {
  const text = value.trim();
  return text.length <= max ? text : `${text.slice(0, max)}\n...[truncated ${text.length - max} chars]`;
}
