#!/usr/bin/env bun

import { existsSync } from "node:fs";
import { spawnSync, type SpawnSyncOptions } from "node:child_process";
import path from "node:path";
import { fileURLToPath } from "node:url";

type DeployMode = "main" | "current-branch";
type DeployRuntime = "docker" | "native";
type DeployScope = "full" | "web" | "api" | "services" | "workers";

type DeployOptions = {
  mode: DeployMode;
  runtime: DeployRuntime;
  scope: DeployScope;
  fast: boolean;
  forceRecreate: boolean;
  noBuild: boolean;
};

type PhaseTiming = {
  name: string;
  durationMs: number;
};

const REMOTE_HOST = "delta@152.53.80.229";
const REMOTE_REPO = "/home/delta/islandflow";
const REMOTE_DOCKER_DEPLOYMENT = "/home/delta/islandflow/deployment/docker";
const SSH_KEY =
  process.env.DEPLOY_SSH_KEY_PATH?.trim() ||
  path.join(process.env.HOME ?? "", ".ssh", "delta_ed25519");
const DEPLOY_FORCE_SSH = process.env.DEPLOY_FORCE_SSH?.trim() === "1";
const SSH_OPTIONS = ["-i", SSH_KEY, "-o", "IdentitiesOnly=yes", "-o", "BatchMode=yes"];
const ALLOWED_REMOTE_UNTRACKED = new Set([
  "deployment/docker/signal-cli-0.14.3-Linux-native.tar.gz"
]);
const PUBLIC_APP_URL = process.env.DEPLOY_PUBLIC_APP_URL?.trim() || "https://flow.deltaisland.io";
const PUBLIC_API_HEALTH_URL = process.env.DEPLOY_PUBLIC_API_HEALTH_URL?.trim() || null;
const DEPLOY_GIT_REMOTE_OVERRIDE = process.env.DEPLOY_GIT_REMOTE?.trim() || null;
const DEPLOY_NATIVE_EDGE_READY = process.env.DEPLOY_NATIVE_EDGE_READY?.trim() === "1";
const NATIVE_SYSTEMCTL_PREFIX =
  process.env.DEPLOY_NATIVE_SYSTEMCTL_PREFIX?.trim() || "sudo -n systemctl";
const NATIVE_UNITS = {
  web: process.env.DEPLOY_NATIVE_WEB_UNIT?.trim() || "islandflow-web",
  api: process.env.DEPLOY_NATIVE_API_UNIT?.trim() || "islandflow-api",
  compute: process.env.DEPLOY_NATIVE_COMPUTE_UNIT?.trim() || "islandflow-compute",
  candles: process.env.DEPLOY_NATIVE_CANDLES_UNIT?.trim() || "islandflow-candles",
  ingestOptions:
    process.env.DEPLOY_NATIVE_INGEST_OPTIONS_UNIT?.trim() || "islandflow-ingest-options",
  ingestEquities:
    process.env.DEPLOY_NATIVE_INGEST_EQUITIES_UNIT?.trim() || "islandflow-ingest-equities",
  ingestNews: process.env.DEPLOY_NATIVE_INGEST_NEWS_UNIT?.trim() || "islandflow-ingest-news"
} as const;
const DOCKER_CORE_SERVICES = [
  "api",
  "web",
  "compute",
  "candles",
  "ingest-options",
  "ingest-equities",
  "ingest-news"
] as const;
const DOCKER_BACKEND_SERVICES = [
  "api",
  "compute",
  "candles",
  "ingest-options",
  "ingest-equities",
  "ingest-news"
] as const;
const DOCKER_WORKER_SERVICES = [
  "compute",
  "candles",
  "ingest-options",
  "ingest-equities",
  "ingest-news"
] as const;

const scriptPath = fileURLToPath(import.meta.url);
const repoRoot = path.resolve(path.dirname(scriptPath), "..");
const isLocalServerExecution = !DEPLOY_FORCE_SSH && repoRoot === REMOTE_REPO;

function usage(exitCode = 1): never {
  console.error(`Usage:
  ./deploy main [--runtime docker|native] [--web-only|--api-only|--services-only|--workers-only] [--fast] [--no-build] [--force-recreate]
  ./deploy current-branch [--runtime docker|native] [--web-only|--api-only|--services-only|--workers-only] [--fast] [--no-build] [--force-recreate]
  ./deploy current branch [--runtime docker|native] [--web-only|--api-only|--services-only|--workers-only] [--fast] [--no-build] [--force-recreate]

Modes:
  main            Deploy <remote>/main to the live server checkout.
  current-branch  Push the current local branch, switch the server to it, and deploy it.

Runtimes:
  docker          Roll out from deployment/docker with Docker Compose (default, recommended).
  native          Experimental host-native Bun services managed by systemd.

Scopes:
  default         Full rollout (web + API + backend services).
  --web-only      Deploy only the Next.js web surface.
  --api-only      Deploy only the API service.
  --services-only Deploy API + backend services without the web service.
  --workers-only  Deploy compute/candles/ingest workers without touching web or API.

Options:
  --runtime <name>     Explicit runtime selector (docker or native).
  --fast               Prefer a quicker rollout profile (defaults full scope to --services-only for docker and --workers-only for native, and skips the public API route suite when API scope is included).
  --no-build           Skip docker image builds or native bun install/web build steps.
  --force-recreate     Docker-only escalation path for docker compose when a normal refresh is not enough.
  --help               Show this help text.

Environment:
  DEPLOY_GIT_REMOTE                Override git remote used for deploy fetch/pull/push (auto-detected by default).
  DEPLOY_SSH_KEY_PATH              Override the SSH key used for remote execution.
  DEPLOY_FORCE_SSH                 Set to 1 to force SSH even when running from the live server checkout.
  DEPLOY_PUBLIC_APP_URL             Override the public app URL (default: https://flow.deltaisland.io).
  DEPLOY_PUBLIC_API_HEALTH_URL      Optional separate public API health URL for two-origin deployments.
  DEPLOY_NATIVE_EDGE_READY          Set to 1 to allow native rollouts that include the public web or API edge.
  DEPLOY_NATIVE_SYSTEMCTL_PREFIX    Override systemctl invocation for native rollouts (default: sudo -n systemctl).
  DEPLOY_NATIVE_WEB_UNIT            Override native web systemd unit name.
  DEPLOY_NATIVE_API_UNIT            Override native api systemd unit name.
  DEPLOY_NATIVE_COMPUTE_UNIT        Override native compute systemd unit name.
  DEPLOY_NATIVE_CANDLES_UNIT        Override native candles systemd unit name.
  DEPLOY_NATIVE_INGEST_OPTIONS_UNIT Override native ingest-options systemd unit name.
  DEPLOY_NATIVE_INGEST_EQUITIES_UNIT Override native ingest-equities systemd unit name.
  DEPLOY_NATIVE_INGEST_NEWS_UNIT Override native ingest-news systemd unit name.`);
  process.exit(exitCode);
}

function section(title: string): void {
  console.log(`\n== ${title} ==`);
}

function formatDuration(durationMs: number): string {
  if (durationMs < 1000) {
    return `${durationMs}ms`;
  }

  return `${(durationMs / 1000).toFixed(2)}s`;
}

function timedPhase<T>(timings: PhaseTiming[], name: string, fn: () => T): T {
  const startedAt = Date.now();
  try {
    return fn();
  } finally {
    timings.push({ name, durationMs: Date.now() - startedAt });
  }
}

function printTimingSummary(timings: PhaseTiming[]): void {
  section("Deploy Timings");
  const totalMs = timings.reduce((sum, timing) => sum + timing.durationMs, 0);
  for (const timing of timings) {
    console.log(`[deploy] ${timing.name}: ${formatDuration(timing.durationMs)}`);
  }
  console.log(`[deploy] total: ${formatDuration(totalMs)}`);
}

function formatCommand(command: string, args: string[]): string {
  return [command, ...args]
    .map((part) => (/\s/.test(part) ? JSON.stringify(part) : part))
    .join(" ");
}

function runChecked(command: string, args: string[], options: SpawnSyncOptions = {}): void {
  console.log(`$ ${formatCommand(command, args)}`);
  const result = spawnSync(command, args, {
    cwd: repoRoot,
    stdio: "inherit",
    ...options
  });

  if (result.status !== 0) {
    process.exit(result.status ?? 1);
  }
}

function captureChecked(command: string, args: string[], options: SpawnSyncOptions = {}): string {
  const result = spawnSync(command, args, {
    cwd: repoRoot,
    encoding: "utf8",
    stdio: ["inherit", "pipe", "pipe"],
    ...options
  });

  if (result.status !== 0) {
    process.stderr.write(result.stderr ?? "");
    process.exit(result.status ?? 1);
  }

  return result.stdout ?? "";
}

function tryCapture(
  command: string,
  args: string[],
  options: SpawnSyncOptions = {}
): string | null {
  const result = spawnSync(command, args, {
    cwd: repoRoot,
    encoding: "utf8",
    stdio: ["inherit", "pipe", "pipe"],
    ...options
  });
  if (result.status !== 0) {
    return null;
  }
  return result.stdout ?? "";
}

function runRemoteScript(title: string, script: string, args: string[] = []): void {
  section(title);

  if (isLocalServerExecution) {
    const localArgs = ["-s", "--", ...args];
    console.log(`$ ${formatCommand("bash", localArgs)}    # local server execution`);
    const result = spawnSync("bash", localArgs, {
      cwd: repoRoot,
      input: script,
      encoding: "utf8",
      stdio: ["pipe", "inherit", "inherit"]
    });

    if (result.status !== 0) {
      process.exit(result.status ?? 1);
    }
    return;
  }

  const sshArgs = [...SSH_OPTIONS, REMOTE_HOST, "bash", "-s", "--", ...args];
  console.log(`$ ${formatCommand("ssh", sshArgs)}`);
  const result = spawnSync("ssh", sshArgs, {
    cwd: repoRoot,
    input: script,
    encoding: "utf8",
    stdio: ["pipe", "inherit", "inherit"]
  });

  if (result.status !== 0) {
    process.exit(result.status ?? 1);
  }
}

function parseRuntime(rawArgs: string[]): DeployRuntime {
  for (let index = 0; index < rawArgs.length; index += 1) {
    const arg = rawArgs[index];
    if (arg === "--runtime") {
      const value = rawArgs[index + 1];
      if (value === "docker" || value === "native") {
        return value;
      }
      usage();
    }

    if (arg.startsWith("--runtime=")) {
      const value = arg.slice("--runtime=".length);
      if (value === "docker" || value === "native") {
        return value;
      }
      usage();
    }
  }

  return "docker";
}

function parseScope(rawArgs: string[]): DeployScope {
  const scopes = [
    rawArgs.includes("--web-only") ? "web" : null,
    rawArgs.includes("--api-only") ? "api" : null,
    rawArgs.includes("--services-only") ? "services" : null,
    rawArgs.includes("--workers-only") ? "workers" : null
  ].filter((value): value is Exclude<DeployScope, "full"> => value !== null);

  if (scopes.length > 1) {
    console.error(
      "Choose only one deploy scope flag: --web-only, --api-only, --services-only, or --workers-only."
    );
    process.exit(1);
  }

  return scopes[0] ?? "full";
}

function parseArgs(rawArgs: string[]): DeployOptions {
  if (rawArgs.includes("--help") || rawArgs.includes("-h")) {
    usage(0);
  }

  const runtime = parseRuntime(rawArgs);
  const scope = parseScope(rawArgs);
  const fast = rawArgs.includes("--fast");
  const forceRecreate = rawArgs.includes("--force-recreate");
  const noBuild = rawArgs.includes("--no-build");
  const positional = rawArgs.filter(
    (arg, index) =>
      arg !== "--force-recreate" &&
      arg !== "--fast" &&
      arg !== "--no-build" &&
      arg !== "--web-only" &&
      arg !== "--api-only" &&
      arg !== "--services-only" &&
      arg !== "--workers-only" &&
      arg !== "--runtime" &&
      rawArgs[index - 1] !== "--runtime" &&
      !arg.startsWith("--runtime=")
  );

  if (forceRecreate && runtime !== "docker") {
    console.error("--force-recreate is only supported with --runtime docker.");
    process.exit(1);
  }

  if (positional.length === 1 && positional[0] === "main") {
    return { mode: "main", runtime, scope, fast, forceRecreate, noBuild };
  }

  if (
    (positional.length === 1 && positional[0] === "current-branch") ||
    (positional.length === 2 && positional[0] === "current" && positional[1] === "branch")
  ) {
    return {
      mode: "current-branch",
      runtime,
      scope,
      fast,
      forceRecreate,
      noBuild
    };
  }

  usage();
}

function assertSshKeyExists(): void {
  if (isLocalServerExecution) {
    return;
  }

  if (!existsSync(SSH_KEY)) {
    console.error(`Missing SSH key: ${SSH_KEY}`);
    console.error(
      "Set DEPLOY_SSH_KEY_PATH or run from the live server checkout without DEPLOY_FORCE_SSH."
    );
    process.exit(1);
  }
}

function shellEscape(value: string): string {
  if (value.length === 0) {
    return "''";
  }
  return `'${value.replace(/'/g, `'"'"'`)}'`;
}

function shellPattern(value: string): string {
  return `'${value.replace(/'/g, `'"'"'`)}'`;
}

function parseUpstreamRemote(upstreamRef: string | null): string | null {
  if (!upstreamRef) {
    return null;
  }
  const trimmed = upstreamRef.trim();
  if (!trimmed || !trimmed.includes("/")) {
    return null;
  }
  return trimmed.split("/", 1)[0] ?? null;
}

function localGitRemotes(): string[] {
  const raw = tryCapture("git", ["remote"]);
  if (!raw) {
    return [];
  }
  return raw
    .split("\n")
    .map((value) => value.trim())
    .filter((value) => value.length > 0);
}

function localHasRemote(name: string): boolean {
  return (
    spawnSync("git", ["remote", "get-url", name], {
      cwd: repoRoot,
      stdio: "ignore"
    }).status === 0
  );
}

function resolveDeployRemote(mode: DeployMode, branch: string | null): string {
  const candidates: string[] = [];

  if (DEPLOY_GIT_REMOTE_OVERRIDE) {
    candidates.push(DEPLOY_GIT_REMOTE_OVERRIDE);
  }

  if (mode === "current-branch" && branch) {
    const branchRemote = tryCapture("git", ["config", "--get", `branch.${branch}.remote`])?.trim();
    if (branchRemote) {
      candidates.push(branchRemote);
    }

    const upstreamRef = tryCapture("git", [
      "rev-parse",
      "--abbrev-ref",
      "--symbolic-full-name",
      "@{u}"
    ]);
    const upstreamRemote = parseUpstreamRemote(upstreamRef);
    if (upstreamRemote) {
      candidates.push(upstreamRemote);
    }
  }

  const mainRemote = tryCapture("git", ["config", "--get", "branch.main.remote"])?.trim();
  if (mainRemote) {
    candidates.push(mainRemote);
  }

  candidates.push("forgejo", "origin", "github", ...localGitRemotes());

  const deduped = Array.from(new Set(candidates.filter((value) => value.length > 0)));
  const selected = deduped.find((name) => localHasRemote(name));

  if (selected) {
    return selected;
  }

  console.error(`Unable to resolve a deploy git remote. Checked candidates: ${deduped.join(", ")}`);
  console.error("Set DEPLOY_GIT_REMOTE to a valid remote name or configure branch.<name>.remote.");
  process.exit(1);
}

function describeRuntime(runtime: DeployRuntime): string {
  return runtime === "docker" ? "Docker Compose" : "experimental native systemd/Bun";
}

function printRuntimeAdvisory(runtime: DeployRuntime): void {
  if (runtime !== "native") {
    return;
  }

  console.warn(
    "[deploy] Native runtime is experimental. Use --runtime docker for the current supported VPS path unless Bun, systemd units, and proxy routing have been prepared intentionally."
  );
}

function describeScope(scope: DeployScope): string {
  switch (scope) {
    case "web":
      return "web only";
    case "api":
      return "api only";
    case "services":
      return "api + backend services";
    case "workers":
      return "worker services only";
    default:
      return "full stack";
  }
}

function effectiveScope(scope: DeployScope, runtime: DeployRuntime, fast: boolean): DeployScope {
  if (fast && scope === "full") {
    return runtime === "native" ? "workers" : "services";
  }
  return scope;
}

function scopeIncludesWeb(scope: DeployScope): boolean {
  return scope === "full" || scope === "web";
}

function scopeIncludesApi(scope: DeployScope): boolean {
  return scope === "full" || scope === "api" || scope === "services";
}

function scopeTouchesPublicEdge(scope: DeployScope): boolean {
  return scopeIncludesWeb(scope) || scopeIncludesApi(scope);
}

function dockerServicesForScope(scope: DeployScope): string[] {
  switch (scope) {
    case "web":
      return ["web"];
    case "api":
      return ["api"];
    case "services":
      return [...DOCKER_BACKEND_SERVICES];
    case "workers":
      return [...DOCKER_WORKER_SERVICES];
    default:
      return [];
  }
}

function dockerBuildServicesForScope(scope: DeployScope): string[] {
  switch (scope) {
    case "full":
      return [...DOCKER_CORE_SERVICES];
    default:
      return dockerServicesForScope(scope);
  }
}

function dockerLogServicesForScope(scope: DeployScope): string[] {
  switch (scope) {
    case "web":
      return ["web"];
    case "api":
      return ["api"];
    case "services":
      return [...DOCKER_BACKEND_SERVICES];
    case "workers":
      return [...DOCKER_WORKER_SERVICES];
    default:
      return [...DOCKER_CORE_SERVICES];
  }
}

function nativeUnitsForScope(scope: DeployScope): string[] {
  switch (scope) {
    case "web":
      return [NATIVE_UNITS.web];
    case "api":
      return [NATIVE_UNITS.api];
    case "services":
      return [
        NATIVE_UNITS.api,
        NATIVE_UNITS.compute,
        NATIVE_UNITS.candles,
        NATIVE_UNITS.ingestOptions,
        NATIVE_UNITS.ingestEquities,
        NATIVE_UNITS.ingestNews
      ];
    case "workers":
      return [
        NATIVE_UNITS.compute,
        NATIVE_UNITS.candles,
        NATIVE_UNITS.ingestOptions,
        NATIVE_UNITS.ingestEquities,
        NATIVE_UNITS.ingestNews
      ];
    default:
      return [
        NATIVE_UNITS.web,
        NATIVE_UNITS.api,
        NATIVE_UNITS.compute,
        NATIVE_UNITS.candles,
        NATIVE_UNITS.ingestOptions,
        NATIVE_UNITS.ingestEquities,
        NATIVE_UNITS.ingestNews
      ];
  }
}

function localDockerWorkspaceSnapshotPrecheck(): void {
  console.log("$ bun run check:docker-workspace");
  const result = spawnSync("bun", ["run", "check:docker-workspace"], {
    cwd: repoRoot,
    stdio: "inherit"
  });

  if (result.status !== 0) {
    console.error(
      "Refusing docker deploy: deployment/docker/workspace-root is out of sync. Run `bun run sync:docker-workspace`, commit updated snapshot files, then retry deploy."
    );
    process.exit(result.status ?? 1);
  }
}

function assertNativeEdgeReady(scope: DeployScope): void {
  if (!scopeTouchesPublicEdge(scope) || DEPLOY_NATIVE_EDGE_READY) {
    return;
  }

  console.error(
    "Refusing native deploy that touches public web/API scope before edge cutover is acknowledged."
  );
  console.error(
    "Set DEPLOY_NATIVE_EDGE_READY=1 only after proxy routing and native units for the public edge are intentionally prepared."
  );
  console.error(
    "For fast iterative backend deploys before cutover, use --runtime native --workers-only or --runtime native --fast."
  );
  process.exit(1);
}

function localRuntimePrecheck(runtime: DeployRuntime, scope: DeployScope, noBuild: boolean): void {
  if (runtime === "docker" && !noBuild) {
    localDockerWorkspaceSnapshotPrecheck();
    return;
  }

  if (runtime === "native") {
    assertNativeEdgeReady(scope);
  }
}

function localMainPrecheck(
  remote: string,
  runtime: DeployRuntime,
  scope: DeployScope,
  noBuild: boolean
): void {
  section("Local Precheck");
  runChecked("git", ["fetch", remote]);
  runChecked("git", ["status", "--short", "--branch"]);
  runChecked("git", ["rev-parse", "--verify", "HEAD"]);
  runChecked("git", ["rev-parse", `${remote}/main`]);
  localRuntimePrecheck(runtime, scope, noBuild);
}

function currentBranchName(): string {
  const branch = captureChecked("git", ["branch", "--show-current"]).trim();
  if (!branch) {
    console.error("Refusing branch deployment from a detached HEAD.");
    process.exit(1);
  }
  return branch;
}

function localBranchPrecheck(
  remote: string,
  branch: string,
  runtime: DeployRuntime,
  scope: DeployScope,
  noBuild: boolean
): void {
  section("Local Precheck");
  runChecked("git", ["branch", "--show-current"]);
  runChecked("git", ["status", "--short", "--branch"]);
  runChecked("git", ["fetch", remote]);

  const porcelain = captureChecked("git", ["status", "--porcelain=v1"]).trim();
  if (porcelain) {
    console.error(
      `Refusing to deploy ${branch} with uncommitted local changes. Commit the intended state first.`
    );
    process.exit(1);
  }

  localRuntimePrecheck(runtime, scope, noBuild);
}

function publishCurrentBranch(remote: string, branch: string): void {
  section("Local Publish");
  const upstreamResult = spawnSync(
    "git",
    ["rev-parse", "--abbrev-ref", "--symbolic-full-name", "@{u}"],
    {
      cwd: repoRoot,
      encoding: "utf8",
      stdio: ["inherit", "pipe", "pipe"]
    }
  );

  if (upstreamResult.status === 0) {
    runChecked("git", ["push", remote, branch]);
    return;
  }

  runChecked("git", ["push", "-u", remote, branch]);
}

function remoteGitPrecheck(): void {
  const allowedRemoteUntrackedPattern = Array.from(ALLOWED_REMOTE_UNTRACKED)
    .map((value) => shellPattern(value))
    .join("|");

  runRemoteScript(
    "Remote Precheck",
    `#!/usr/bin/env bash
set -euo pipefail

cd ${shellEscape(REMOTE_REPO)}
status="$(git status --porcelain=v1 --branch)"
git status --short --branch
git branch --show-current

while IFS= read -r line; do
  [[ -z "$line" ]] && continue
  case "$line" in
    '## '*)
      ;;
    '?? '*)
      path="\${line#?? }"
      case "$path" in
        ${allowedRemoteUntrackedPattern})
          ;;
        *)
          echo "Refusing rollout: unexpected untracked path on server: $path" >&2
          exit 1
          ;;
      esac
      ;;
    *)
      echo "Refusing rollout: tracked local modifications on server: $line" >&2
      exit 1
      ;;
  esac
done <<< "$status"
`
  );
}

function remoteRuntimePrecheck(runtime: DeployRuntime, scope: DeployScope): void {
  if (runtime === "docker") {
    runRemoteScript(
      "Remote Runtime Precheck",
      `#!/usr/bin/env bash
set -euo pipefail

cd ${shellEscape(REMOTE_DOCKER_DEPLOYMENT)}
command -v docker >/dev/null 2>&1

docker compose version >/dev/null

if docker ps --format '{{.Names}} {{.Label "com.docker.compose.project"}}' | grep -q '^islandflow-.* islandflow$'; then
  echo '[deploy] Warning: found an additional compose project named "islandflow" on the server.' >&2
  echo '[deploy] The live VPS should normally use only the deployment/docker stack (compose project "islandflow-vps").' >&2
  echo '[deploy] The repo-root docker-compose.yml is for local infra and can create duplicate exposed NATS, ClickHouse, and Redis services on the VPS.' >&2
fi
`
    );
    return;
  }

  const units = nativeUnitsForScope(scope)
    .map((value) => shellEscape(value))
    .join(" ");
  runRemoteScript(
    "Remote Runtime Precheck",
    `#!/usr/bin/env bash
set -euo pipefail

cd ${shellEscape(REMOTE_REPO)}

if [[ -x "$HOME/.bun/bin/bun" ]]; then
  export PATH="$HOME/.bun/bin:$PATH"
fi

if ! command -v bun >/dev/null 2>&1; then
  echo "Refusing native rollout: bun is not installed on the server." >&2
  echo "The current supported VPS path remains --runtime docker." >&2
  echo "See deployment/native/README.md for native prerequisites." >&2
  exit 1
fi

if ! command -v systemctl >/dev/null 2>&1; then
  echo "Refusing native rollout: systemctl is not available on the server." >&2
  echo "See deployment/native/README.md for native prerequisites." >&2
  exit 1
fi

if ! ${NATIVE_SYSTEMCTL_PREFIX} --version >/dev/null 2>&1; then
  echo "Refusing native rollout: cannot run ${NATIVE_SYSTEMCTL_PREFIX}." >&2
  echo "If the server uses user units, try DEPLOY_NATIVE_SYSTEMCTL_PREFIX='systemctl --user'." >&2
  echo "If the server uses system units, ensure passwordless sudo for this command or use --runtime docker." >&2
  exit 1
fi

declare -a units=(${units})
for unit in "\${units[@]}"; do
  load_state="$(${NATIVE_SYSTEMCTL_PREFIX} show --property=LoadState --value "$unit" 2>/dev/null || true)"
  if [[ -z "$load_state" || "$load_state" == "not-found" ]]; then
    echo "Refusing native rollout: missing systemd unit $unit" >&2
    echo "See deployment/native/README.md for expected unit names and overrides." >&2
    echo "Use --runtime docker for the current supported VPS path." >&2
    exit 1
  fi
done
`
  );
}

function remoteGitUpdateScript(mode: DeployMode, remote: string, branch: string | null): string {
  const escapedBranch = branch ? shellEscape(branch) : null;
  const escapedRemote = shellEscape(remote);
  const switchCommand =
    mode === "main"
      ? `git switch main\ngit pull --ff-only ${escapedRemote} main`
      : `git switch ${escapedBranch} || git switch -c ${escapedBranch} --track ${escapedRemote}/${escapedBranch}\ngit pull --ff-only ${escapedRemote} ${escapedBranch}`;

  return `cd ${shellEscape(REMOTE_REPO)}\ngit remote get-url ${escapedRemote} >/dev/null\ngit fetch ${escapedRemote}\n${switchCommand}`;
}

function remoteDockerRollout(
  mode: DeployMode,
  remote: string,
  branch: string | null,
  scope: DeployScope,
  forceRecreate: boolean,
  noBuild: boolean
): void {
  const rolloutServices = dockerServicesForScope(scope);
  const upArgs = ["up", "-d"];
  if (forceRecreate) {
    upArgs.push("--force-recreate");
  }
  const buildServices = dockerBuildServicesForScope(scope);
  const buildCommand = noBuild ? null : `docker compose build ${buildServices.join(" ")}`;
  const upCommand = `docker compose ${[...upArgs, ...rolloutServices].join(" ")}`;

  runRemoteScript(
    "Remote Rollout",
    `#!/usr/bin/env bash
set -euo pipefail

${remoteGitUpdateScript(mode, remote, branch)}

cd ${shellEscape(REMOTE_DOCKER_DEPLOYMENT)}
${buildCommand ? `${buildCommand}\n` : ""}${upCommand}
`
  );
}

function remoteNativeRollout(
  mode: DeployMode,
  remote: string,
  branch: string | null,
  scope: DeployScope,
  noBuild: boolean
): void {
  const units = nativeUnitsForScope(scope)
    .map((value) => shellEscape(value))
    .join(" ");
  const buildSteps: string[] = [];

  if (!noBuild) {
    buildSteps.push("bun install --frozen-lockfile");
    if (scopeIncludesWeb(scope)) {
      buildSteps.push("bun --cwd=apps/web run build");
    }
  }

  buildSteps.push(
    `${NATIVE_SYSTEMCTL_PREFIX} restart ${nativeUnitsForScope(scope)
      .map((value) => shellEscape(value))
      .join(" ")}`
  );

  runRemoteScript(
    "Remote Rollout",
    `#!/usr/bin/env bash
set -euo pipefail

if [[ -x "$HOME/.bun/bin/bun" ]]; then
  export PATH="$HOME/.bun/bin:$PATH"
fi

${remoteGitUpdateScript(mode, remote, branch)}

cd ${shellEscape(REMOTE_REPO)}
${buildSteps.join("\n")}

declare -a units=(${units})
for unit in "\${units[@]}"; do
  ${NATIVE_SYSTEMCTL_PREFIX} is-active --quiet "$unit"
done
`
  );
}

function remoteRollout(
  mode: DeployMode,
  remote: string,
  runtime: DeployRuntime,
  branch: string | null,
  scope: DeployScope,
  forceRecreate: boolean,
  noBuild: boolean
): void {
  if (runtime === "docker") {
    remoteDockerRollout(mode, remote, branch, scope, forceRecreate, noBuild);
    return;
  }

  remoteNativeRollout(mode, remote, branch, scope, noBuild);
}

function remoteDockerVerification(scope: DeployScope, fast: boolean): void {
  const psServices = dockerServicesForScope(scope);
  const logServices = dockerLogServicesForScope(scope);
  const psCommand =
    psServices.length > 0 ? `docker compose ps ${psServices.join(" ")}` : "docker compose ps";
  const logCommand = fast
    ? `echo '[deploy] Fast mode: skipping docker compose logs tail for quicker feedback.'`
    : `docker compose logs --tail=100 ${logServices.join(" ")}`;
  const checks: string[] = [];

  if (scopeIncludesApi(scope)) {
    checks.push(
      `docker compose exec -T api bun -e 'const r = await fetch("http://127.0.0.1:4000/health"); if (!r.ok) throw new Error("api healthcheck failed: " + r.status); console.log(await r.text())'`
    );
  }

  if (scopeIncludesWeb(scope)) {
    checks.push(
      `docker compose exec -T web bun -e 'const r = await fetch("http://127.0.0.1:3000/"); if (!r.ok) throw new Error("web healthcheck failed: " + r.status); console.log(r.status)'`
    );
  }

  runRemoteScript(
    "Remote Verification",
    `#!/usr/bin/env bash
set -euo pipefail

cd ${shellEscape(REMOTE_DOCKER_DEPLOYMENT)}
${psCommand}
${logCommand}
${checks.join("\n")}
`
  );
}

function remoteNativeVerification(scope: DeployScope, fast: boolean): void {
  const units = nativeUnitsForScope(scope)
    .map((value) => shellEscape(value))
    .join(" ");
  const checks: string[] = [];

  if (scope === "full" || scope === "api" || scope === "services" || scope === "workers") {
    checks.push("./deployment/native/check-native-infra.sh");
  }

  if (scopeIncludesApi(scope)) {
    checks.push("curl -fksS http://127.0.0.1:4000/health");
  }

  if (scopeIncludesWeb(scope)) {
    checks.push("curl -I -fksS http://127.0.0.1:3000/");
  }

  runRemoteScript(
    "Remote Verification",
    `#!/usr/bin/env bash
set -euo pipefail

cd ${shellEscape(REMOTE_REPO)}

if [[ -x "$HOME/.bun/bin/bun" ]]; then
  export PATH="$HOME/.bun/bin:$PATH"
fi

declare -a units=(${units})
for unit in "\${units[@]}"; do
  ${NATIVE_SYSTEMCTL_PREFIX} is-active --quiet "$unit"
  ${fast ? 'echo "[deploy] Fast mode: skipping unit status and recent journal dump for $unit."' : `${NATIVE_SYSTEMCTL_PREFIX} status --no-pager "$unit" || true\n  journalctl -u "$unit" -n 50 --no-pager || true`}
done
${checks.join("\n")}
`
  );
}

function remoteVerification(runtime: DeployRuntime, scope: DeployScope, fast: boolean): void {
  if (runtime === "docker") {
    remoteDockerVerification(scope, fast);
    return;
  }

  remoteNativeVerification(scope, fast);
}

function publicVerification(scope: DeployScope, fast: boolean): void {
  section("Public Verification");
  if (scopeIncludesWeb(scope)) {
    runChecked("curl", ["-I", "-fksS", PUBLIC_APP_URL]);
  } else {
    console.log("[deploy] Skipping public app HEAD check because web scope is not included.");
  }

  if (scopeIncludesApi(scope) && PUBLIC_API_HEALTH_URL) {
    runChecked("curl", ["-fksS", PUBLIC_API_HEALTH_URL]);
    return;
  }

  if (scopeIncludesApi(scope)) {
    if (fast) {
      console.log(
        "[deploy] Fast mode: skipping scripts/check-public-api-routes.ts route suite. Set DEPLOY_PUBLIC_API_HEALTH_URL to keep a public API health probe in fast mode."
      );
      return;
    }
    runChecked("bun", ["run", "scripts/check-public-api-routes.ts", PUBLIC_APP_URL]);
  }
}

function main(): void {
  const options = parseArgs(process.argv.slice(2));
  const scope = effectiveScope(options.scope, options.runtime, options.fast);
  const timings: PhaseTiming[] = [];
  const currentBranch = options.mode === "current-branch" ? currentBranchName() : null;
  const deployRemote = resolveDeployRemote(options.mode, currentBranch);
  assertSshKeyExists();
  printRuntimeAdvisory(options.runtime);

  console.log(
    `Deploying ${options.mode === "main" ? `${deployRemote}/main` : "the current local branch"} ` +
      `via ${describeRuntime(options.runtime)} (${describeScope(scope)}${options.fast ? ", fast mode" : ""}).`
  );
  console.log(`[deploy] Using git remote: ${deployRemote}`);
  console.log(
    `[deploy] Execution mode: ${isLocalServerExecution ? "local server checkout" : `ssh to ${REMOTE_HOST}`}`
  );
  if (options.fast && options.scope === "full") {
    console.log(
      `[deploy] Fast mode changed default full scope to ${options.runtime === "native" ? "--workers-only" : "--services-only"}.`
    );
  }

  if (options.mode === "main") {
    timedPhase(timings, "local precheck", () =>
      localMainPrecheck(deployRemote, options.runtime, scope, options.noBuild)
    );
    timedPhase(timings, "remote git precheck", () => remoteGitPrecheck());
    timedPhase(timings, "remote runtime precheck", () =>
      remoteRuntimePrecheck(options.runtime, scope)
    );
    timedPhase(timings, "remote rollout", () =>
      remoteRollout(
        options.mode,
        deployRemote,
        options.runtime,
        null,
        scope,
        options.forceRecreate,
        options.noBuild
      )
    );
  } else {
    const branch = currentBranch;
    if (!branch) {
      console.error("Unable to resolve current branch for current-branch deploy mode.");
      process.exit(1);
    }
    timedPhase(timings, "local precheck", () =>
      localBranchPrecheck(deployRemote, branch, options.runtime, scope, options.noBuild)
    );
    timedPhase(timings, "local publish", () => publishCurrentBranch(deployRemote, branch));
    timedPhase(timings, "remote git precheck", () => remoteGitPrecheck());
    timedPhase(timings, "remote runtime precheck", () =>
      remoteRuntimePrecheck(options.runtime, scope)
    );
    timedPhase(timings, "remote rollout", () =>
      remoteRollout(
        options.mode,
        deployRemote,
        options.runtime,
        branch,
        scope,
        options.forceRecreate,
        options.noBuild
      )
    );
  }

  timedPhase(timings, "remote verification", () =>
    remoteVerification(options.runtime, scope, options.fast)
  );
  timedPhase(timings, "public verification", () => publicVerification(scope, options.fast));
  printTimingSummary(timings);
}

main();
