import { createInterface } from 'node:readline/promises'
import process from 'node:process'
import { Command } from 'commander'
import { logger } from '../../core/logger.ts'
import { DAEMON_BASE_URL } from '../../core/paths.ts'

type PruneOptions = {
  before: string
  yes: boolean
  json: boolean
}

type PrunePreview = {
  cutoff: number
  totalActions: number
  totalRuns: number
  totalThreads: number
  toDeleteActions: number
  toDeleteRuns: number
  toDeleteThreads: number
  estimatedBytes: number
}

type PruneResult = PrunePreview & {
  deletedActions: number
  deletedRuns: number
  deletedThreads: number
  vacuumed: boolean
}

export const pruneCommand = new Command('prune')
  .description('Delete captured traces older than a cutoff to reclaim disk.')
  .option('--before <when>', "'7d' | '30d' | '90d' | ISO date — keep traces newer than this", '30d')
  .option('-y, --yes', 'Skip confirmation.', false)
  .option('--json', 'Output JSON for piping.', false)
  .action(async (options: PruneOptions) => {
    let cutoff: number
    try {
      cutoff = parseBefore(options.before)
    } catch (err) {
      logger.print(`error: ${(err as Error).message}`)
      process.exitCode = 2
      return
    }

    // 1. Preview
    let preview: PrunePreview
    try {
      const r = await fetch(`${DAEMON_BASE_URL}/api/prune?cutoff=${cutoff}&dry=1`, {
        signal: AbortSignal.timeout(30_000),
      })
      if (!r.ok) {
        logger.print(`Daemon error: HTTP ${r.status}`)
        process.exitCode = 1
        return
      }
      preview = (await r.json()) as PrunePreview
    } catch {
      logger.print('Daemon not reachable. Start it with `openthomas wire` or `openthomas daemon`.')
      process.exitCode = 1
      return
    }

    if (options.json) {
      process.stdout.write(`${JSON.stringify(preview, null, 2)}\n`)
      return
    }

    if (preview.toDeleteActions === 0) {
      logger.print(`Nothing to prune — no actions older than ${formatCutoff(cutoff)}.`)
      return
    }

    const bytes = formatBytes(preview.estimatedBytes)
    logger.print('')
    logger.print(`Prune preview — cutoff ${formatCutoff(cutoff)}:`)
    logger.print(`  ${preview.toDeleteActions.toLocaleString()} actions (of ${preview.totalActions.toLocaleString()})`)
    logger.print(`  ${preview.toDeleteRuns.toLocaleString()} runs (of ${preview.totalRuns.toLocaleString()})`)
    logger.print(`  ${preview.toDeleteThreads.toLocaleString()} empty threads`)
    logger.print(`  ~${bytes} reclaimable after VACUUM`)
    logger.print('')

    if (!options.yes) {
      const rl = createInterface({ input: process.stdin, output: process.stdout })
      const answer = await rl.question('Proceed with delete? [y/N] ')
      rl.close()
      if (answer.trim().toLowerCase() !== 'y') {
        logger.print('Aborted.')
        return
      }
    }

    // 2. Execute
    logger.print('Deleting…')
    try {
      const r = await fetch(`${DAEMON_BASE_URL}/api/prune?cutoff=${cutoff}`, {
        method: 'POST',
        signal: AbortSignal.timeout(300_000),
      })
      if (!r.ok) {
        logger.print(`Daemon error: HTTP ${r.status}`)
        process.exitCode = 1
        return
      }
      const result = (await r.json()) as PruneResult
      logger.print(`✓ deleted ${result.deletedActions.toLocaleString()} actions, ${result.deletedRuns.toLocaleString()} runs, ${result.deletedThreads.toLocaleString()} threads`)
      if (result.vacuumed) {
        logger.print('✓ database vacuumed')
      } else {
        logger.print('⚠ VACUUM skipped (set THOMAS_PRUNE_VACUUM=1 to enable; takes ~1min and pauses writes)')
      }
    } catch (err) {
      logger.print(`error during prune: ${(err as Error).message}`)
      process.exitCode = 1
    }
  })

function parseBefore(s: string): number {
  const m = /^(\d+)([hdwm])$/.exec(s)
  if (m?.[1] && m[2]) {
    const n = Number.parseInt(m[1], 10)
    const ms =
      m[2] === 'h'
        ? n * 3_600_000
        : m[2] === 'd'
          ? n * 86_400_000
          : m[2] === 'w'
            ? n * 7 * 86_400_000
            : n * 30 * 86_400_000
    return Date.now() - ms
  }
  const t = Date.parse(s)
  if (Number.isNaN(t)) throw new Error(`invalid --before: ${s} (use '30d', '12w', '6m', or ISO date)`)
  return t
}

function formatCutoff(ms: number): string {
  return new Date(ms).toISOString().slice(0, 19).replace('T', ' ') + 'Z'
}

function formatBytes(n: number): string {
  if (n < 1024) return `${n}B`
  if (n < 1024 * 1024) return `${(n / 1024).toFixed(1)}KB`
  if (n < 1024 * 1024 * 1024) return `${(n / 1024 / 1024).toFixed(1)}MB`
  return `${(n / 1024 / 1024 / 1024).toFixed(2)}GB`
}
