/**
 * Linux systemd unit writer — PLAN_RELAY_AGENT T17. Runs as the LOGGED-IN USER (never root,
 * EXPLORE §4d), restart-on-failure; no secrets in the unit (key/cert stay in the keystore).
 *
 * PLAN_NATIVE_TUNNEL S2: the unit can now inject the per-host tunnel env via an
 * `EnvironmentFile=` line (preferred — keeps values out of the world-readable unit) and/or
 * inline `Environment=` lines from a caller-supplied env map. Inline `Environment=` is emitted
 * after `EnvironmentFile=` so an explicit value overrides the file on conflict.
 */
import type { ServiceEnv } from './launchd.js'

const UNIT_NAME = 'web-terminal-agent.service'

/** DEL (0x7F) and everything below the printable ASCII range are rejected in env values. */
const FIRST_PRINTABLE_ASCII = 0x20
const DEL_CODE = 0x7f

export interface SystemdUnitOptions {
  /** Inline env map → one `Environment="KEY=value"` line each (keys sorted, values escaped). */
  readonly env?: ServiceEnv
  /** Path referenced by a single `EnvironmentFile=` line (preferred over inline for secrets). */
  readonly envFile?: string
}

export function systemdUnitName(): string {
  return UNIT_NAME
}

export function systemdUnitPath(homedir: string): string {
  return `${homedir}/.config/systemd/user/${UNIT_NAME}`
}

/**
 * True if `value` contains any control character (C0 range below 0x20, or DEL 0x7F). A raw
 * newline/CR would terminate the single `Environment=` line and let the remainder inject arbitrary
 * directives into the `[Service]` section — so such values are rejected rather than escaped.
 */
function hasControlChar(value: string): boolean {
  for (const char of value) {
    const code = char.codePointAt(0)
    if (code === undefined) continue
    if (code < FIRST_PRINTABLE_ASCII || code === DEL_CODE) return true
  }
  return false
}

/** Quote a systemd `Environment=` value: reject control chars, then double-quote escaping `\` and `"`. */
function quoteEnvAssignment(key: string, value: string): string {
  if (hasControlChar(value)) {
    throw new Error(
      `refusing to write systemd Environment for '${key}': value contains a control character ` +
        '(newline/CR/etc.) that could corrupt the [Service] section',
    )
  }
  const escaped = value.replace(/\\/g, '\\\\').replace(/"/g, '\\"')
  return `"${key}=${escaped}"`
}

/** Build the `EnvironmentFile=` / `Environment=` lines (empty array when neither is supplied). */
function environmentLines(options: SystemdUnitOptions): readonly string[] {
  const lines: string[] = []
  if (options.envFile) lines.push(`EnvironmentFile=${options.envFile}`)
  const entries = Object.entries(options.env ?? {}).sort(([a], [b]) => a.localeCompare(b))
  for (const [key, value] of entries) {
    lines.push(`Environment=${quoteEnvAssignment(key, value)}`)
  }
  return lines
}

/**
 * Build the unit. ExecStart = `<bin> run`; Restart=on-failure; User=<user> (never root). When
 * `options.env`/`options.envFile` are supplied, the per-host tunnel env is injected into the
 * `[Service]` section. Pure/immutable — returns a fresh string.
 */
export function buildSystemdUnit(
  binPath: string,
  user: string,
  options: SystemdUnitOptions = {},
): string {
  return [
    '[Unit]',
    'Description=web-terminal host agent (rendezvous relay)',
    'After=network-online.target',
    '',
    '[Service]',
    'Type=simple',
    `ExecStart=${binPath} run`,
    'Restart=on-failure',
    'RestartSec=1',
    `User=${user}`,
    ...environmentLines(options),
    '',
    '[Install]',
    'WantedBy=default.target',
    '',
  ].join('\n')
}

export function systemdEnableCommand(): { cmd: string; args: readonly string[] } {
  return { cmd: 'systemctl', args: ['--user', 'enable', '--now', UNIT_NAME] }
}
export function systemdDisableCommand(): { cmd: string; args: readonly string[] } {
  return { cmd: 'systemctl', args: ['--user', 'disable', '--now', UNIT_NAME] }
}
