/**
 * CliDeps factory — PLAN_RELAY_PHASE1 C2. Wires the abstract `CliDeps` seams (consumed by
 * `runCli`) to their real implementations: env-driven config, the on-disk keystore, Ed25519
 * identity generation, §4.5 pairing redemption, the supervised tunnel, and OS service install.
 * All side effects live here so `cli.ts`/`runCli` stay pure and unit-testable.
 */
import { execFile } from 'node:child_process'
import { mkdirSync, writeFileSync } from 'node:fs'
import { homedir, userInfo } from 'node:os'
import { dirname } from 'node:path'
import { fileURLToPath } from 'node:url'
import type { CliDeps } from '../cli.js'
import type { AgentConfig } from '../config/agentConfig.js'
import { loadAgentConfig } from '../config/agentConfig.js'
import { openKeystore } from '../keys/keystore.js'
import { generateIdentity } from '../keys/identity.js'
import { redeemPairingCode } from '../enroll/pair.js'
import { runTunnel } from '../transport/runTunnel.js'
import {
  detectPlatform,
  installService as installServiceUnit,
  uninstallService as uninstallServiceUnit,
  type InstallDeps,
  type ServicePlatform,
} from '../service/install.js'

/** Resolve this process's own executable path (the bundled `dist/cli.js`) for the service unit. */
function selfBinPath(): string {
  return fileURLToPath(import.meta.url)
}

function runCommand(cmd: string, args: readonly string[]): Promise<void> {
  return new Promise<void>((resolve, reject) => {
    execFile(cmd, [...args], (err) => (err ? reject(err) : resolve()))
  })
}

function realInstallDeps(): InstallDeps {
  return {
    writeFile: (path, content) => {
      mkdirSync(dirname(path), { recursive: true })
      writeFileSync(path, content)
    },
    runCommand,
    getuid: () => (typeof process.getuid === 'function' ? process.getuid() : 0),
    homedir,
    username: () => userInfo().username,
    binPath: selfBinPath,
  }
}

/** Map the current OS to its service manager, or fail fast with a clear message. */
function requirePlatform(): ServicePlatform {
  const platform = detectPlatform(process.platform)
  if (platform === null) {
    throw new Error(`service install/uninstall is unsupported on platform '${process.platform}'`)
  }
  return platform
}

/** Build the concrete CliDeps used by the real CLI entrypoint. */
export function createCliDeps(): CliDeps {
  return {
    loadConfig: () => loadAgentConfig(process.env),
    openKeystore: (stateDir) => openKeystore(stateDir),
    generateIdentity: () => generateIdentity(),
    redeem: (cfg: AgentConfig, code, id, ks) => redeemPairingCode(cfg.enrollUrl, code, id, ks),
    runTunnel: async (cfg, ks) => {
      const handle = await runTunnel(cfg, ks)
      const onSignal = (): void => {
        void handle.stop()
      }
      process.once('SIGTERM', onSignal)
      process.once('SIGINT', onSignal)
      return handle.done
    },
    installService: (cfg) => installServiceUnit(cfg, requirePlatform(), realInstallDeps()),
    uninstallService: () => uninstallServiceUnit(requirePlatform(), { runCommand, homedir }),
    print: (line) => {
      process.stdout.write(`${line}\n`)
    },
  }
}
