import { useMemo, useState, type JSX } from 'react'
import {
  DataTable,
  withColumnResizing,
  withSorting,
  type DataTableColumn,
  type DataTableSort,
} from '@overdeck/deck-ui'
import {
  Button,
  DeckTooltipLayer,
  FilterInput,
  SectionCard,
  StatusChip,
  formatRelativeTime,
  useDeckTooltip,
} from '@overdeck/deck-ui'
import type { HookFireModuleRow, HookFireStatsResponse, HookInventoryItem, HookInventoryResponse } from '../../lib/collector-types'
import { useHookControls, useHookFireStats, useHookInventory, useRepairHookControls, useSetHookControl } from '../../lib/collector-queries'
import { HookControlToggle } from './HookControlToggle'
import { CollectorQueryBoundary } from '../shared/CollectorQueryBoundary'

const EM_DASH = '—'

const STATUS_TOKEN: Record<HookInventoryItem['status'], string> = {
  OK: 'ok',
  DEAD: 'failed',
  UNOBSERVED: 'unknown',
}

function StatusCell({ row }: { row: HookInventoryItem }): JSX.Element {
  const tooltip = useDeckTooltip(row.problem ?? '')
  const chip = <StatusChip status={STATUS_TOKEN[row.status]} label={row.status} />
  if (row.problem === null) return chip
  return <span {...tooltip}>{chip}</span>
}

function PathCell({ value, strong = false }: { value: string | null; strong?: boolean }): JSX.Element {
  const tooltip = useDeckTooltip(value ?? '')
  if (value === null) return <>{EM_DASH}</>
  return (
    <span {...tooltip} className={`block truncate ${strong ? 'font-medium text-fg' : 'text-fg-muted'}`}>
      {value}
    </span>
  )
}

function RuntimeCell({ row }: { row: HookInventoryItem }): JSX.Element {
  const tooltip = useDeckTooltip(
    row.runtimeResolves === false ? `${row.runtime} does not resolve to an executable` : '',
  )
  if (row.runtime === null) return <>{EM_DASH}</>
  if (row.runtimeResolves === false) {
    return (
      <span {...tooltip} className="text-danger">
        {row.runtime}
      </span>
    )
  }
  return <>{row.runtime}</>
}

function GeneratedAt({ value }: { value: string }): JSX.Element {
  const tooltip = useDeckTooltip(value)
  const epochMs = Date.parse(value)
  if (Number.isNaN(epochMs)) return <>{EM_DASH}</>
  return (
    <span {...tooltip} className="text-xs text-fg-muted">
      scanned {formatRelativeTime(epochMs)}
    </span>
  )
}

function hookColumns(): DataTableColumn<HookInventoryItem>[] {
  return [
    { id: 'status', header: 'Status', sortable: true, sortValue: (row) => row.status, minWidth: 120, cell: (row) => <StatusCell row={row} /> },
    { id: 'matcher', header: 'Matcher', sortable: true, sortValue: (row) => row.matcher ?? '', minWidth: 100, cell: (row) => row.matcher ?? EM_DASH },
    { id: 'command', header: 'Command', sortable: true, sortValue: (row) => row.command, minWidth: 240, cell: (row) => <PathCell value={row.command} strong /> },
    { id: 'target', header: 'Target', sortable: true, sortValue: (row) => row.commandPath ?? '', minWidth: 200, cell: (row) => <PathCell value={row.commandPath} /> },
    { id: 'runtime', header: 'Runtime', sortable: true, sortValue: (row) => row.runtime ?? '', minWidth: 90, cell: (row) => <RuntimeCell row={row} /> },
    { id: 'scope', header: 'Scope', sortable: true, sortValue: (row) => row.scope, minWidth: 80, cell: (row) => row.scope },
    { id: 'problem', header: 'Problem', sortable: true, sortValue: (row) => row.problem ?? '', minWidth: 200, cell: (row) => (row.problem === null ? EM_DASH : <span className="text-danger">{row.problem}</span>) },
  ]
}

function matches(hook: HookInventoryItem, filter: string): boolean {
  const needle = filter.trim().toLowerCase()
  if (needle.length === 0) return true
  return [hook.event, hook.matcher ?? '', hook.command, hook.commandPath ?? '', hook.runtime ?? '', hook.status, hook.scope, hook.source]
    .some((value) => value.toLowerCase().includes(needle))
}

export function HooksPanel({ data }: { data: HookInventoryResponse }): JSX.Element {
  const controls = useHookControls()
  const setControl = useSetHookControl()
  const repair = useRepairHookControls()
  const [controlError, setControlError] = useState<string>()
  const control = controls.data
  const issue = control?.issue
  const indeterminate = control?.persistence === 'indeterminate'
  const hasControl = control !== undefined
  const observedEnabled = hasControl ? control.controls.hooks['background-jobs-blocker'] : false
  const reconfirmPending = indeterminate && setControl.isPending
  const repairPending = repair.isPending
  const pending = !hasControl || controls.isLoading || setControl.isPending || repairPending || issue !== null && issue !== undefined
  const toggle = (enabled: boolean) => {
    setControlError(undefined)
    setControl.mutate(
      { id: 'background-jobs-blocker', enabled },
      {
        onSuccess: () => setControlError(undefined),
        onError: (error) => setControlError(error instanceof Error ? error.message : 'Unable to update hook control'),
      },
    )
  }
  const reconfirmControls = () => {
    setControlError(undefined)
    setControl.mutate(
      { id: 'background-jobs-blocker', enabled: observedEnabled },
      {
        onSuccess: () => setControlError(undefined),
        onError: (error) => setControlError(error instanceof Error ? error.message : 'Unable to reconfirm hook controls'),
      },
    )
  }
  const repairControls = () => {
    setControlError(undefined)
    repair.mutate(undefined, {
      onSuccess: () => setControlError(undefined),
      onError: (error) => setControlError(error instanceof Error ? error.message : 'Unable to repair hook controls'),
    })
  }
  const [filter, setFilter] = useState('')
  const [sort, setSort] = useState<DataTableSort | null>(null)

  const visible = useMemo(() => data.hooks.filter((hook) => matches(hook, filter)), [data.hooks, filter])
  const events = useMemo(() => [...new Set(visible.map((hook) => hook.event))], [visible])
  const columns = useMemo(() => hookColumns(), [])
  const capabilities = useMemo(() => [withSorting({ sort, onSortChange: setSort, manual: false }), withColumnResizing()], [sort])

  const dead = data.hooks.filter((hook) => hook.status === 'DEAD').length
  const unobserved = data.hooks.filter((hook) => hook.status === 'UNOBSERVED').length
  const controlAlert = controlError
    ?? ([
      issue?.detail,
      indeterminate ? control?.persistenceDetail ?? 'Hook control persistence is uncertain.' : undefined,
    ].filter(Boolean).join('; ') || undefined)
    ?? (controls.isError ? 'Unable to load hook controls.' : undefined)

  return (
    <div className="space-y-4">
      <div className="flex flex-wrap items-center justify-between gap-3">
        <FilterInput
          label="Filter hooks"
          value={filter}
          onChange={setFilter}
          placeholder="Event, command, runtime, status…"
        />
        <div className="flex flex-wrap items-center gap-3">
          <HookControlToggle
            id="background-jobs-blocker"
            label="Block agent background jobs"
            hint="Disabling permits agent-initiated background commands."
            enabled={observedEnabled}
            pending={pending || indeterminate}
            error={controlAlert}
            onEnabledChange={toggle}
          />
          {indeterminate && issue == null && (
            <Button onClick={reconfirmControls} disabled={reconfirmPending || repairPending}>Reconfirm hook controls</Button>
          )}
          {issue?.repairable && (
            <Button onClick={repairControls} disabled={repairPending || reconfirmPending}>Repair hook controls</Button>
          )}
          <span className="text-xs text-fg-muted tabular-nums">{data.hooks.length} hooks</span>
          {dead > 0 && (
            <span className="text-xs font-semibold text-danger tabular-nums" data-testid="hooks-dead-count">
              {dead} dead
            </span>
          )}
          {unobserved > 0 && (
            <span className="text-xs text-fg-muted tabular-nums" data-testid="hooks-unobserved-count">
              {unobserved} unobserved
            </span>
          )}
          <GeneratedAt value={data.generatedAt} />
        </div>
      </div>

      {data.sourceErrors.length > 0 && (
        <SectionCard title="Unreadable hook sources">
          <ul className="space-y-1 text-sm text-danger" data-testid="hooks-source-errors">
            {data.sourceErrors.map((entry) => (
              <li key={entry.source} className="truncate">
                {entry.source}: {entry.error}
              </li>
            ))}
          </ul>
        </SectionCard>
      )}

      {events.length === 0 ? (
        <SectionCard title="Hooks">
          <p className="text-sm text-fg-muted" data-testid="hooks-empty">
            {data.hooks.length === 0 ? 'No hooks configured in the scanned sources.' : 'No hooks match the filter.'}
          </p>
        </SectionCard>
      ) : (
        events.map((event) => {
          const rows = visible.filter((hook) => hook.event === event)
          const deadHere = rows.filter((hook) => hook.status === 'DEAD').length
          return (
            <SectionCard key={event} title={`${event} (${rows.length}${deadHere > 0 ? `, ${deadHere} dead` : ''})`}>
              <div data-testid={`hooks-table-${event}`}>
                <DataTable
                  caption={`${event} hooks`}
                  columns={columns}
                  rows={rows}
                  getRowId={(row) => row.id}
                  capabilities={capabilities}
                />
              </div>
            </SectionCard>
          )
        })
      )}

      <DeckTooltipLayer />
    </div>
  )
}

const FIRE_STATUS_CHIP: Record<HookFireModuleRow['status'], { token: string; label: string }> = {
  ACTIVE: { token: 'ok', label: 'ACTIVE' },
  SUSPECT: { token: 'warn', label: 'SUSPECT' },
  REMOVAL_CANDIDATE: { token: 'failed', label: 'REMOVAL CANDIDATE' },
  OBSERVING: { token: 'queued', label: 'observing' },
}

function FireStatusCell({ row, daysObserved }: { row: HookFireModuleRow; daysObserved: number }): JSX.Element {
  const chip = FIRE_STATUS_CHIP[row.status]
  const label = row.status === 'OBSERVING' ? `observing (day ${daysObserved})` : chip.label
  return <StatusChip status={chip.token} label={label} />
}

function LastFiredCell({ value }: { value: string | null }): JSX.Element {
  if (value === null) return <>{EM_DASH}</>
  const epochMs = Date.parse(value)
  if (Number.isNaN(epochMs)) return <>{EM_DASH}</>
  return <span className="text-xs text-fg-muted">{formatRelativeTime(epochMs)}</span>
}

function fireColumns(daysObserved: number): DataTableColumn<HookFireModuleRow>[] {
  return [
    { id: 'module', header: 'Module', sortable: true, sortValue: (row) => row.module, minWidth: 200, cell: (row) => <span className="font-medium text-fg">{row.module}</span> },
    { id: 'event', header: 'Event', sortable: true, sortValue: (row) => row.event, minWidth: 140, cell: (row) => row.event },
    { id: 'fires24h', header: '24h', sortable: true, sortValue: (row) => row.fires24h, minWidth: 60, cell: (row) => row.fires24h },
    { id: 'fires7d', header: '7d', sortable: true, sortValue: (row) => row.fires7d, minWidth: 60, cell: (row) => row.fires7d },
    { id: 'fires30d', header: '30d', sortable: true, sortValue: (row) => row.fires30d, minWidth: 60, cell: (row) => row.fires30d },
    { id: 'lastFired', header: 'Last fired', sortable: true, sortValue: (row) => row.lastFiredAt ?? '', minWidth: 140, cell: (row) => <LastFiredCell value={row.lastFiredAt} /> },
    { id: 'status', header: 'Status', sortable: true, sortValue: (row) => row.status, minWidth: 180, cell: (row) => <FireStatusCell row={row} daysObserved={daysObserved} /> },
  ]
}

export function HookFireStatsPanel({ data }: { data: HookFireStatsResponse }): JSX.Element {
  const [sort, setSort] = useState<DataTableSort | null>(null)
  const columns = useMemo(() => fireColumns(data.daysObserved), [data.daysObserved])
  const capabilities = useMemo(() => [withSorting({ sort, onSortChange: setSort, manual: false }), withColumnResizing()], [sort])
  const noData = data.status === 'absent' && data.modules.every((row) => row.fires30d === 0)
  const removalCandidates = data.modules.filter((row) => row.status === 'REMOVAL_CANDIDATE').length
  const suspects = data.modules.filter((row) => row.status === 'SUSPECT').length

  return (
    <SectionCard title={`Hook fires (${data.modules.length})`}>
      <div className="space-y-3">
        <div className="flex flex-wrap items-center gap-3 text-xs text-fg-muted">
          <span data-testid="hook-fires-observed">
            {data.status === 'absent' ? 'no data yet' : `observing ${data.daysObserved} day${data.daysObserved === 1 ? '' : 's'}`}
          </span>
          {removalCandidates > 0 && (
            <span className="font-semibold text-danger" data-testid="hook-fires-removal-count">
              {removalCandidates} removal candidate{removalCandidates === 1 ? '' : 's'}
            </span>
          )}
          {suspects > 0 && (
            <span className="text-warning" data-testid="hook-fires-suspect-count">
              {suspects} suspect{suspects === 1 ? '' : 's'}
            </span>
          )}
        </div>
        {data.modules.length === 0 ? (
          <p className="text-sm text-fg-muted" data-testid="hook-fires-empty">
            {noData ? 'No fire telemetry recorded yet.' : 'No hook modules found in the dispatcher manifest.'}
          </p>
        ) : (
          <div data-testid="hook-fires-table">
            <DataTable
              caption="hook fire counts"
              columns={columns}
              rows={data.modules}
              getRowId={(row) => row.module}
              capabilities={capabilities}
            />
          </div>
        )}
      </div>
    </SectionCard>
  )
}

function HookFireStatsContent(): JSX.Element {
  const query = useHookFireStats()
  return <CollectorQueryBoundary query={query}>{(data) => <HookFireStatsPanel data={data} />}</CollectorQueryBoundary>
}

export function HooksContent(): JSX.Element {
  const query = useHookInventory()
  return (
    <div className="space-y-4">
      <CollectorQueryBoundary query={query}>{(data) => <HooksPanel data={data} />}</CollectorQueryBoundary>
      <HookFireStatsContent />
    </div>
  )
}
