import { KvPanel, SectionCard, StatusChip, DetailDrawer, Button, formatDurationMs } from '@overdeck/deck-ui'
import { DataTable, withColumnResizing, withSearch, withSorting, type DataTableColumn } from '@overdeck/deck-ui'
import { useId, useState, type JSX } from 'react'
import type { FactoryEventView, FactoryPhaseView } from '../../lib/factory-types'
import { displayText, formatAbsoluteTimestamp, formatTokenCount, parseTimestampMs } from './factory-helpers'
import { ArtifactLink } from './FactoryTraceTables'

const SUMMARY_KEYS = ['summary', 'message', 'action', 'command', 'cmd', 'status', 'result', 'error', 'reason', 'note', 'client_session_id', 'billing_status']

function parsePayload(payloadJson: string | null): unknown {
  if (!payloadJson) return null
  try {
    return JSON.parse(payloadJson) as unknown
  } catch {
    return payloadJson
  }
}

function compactValue(value: unknown): string | null {
  if (typeof value === 'string') return value.trim() || null
  if (typeof value === 'number' || typeof value === 'boolean') return String(value)
  return null
}

export function eventSummary(event: FactoryEventView): string {
  const parsed = parsePayload(event.payloadJson)
  if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
    const record = parsed as Record<string, unknown>
    const attempt = record.attempt
    const nested = attempt && typeof attempt === 'object' && !Array.isArray(attempt)
      ? (attempt as Record<string, unknown>)
      : null
    const providerFailure = record.provider_failure ?? record.providerFailure
      ?? nested?.provider_failure ?? nested?.providerFailure
    if (providerFailure && typeof providerFailure === 'object' && !Array.isArray(providerFailure)) {
      const failure = providerFailure as Record<string, unknown>
      const kind = compactValue(failure.kind) ?? 'provider failure'
      const detail = compactValue(failure.detail)
      return detail ? `${kind}: ${detail.replace(/\s+/g, ' ')}` : kind
    }
    if (record.usage_estimated === true) {
      const total = compactValue(record.total_tokens)
      const billing = compactValue(record.billing_status) ?? 'unavailable'
      return `usage estimate${total ? `: ${total} tokens` : ''} · billing ${billing}`
    }
    for (const key of SUMMARY_KEYS) {
      const value = compactValue(record[key])
      if (value) return `${key}: ${value.replace(/\s+/g, ' ')}`
    }
    const fields = Object.entries(record).flatMap(([key, value]) => {
      const compact = compactValue(value)
      return compact ? [`${key}: ${compact.replace(/\s+/g, ' ')}`] : []
    })
    if (fields.length > 0) return fields.slice(0, 3).join(' · ')
  }
  if (typeof parsed === 'string' && parsed.trim()) return parsed.trim().replace(/\s+/g, ' ')
  const name = event.name?.trim()
  return name && name !== event.type?.trim() ? name : '—'
}

export function payloadArtifactPaths(payloadJson: string | null): string[] {
  if (!payloadJson) return []
  try {
    const paths = new Set<string>()
    const visit = (value: unknown, key = ''): void => {
      if (typeof value === 'string' && /(artifact|output|stderr|stdout|log).*path|(^|_)path$/i.test(key)) paths.add(value)
      else if (Array.isArray(value)) value.forEach((entry) => visit(entry, key))
      else if (value && typeof value === 'object') Object.entries(value).forEach(([nestedKey, nested]) => visit(nested, nestedKey))
    }
    visit(JSON.parse(payloadJson))
    return Array.from(paths)
  } catch {
    return []
  }
}

function formatOffset(event: FactoryEventView, runStartedAt: string | null): string {
  const eventMs = parseTimestampMs(event.startedAt ?? event.endedAt)
  const runMs = parseTimestampMs(runStartedAt)
  if (eventMs === null || runMs === null) return '—'
  const offsetMs = eventMs - runMs
  const sign = offsetMs < 0 ? '−' : '+'
  const absoluteMs = Math.abs(offsetMs)
  if (absoluteMs < 60_000) return `${sign}${(absoluteMs / 1_000).toFixed(1)}s`
  if (absoluteMs < 3_600_000) return `${sign}${(absoluteMs / 60_000).toFixed(1)}m`
  return `${sign}${(absoluteMs / 3_600_000).toFixed(1)}h`
}

function formattedPayload(payloadJson: string): string {
  try {
    return JSON.stringify(JSON.parse(payloadJson), null, 2)
  } catch {
    return payloadJson
  }
}

function eventKey(event: FactoryEventView): string {
  return `${event.eventId}-${event.rowid}`
}

function chronological(events: FactoryEventView[]): FactoryEventView[] {
  return [...events].sort((left, right) => {
    const leftTime = parseTimestampMs(left.startedAt ?? left.endedAt)
    const rightTime = parseTimestampMs(right.startedAt ?? right.endedAt)
    if (leftTime !== null && rightTime !== null && leftTime !== rightTime) return leftTime - rightTime
    if (leftTime !== null && rightTime === null) return -1
    if (leftTime === null && rightTime !== null) return 1
    return left.rowid - right.rowid
  })
}

type PhaseGroup = {
  key: string
  phase: FactoryPhaseView | null
  label: string
  events: FactoryEventView[]
}

/** Events carry their phase id, not their phase — grouping is what turns six repeated
 * `git_preflight` rows into one preflight the reader can collapse. */
function groupByPhase(events: FactoryEventView[], phases: Map<string, FactoryPhaseView>): PhaseGroup[] {
  const groups = new Map<string, PhaseGroup>()
  for (const event of events) {
    const key = event.phaseId ?? '__run__'
    let group = groups.get(key)
    if (!group) {
      const phase = event.phaseId === null ? null : phases.get(event.phaseId) ?? null
      group = {
        key,
        phase,
        label: phase?.name ?? (event.phaseId === null ? 'Run level' : event.phaseId),
        events: [],
      }
      groups.set(key, group)
    }
    group.events.push(event)
  }
  return Array.from(groups.values())
}

function groupDuration(group: PhaseGroup): string {
  if (group.phase?.durationMs != null) return formatDurationMs(group.phase.durationMs)
  const times = group.events
    .map((event) => parseTimestampMs(event.startedAt ?? event.endedAt))
    .filter((value): value is number => value !== null)
  if (times.length < 2) return '—'
  return formatDurationMs(Math.max(...times) - Math.min(...times))
}

const EVENT_CAPABILITIES = [
  withSorting({ defaultSort: { columnId: 'time', direction: 'asc' } }),
  withSearch({ label: 'Search events' }),
  withColumnResizing(),
]

function EventDetails({ event }: { event: FactoryEventView }): JSX.Element {
  const artifacts = payloadArtifactPaths(event.payloadJson)
  return (
    <div className="mt-4 space-y-3">
      <KvPanel rows={[
        { label: 'event id', value: displayText(event.eventId) },
        { label: 'phase id', value: displayText(event.phaseId) },
        { label: 'parent id', value: displayText(event.parentId) },
        { label: 'name', value: displayText(event.name) },
        { label: 'started', value: formatAbsoluteTimestamp(event.startedAt) },
        { label: 'ended', value: formatAbsoluteTimestamp(event.endedAt) },
        { label: 'tokens', value: formatTokenCount(event.tokens) },
      ]} />
      {artifacts.length > 0 ? (
        <div className="flex flex-wrap gap-2">
          {artifacts.map((path) => <ArtifactLink key={path} path={path} label={path} />)}
        </div>
      ) : null}
      {event.payloadJson ? (
        <pre className="max-h-96 overflow-auto whitespace-pre-wrap break-words rounded border border-border bg-bg p-3 font-mono text-xs text-fg">{formattedPayload(event.payloadJson)}</pre>
      ) : <p className="text-xs text-fg-subtle">This event carries no payload.</p>}
    </div>
  )
}

function PhaseEventGroup(props: {
  group: PhaseGroup
  runStartedAt: string | null
  defaultExpanded: boolean
  onSelect(event: FactoryEventView): void
}): JSX.Element {
  const [expanded, setExpanded] = useState(props.defaultExpanded)
  const { group } = props
  const first = group.events[0]

  const columns: DataTableColumn<FactoryEventView>[] = [
    { id: 'time', header: 'Time', sortable: true, sortValue: (event) => event.startedAt ?? event.endedAt ?? '', minWidth: 110, cell: (event) => <span className="tabular-nums">{formatAbsoluteTimestamp(event.startedAt ?? event.endedAt)}</span> },
    { id: 'offset', header: 'Run offset', sortable: true, sortValue: (event) => parseTimestampMs(event.startedAt ?? event.endedAt) ?? 0, minWidth: 100, cell: (event) => <span className="tabular-nums">{formatOffset(event, props.runStartedAt)}</span> },
    { id: 'type', header: 'Type', sortable: true, sortValue: (event) => event.type ?? '', minWidth: 140, cell: (event) => displayText(event.type, 'event') },
    {
      id: 'summary',
      header: 'Summary',
      searchValue: (event) => `${event.type ?? ''} ${event.name ?? ''} ${eventSummary(event)}`,
      minWidth: 320,
      cell: (event) => (
        <Button size="sm" variant="ghost" tone="neutral" onClick={() => props.onSelect(event)}>
          <span className="flex items-center gap-1.5 text-left">
            <span aria-hidden="true" className="text-fg-subtle">▸</span>
            <span className="line-clamp-2">{eventSummary(event)}</span>
          </span>
        </Button>
      ),
    },
  ]

  return (
    <SectionCard
      title={group.label}
      titleBadge={
        <span className="flex flex-wrap items-center gap-2 text-[11px] normal-case tracking-normal text-fg-subtle">
          <StatusChip status={group.phase?.status === 'failed' ? 'failed' : group.phase?.status === 'running' ? 'running' : 'completed'} label={displayText(group.phase?.status, 'recorded')} />
          <span>{group.events.length} events</span>
          <span>by {displayText(group.phase?.owner, 'unattributed')}</span>
          <span className="tabular-nums">starts {formatAbsoluteTimestamp(group.phase?.startedAt ?? first?.startedAt ?? null)}</span>
          <span className="tabular-nums">takes {groupDuration(group)}</span>
        </span>
      }
      collapsible={{ expanded, onToggle: () => setExpanded(!expanded) }}
    >
      {expanded ? (
        <DataTable
          caption={`${group.label} events`}
          columns={columns}
          rows={group.events}
          getRowId={eventKey}
          capabilities={EVENT_CAPABILITIES}
        />
      ) : null}
    </SectionCard>
  )
}

export function FactoryEventTable(props: { events: FactoryEventView[]; runStartedAt: string | null; phases?: FactoryPhaseView[] }): JSX.Element {
  const [selected, setSelected] = useState<FactoryEventView | null>(null)
  const titleId = useId()
  const phasesById = new Map((props.phases ?? []).map((phase) => [phase.phaseId, phase]))
  const groups = groupByPhase(chronological(props.events), phasesById)

  return (
    <div className="flex flex-col gap-2" data-testid="factory-event-groups">
      {groups.map((group, index) => (
        <PhaseEventGroup
          key={group.key}
          group={group}
          runStartedAt={props.runStartedAt}
          defaultExpanded={index === groups.length - 1}
          onSelect={setSelected}
        />
      ))}
      {selected ? (
        <DetailDrawer
          eyebrow={displayText(phasesById.get(selected.phaseId ?? '')?.name ?? selected.phaseId, 'run level')}
          title={displayText(selected.type, 'event')}
          titleId={titleId}
          onClose={() => setSelected(null)}
        >
          <EventDetails event={selected} />
        </DetailDrawer>
      ) : null}
    </div>
  )
}
