import process from 'node:process'
import { Command } from 'commander'
import type { AgentId } from '../../core/agent.ts'
import { logger } from '../../core/logger.ts'
import { readManifest } from '../backup/manifest.ts'
import { probeService, uninstallService } from '../service/index.ts'
import { hardCleanupAgents, runUnwire, type UnwireAgentResult } from '../wire/orchestrate.ts'
import { readRoutes } from '../wire/routes.ts'

type UnwireOptions = {
  force: boolean
  stopDaemon: boolean
}

export const unwireCommand = new Command('unwire')
  .description('Restore agent configs. Leaves the daemon running so historical traces stay viewable.')
  .argument(
    '[agents...]',
    'Unwire just these agents (others stay wired). Defaults to all wired.',
  )
  .option(
    '--force',
    'Whole-file restore from the backup snapshot instead of reverse-replaying ' +
      "the recorded edits. Discards any changes the agent made since wiring.",
    false,
  )
  .option('--stop-daemon', 'Also stop and uninstall the daemon service.', false)
  .action(async (agents: string[], options: UnwireOptions) => {
    const manifest = await readManifest()
    if (manifest.entries.length === 0) {
      // Nothing wired in the manifest — but `--stop-daemon` still has work
      // to do if a prior soft unwire left the daemon running and tombstoned
      // routes / leftover secrets behind. Sweep them, then stop the
      // service. Without `--stop-daemon` there is genuinely nothing to do.
      if (!options.stopDaemon) {
        logger.print('Nothing wired.')
        return
      }
      await sweepAndStopDaemon()
      return
    }

    const only = agents.length > 0 ? (agents as AgentId[]) : undefined
    const filteredCount = only
      ? manifest.entries.filter((e) => only.includes(e.agent)).length
      : manifest.entries.length

    if (filteredCount === 0) {
      logger.print('No matching wired entries.')
      return
    }

    logger.print(
      `Taking OpenThomas off the wire (${filteredCount} entr${filteredCount === 1 ? 'y' : 'ies'})…`,
    )

    const { results, remainingEntries } = await runUnwire({
      agents: only,
      force: options.force,
    })

    const restoredAgents: AgentId[] = []
    for (const r of results) {
      renderUnwireResult(r)
      if (r.ok) restoredAgents.push(r.agent)
    }

    if (!options.stopDaemon) {
      logger.print('')
      if (remainingEntries > 0) {
        logger.print(
          `ⓘ ${remainingEntries} entr${remainingEntries === 1 ? 'y' : 'ies'} still wired; daemon left running`,
        )
      } else {
        logger.print('ⓘ daemon left running so historical traces stay viewable — use `openthomas unwire --stop-daemon` to also stop the service')
      }
      if (restoredAgents.length > 0) {
        logger.print(
          `ⓘ already-running agent processes will keep routing through OpenThomas until you restart them (config restored for new launches)`,
        )
      }
      return
    }

    // --stop-daemon: only actually stop if nothing is still wired. Other
    // agents not in the positional list stay wired and need the daemon up.
    if (remainingEntries > 0) {
      logger.print('')
      logger.print(
        `ⓘ ${remainingEntries} entr${remainingEntries === 1 ? 'y' : 'ies'} still wired; ignoring --stop-daemon`,
      )
      return
    }

    // Daemon is going away — now's the right moment to drop the
    // wire-authored routes / secrets / seeded registry rows / dead
    // routing-policy entries. Errors are reported but never block the
    // service uninstall.
    const hard = await hardCleanupAgents(restoredAgents)
    for (const h of hard) {
      if (h.error) {
        logger.print(`      ⚠ ${h.agent} state cleanup partial: ${h.error}`)
      } else if (h.note) {
        logger.print(`      ⓘ ${h.agent} cleaned: ${h.note}`)
      }
    }

    await stopDaemonService()
  })

function renderUnwireResult(r: UnwireAgentResult): void {
  if (!r.ok) {
    logger.print(`  ✗ ${r.agent}  ${r.error ?? 'unknown error'}`)
    process.exitCode = 1
    return
  }
  logger.print(`  ✓ ${r.agent}  restored ${r.filesRestored} file(s)`)
  if (r.skipped.length > 0) {
    logger.print(
      `      ⓘ ${r.skipped.length} setting(s) modified after wiring, left as-is`,
    )
    for (const s of r.skipped) {
      logger.print(`        · ${s.pointer} (${s.reason})`)
    }
  }
  if (r.cleanupNote) {
    logger.print(`      ⓘ ${r.agent} cleaned: ${r.cleanupNote}`)
  }
}

/** Probe the launchd / systemd service and uninstall it if installed. */
async function stopDaemonService(): Promise<void> {
  const sState = await probeService()
  if (sState.state === 'unsupported' || sState.state === 'not-installed') {
    logger.print('')
    logger.print(`ⓘ daemon service not installed (${sState.state}); nothing to stop`)
    return
  }
  try {
    await uninstallService()
    logger.print('')
    logger.print('✓ daemon stopped + service uninstalled')
  } catch (err) {
    logger.print('')
    logger.print(`⚠ service uninstall failed: ${(err as Error).message}`)
  }
}

/** Called for `openthomas unwire --stop-daemon` when the manifest is already
 *  empty (a prior soft unwire restored every agent but left the daemon
 *  running). Sweeps the leftover tombstoned routes / secrets / registry
 *  rows for each agent that still appears in routes.json, then stops the
 *  service. */
async function sweepAndStopDaemon(): Promise<void> {
  const routes = await readRoutes()
  const agents = new Set<AgentId>()
  for (const key of Object.keys(routes.routes)) {
    const slash = key.indexOf('/')
    if (slash > 0) agents.add(key.slice(0, slash) as AgentId)
  }
  if (agents.size > 0) {
    logger.print(
      `Sweeping leftover state for ${agents.size} agent${agents.size === 1 ? '' : 's'}…`,
    )
    const hard = await hardCleanupAgents([...agents])
    for (const h of hard) {
      if (h.error) {
        logger.print(`      ⚠ ${h.agent} state cleanup partial: ${h.error}`)
      } else if (h.note) {
        logger.print(`      ⓘ ${h.agent} cleaned: ${h.note}`)
      }
    }
  }
  await stopDaemonService()
}
