import process from 'node:process'
import { Command } from 'commander'
import { logger } from '../../core/logger.ts'
import { DAEMON_BASE_URL } from '../../core/paths.ts'
import { formatCost, formatDuration, formatTime, formatTokenBuckets } from '../tui/table.ts'

type ShowOptions = {
  full: boolean
  json: boolean
  raw: boolean
}

type RunDetail = {
  run: {
    id: string
    threadId: string
    agent: string
    status: string
    startedAt: number
    endedAt: number | null
    durMs: number | null
    costUsd: number
    tokensIn: number | null
    tokensOut: number | null
    cacheReadTokens: number | null
    cacheWriteTokens: number | null
    actionCount: number
  }
  actions: ActionSummary[]
  toolTargets?: ToolTargetSummary[]
}

type ToolTargetSummary = {
  kind: string
  op: string
  target: string
  toolName: string
  ts: number
}

type ActionSummary = {
  id: string
  kind: string
  sourceAgent: string
  ts: number
  durMs: number
  costUsd: number
  tokensIn: number | null
  tokensOut: number | null
  cacheReadTokens: number | null
  cacheWriteTokens: number | null
  riskTags: unknown[]
  // biome-ignore lint/suspicious/noExplicitAny: payload shape is per-kind.
  payload: any
  rawReq?: string | null
  rawRes?: string | null
}

export const showCommand = new Command('show')
  .description('Show full detail of a run.')
  .argument('<run-id>', 'Run ID (full or unique short prefix).')
  .option('--full', 'Show full prompts/responses (no truncation).', false)
  .option('--json', 'Output normalized JSON.', false)
  .option('--raw', 'Output original wire-level req/res.', false)
  .action(async (runId: string, options: ShowOptions) => {
    const url =
      `${DAEMON_BASE_URL}/api/runs/${encodeURIComponent(runId)}` + (options.raw ? '?raw=1' : '')
    let res: Response
    try {
      res = await fetch(url, { signal: AbortSignal.timeout(3000) })
    } catch {
      logger.print('Daemon not reachable. Start it with `openthomas wire` or `openthomas daemon`.')
      process.exitCode = 1
      return
    }
    if (res.status === 404) {
      logger.print(`Run not found: ${runId}`)
      process.exitCode = 1
      return
    }
    if (!res.ok) {
      logger.print(`Daemon error: HTTP ${res.status}`)
      process.exitCode = 1
      return
    }

    const detail = (await res.json()) as RunDetail

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

    const r = detail.run
    logger.print(`Run    ${r.id}`)
    logger.print(`Thread ${r.threadId}`)
    logger.print(`Agent  ${r.agent}`)
    logger.print(`Status ${r.status}`)
    logger.print(`When   ${formatTime(r.startedAt)}  (${formatDuration(r.durMs)})`)
    logger.print(
      `Cost   ${formatCost(r.costUsd)}  cached/fresh/out=` +
        formatTokenBuckets(r.tokensIn, r.tokensOut, r.cacheReadTokens, r.cacheWriteTokens),
    )
    logger.print('')

    const targets = detail.toolTargets ?? []
    if (targets.length > 0 && !options.raw) {
      const files = targets.filter((t) => t.kind === 'fs')
      const shells = targets.filter((t) => t.kind === 'shell')
      if (files.length > 0) {
        const uniq = [...new Set(files.map((f) => `${f.op} ${f.target}`))]
        logger.print(`Files (${uniq.length}):`)
        for (const f of uniq.slice(0, 20)) logger.print(`  ${f}`)
        if (uniq.length > 20) logger.print(`  … +${uniq.length - 20} more`)
      }
      if (shells.length > 0) {
        logger.print(`Commands (${shells.length}):`)
        for (const s of shells.slice(0, 20)) {
          logger.print(`  $ ${s.target.replace(/\s+/g, ' ').slice(0, 120)}`)
        }
        if (shells.length > 20) logger.print(`  … +${shells.length - 20} more`)
      }
      logger.print('')
    }

    if (detail.actions.length === 0) {
      logger.print('(no actions)')
      return
    }

    for (const a of detail.actions) {
      logger.print(`─── action ${a.id} ─────────────────────────`)
      logger.print(`kind:    ${a.kind}`)
      logger.print(`agent:   ${a.sourceAgent}`)
      logger.print(`when:    ${formatTime(a.ts)}  (${formatDuration(a.durMs)})`)
      if (a.costUsd > 0) {
        logger.print(
          `cost:    ${formatCost(a.costUsd)}  cached/fresh/out=` +
            formatTokenBuckets(a.tokensIn, a.tokensOut, a.cacheReadTokens, a.cacheWriteTokens),
        )
      }
      if (a.riskTags.length > 0) {
        for (const t of a.riskTags as Array<{ tag: string; severity: string }>) {
          const marker = t.severity === 'high' ? '⚠' : t.severity === 'warn' ? '!' : 'ⓘ'
          logger.print(`risk:    ${marker} ${t.tag} (${t.severity})`)
        }
      }

      if (options.raw) {
        if (a.rawReq != null || a.rawRes != null) {
          if (a.rawReq != null) {
            logger.print('request (raw wire):')
            logger.print(a.rawReq)
          }
          if (a.rawRes != null) {
            logger.print('response (raw wire):')
            logger.print(a.rawRes)
          }
        } else {
          logger.print('(no raw wire bytes captured for this action)')
        }
        logger.print('')
        continue
      }

      if (a.kind === 'mcp_call') {
        const p = a.payload
        const arrow = p.direction === 'request' ? '→' : p.direction === 'response' ? '←' : '·'
        logger.print(`server:  ${p.server} ${arrow} ${p.direction}`)
        if (p.method) logger.print(`method:  ${p.method}`)
        if (p.jsonrpcId != null) logger.print(`id:      ${p.jsonrpcId}`)
        if (p.params !== undefined) {
          logger.print(
            `params:  ${truncate(JSON.stringify(p.params), options.full ? Number.POSITIVE_INFINITY : 200)}`,
          )
        }
        if (p.result !== undefined) {
          logger.print(
            `result:  ${truncate(JSON.stringify(p.result), options.full ? Number.POSITIVE_INFINITY : 200)}`,
          )
        }
        if (p.error !== undefined) {
          logger.print(
            `error:   ${truncate(JSON.stringify(p.error), options.full ? Number.POSITIVE_INFINITY : 200)}`,
          )
        }
      } else if (a.kind === 'model_call') {
        const p = a.payload
        logger.print(`model:   ${p.model ?? '?'}`)
        logger.print(`endpoint: ${p.endpoint ?? '?'}`)
        if (p.stopReason) logger.print(`stop:    ${p.stopReason}`)
        if (p.error) logger.print(`error:   ${p.error}`)

        const sys = typeof p.systemPrompt === 'string' ? p.systemPrompt : ''
        if (sys)
          logger.print(`system:  ${truncate(sys, options.full ? Number.POSITIVE_INFINITY : 200)}`)

        if (Array.isArray(p.messages) && p.messages.length > 0) {
          logger.print(`messages: ${p.messages.length}`)
          if (options.full) {
            for (const m of p.messages) logger.print(`  ${JSON.stringify(m)}`)
          }
        }

        if (Array.isArray(p.response) && p.response.length > 0) {
          logger.print(`response blocks: ${p.response.length}`)
          for (const b of p.response) {
            // biome-ignore lint/suspicious/noExplicitAny: block shape is per-type.
            const block: any = b
            if (block.type === 'text') {
              logger.print(
                `  [text] ${truncate(block.text, options.full ? Number.POSITIVE_INFINITY : 200)}`,
              )
            } else if (block.type === 'tool_use') {
              const args = JSON.stringify(block.input)
              logger.print(
                `  [tool_use ${block.name}] ${truncate(args, options.full ? Number.POSITIVE_INFINITY : 200)}`,
              )
            } else if (block.type === 'thinking') {
              logger.print(
                `  [thinking] ${truncate(block.text, options.full ? Number.POSITIVE_INFINITY : 200)}`,
              )
            } else {
              logger.print(`  [${block.type ?? 'unknown'}]`)
            }
          }
        }
      } else {
        logger.print(
          `payload: ${truncate(JSON.stringify(a.payload), options.full ? Number.POSITIVE_INFINITY : 200)}`,
        )
      }
      logger.print('')
    }
  })

function truncate(s: string, n: number): string {
  if (!Number.isFinite(n) || s.length <= n) return s
  return `${s.slice(0, n)}… (+${s.length - n} chars; --full to expand)`
}
