import { useEffect, useId, useMemo, useState, type JSX } from 'react'
import {
  Button,
  DeckTooltipLayer,
  DetailDrawer,
  FilterInput,
  KpiTile,
  KvPanel,
  SectionCard,
  formatDurationMs,
  formatRelativeTime,
  StatusChip,
  useDeckTooltip,
} from '@overdeck/deck-ui'
import { DataTable, withColumnResizing, withSearch, withSorting, type DataTableColumn } from '@platform-modules/ui-primitives'
import {
  type ActivityEvent,
  type ActivitySeverity,
  type ActivitySkippedEntry,
  type ActivitySourceEntriesQuery,
  type ActivitySourceEntriesResponse,
} from '../../lib/activity-types'
import { useActivitySourceEntries } from '../../lib/collector-queries'
import { activityDetailRows } from './activity-detail-rows'
import { CollectorQueryBoundary } from '../shared/CollectorQueryBoundary'

type EntryRange = '1h' | '6h' | '1d' | '3d' | '7d' | '30d' | 'all'

const PAGE_SIZE = 100

const RANGE_MS: Record<Exclude<EntryRange, 'all'>, number> = {
  '1h': 60 * 60 * 1000,
  '6h': 6 * 60 * 60 * 1000,
  '1d': 24 * 60 * 60 * 1000,
  '3d': 3 * 24 * 60 * 60 * 1000,
  '7d': 7 * 24 * 60 * 60 * 1000,
  '30d': 30 * 24 * 60 * 60 * 1000,
}

const ENTRY_CAPABILITIES = [withSorting({ defaultSort: { columnId: 'time', direction: 'desc' } }), withSearch({ label: 'Search source entries' }), withColumnResizing()]
const SKIPPED_CAPABILITIES = [withSorting(), withSearch({ label: 'Search skipped records' }), withColumnResizing()]

function UnknownValue({ text }: { text: string }) {
  return <span className="text-fg-subtle">{text}</span>
}

function TruncatedCell({ value, className = 'text-fg-muted' }: { value: string; className?: string }): JSX.Element {
  const tooltipProps = useDeckTooltip(value)
  return <span {...tooltipProps} className={`block max-w-full truncate ${className}`}>{value}</span>
}

function TruncatedCodeCell({ value }: { value: string }): JSX.Element {
  const tooltipProps = useDeckTooltip(value)
  return <code {...tooltipProps} className="block max-w-full truncate text-xs text-fg-muted">{value}</code>
}

function RelativeTimeCell({ value, tooltip }: { value?: string | null; tooltip?: string }): JSX.Element {
  const timestamp = value === undefined || value === null ? Number.NaN : Date.parse(value)
  const tooltipProps = useDeckTooltip(value ?? '', tooltip)
  if (!Number.isFinite(timestamp)) return <UnknownValue text="—" />
  return (
    <time dateTime={value ?? undefined} className="tabular-nums" {...tooltipProps}>
      {formatRelativeTime(timestamp)}
    </time>
  )
}

function eventContext(event: ActivityEvent): string {
  return event.project ?? event.host ?? event.runtime ?? event.session ?? '—'
}

function skippedLocation(entry: ActivitySkippedEntry): string {
  return entry.line === undefined ? '—' : `line ${entry.line}`
}

function RangeFilters({ value, onChange }: { value: EntryRange; onChange(next: EntryRange): void }) {
  const options: EntryRange[] = ['1h', '6h', '1d', '3d', '7d', '30d', 'all']
  return (
    <div className="flex flex-wrap items-center gap-2">
      <span className="text-fg-muted text-sm">Range</span>
      {options.map((option) => {
        const active = option === value
        return (
          <Button
            key={option}
            variant={active ? 'solid' : 'outline'}
            tone={active ? 'accent' : 'neutral'}
            size="sm"
            onClick={() => onChange(option)}
          >
            {option}
          </Button>
        )
      })}
    </div>
  )
}

function SeverityFilters({ value, onChange }: { value: ActivitySeverity | undefined; onChange(next: ActivitySeverity | undefined): void }) {
  const options: Array<{ label: string; value: ActivitySeverity | undefined }> = [
    { label: 'All', value: undefined },
    { label: 'info', value: 'info' },
    { label: 'notice', value: 'notice' },
    { label: 'warn', value: 'warn' },
    { label: 'error', value: 'error' },
  ]
  return (
    <div className="flex flex-wrap items-center gap-2">
      <span className="text-fg-muted text-sm">Severity</span>
      {options.map((option) => {
        const active = option.value === value
        return (
          <Button
            key={`${option.label}-${option.value ?? 'all'}`}
            variant={active ? 'solid' : 'outline'}
            tone={active ? 'accent' : 'neutral'}
            size="sm"
            onClick={() => onChange(option.value)}
          >
            {option.label}
          </Button>
        )
      })}
    </div>
  )
}

interface SourceEntriesBodyProps {
  data: ActivitySourceEntriesResponse
  sourceId: string
  range: EntryRange
  severityFloor: ActivitySeverity | undefined
  searchText: string
  offset: number
  selectedId: string | null
  onRange(next: EntryRange): void
  onSeverity(next: ActivitySeverity | undefined): void
  onSearch(next: string): void
  onOffset(next: number): void
  onSelect(id: string | null): void
}

export function SourceEntriesBody(props: SourceEntriesBodyProps) {
  const { data } = props
  const titleId = useId()
  const [selectedId, setSelectedId] = useState(props.selectedId)
  const selected = data.events.find((event) => event.id === selectedId) ?? null
  const pageStart = data.total === 0 ? 0 : data.offset + 1
  const pageEnd = data.offset + data.events.length
  const hasPrevious = data.offset > 0
  const hasNext = pageEnd < data.total
  const entryColumns: DataTableColumn<ActivityEvent>[] = [
    { id: 'time', header: 'Time', minWidth: 120, resizable: true, sortable: true, sortValue: (row) => Date.parse(row.ts), cell: (row) => <span data-entry-row={row.id}><RelativeTimeCell value={row.ts} tooltip="Entry time" /></span> },
    { id: 'severity', header: 'Severity', minWidth: 100, resizable: true, sortable: true, sortValue: (row) => row.severity.toLowerCase(), searchValue: (row) => row.severity, cell: (row) => <StatusChip status={row.severity} /> },
    { id: 'actor', header: 'Actor', minWidth: 100, resizable: true, sortable: true, sortValue: (row) => row.actor.toLowerCase(), searchValue: (row) => row.actor, cell: (row) => <TruncatedCell value={row.actor} className="text-fg" /> },
    { id: 'account', header: 'Account', minWidth: 100, resizable: true, sortable: true, sortValue: (row) => row.account?.toLowerCase() ?? '', searchValue: (row) => row.account ?? '', cell: (row) => row.account === undefined ? <UnknownValue text="—" /> : <TruncatedCell value={row.account} className="text-fg" /> },
    { id: 'context', header: 'Context', minWidth: 140, resizable: true, sortable: true, sortValue: (row) => eventContext(row).toLowerCase(), searchValue: (row) => eventContext(row), cell: (row) => <TruncatedCell value={eventContext(row)} className="text-fg" /> },
    { id: 'duration', header: 'Duration', minWidth: 100, resizable: true, sortable: true, sortValue: (row) => row.durationMs ?? null, cell: (row) => <span className="block text-right tabular-nums">{row.durationMs === undefined ? '—' : formatDurationMs(row.durationMs)}</span> },
    { id: 'title', header: 'Entry', minWidth: 220, resizable: true, sortable: true, sortValue: (row) => row.title.toLowerCase(), searchValue: (row) => row.title, cell: (row) => <TruncatedCell value={row.title} className="text-fg" /> },
    { id: 'details', header: '', minWidth: 90, resizable: true, cell: (row) => <Button size="sm" variant="ghost" tone="neutral" onClick={() => { setSelectedId(row.id); props.onSelect(row.id) }} aria-expanded={selectedId === row.id}>Details</Button> },
  ]
  const skippedColumns: DataTableColumn<ActivitySkippedEntry>[] = [
    { id: 'location', header: 'Location', minWidth: 100, resizable: true, sortable: true, sortValue: (row) => row.line ?? null, searchValue: (row) => skippedLocation(row), cell: (row) => <span className="tabular-nums">{skippedLocation(row)}</span> },
    { id: 'reason', header: 'Reason', minWidth: 150, resizable: true, sortable: true, sortValue: (row) => row.reason.toLowerCase(), searchValue: (row) => row.reason, cell: (row) => <TruncatedCell value={row.reason} className="text-fg" /> },
    { id: 'explanation', header: 'Why it was skipped', minWidth: 260, resizable: true, sortable: true, sortValue: (row) => row.explanation.toLowerCase(), searchValue: (row) => row.explanation, cell: (row) => <TruncatedCell value={row.explanation} /> },
    { id: 'excerpt', header: 'Record', minWidth: 240, resizable: true, sortable: true, sortValue: (row) => row.excerpt?.toLowerCase() ?? '', searchValue: (row) => row.excerpt ?? '', cell: (row) => row.excerpt === undefined ? <UnknownValue text="—" /> : <TruncatedCodeCell value={row.excerpt} /> },
  ]

  const kpiTiles = useMemo(() => {
    const tiles: Array<{ key: string; label: string; value: number }> = [
      { key: 'entries-in-range', label: 'Entries in range', value: data.total },
      { key: 'source-records', label: 'Records in source', value: data.source.totalRecords },
      { key: 'skipped', label: 'Skipped records (whole file)', value: data.skippedTotal },
    ]
    return tiles
  }, [data])

  return (
    <div className="flex flex-col gap-3.5" data-testid="logs-source-page">
      <SectionCard title={data.source.label}>
        <KvPanel rows={[
          { label: 'Source id', value: data.source.id },
          { label: 'Category', value: data.source.category },
          { label: 'Path', value: data.source.path },
          ...(data.source.status === 'error'
            ? [{ label: 'Error', value: data.source.error ?? 'not recorded', intent: 'err' as const }]
            : []),
        ]} />
        <div className="mt-2 flex items-center gap-2">
          <StatusChip status={data.source.status} label={data.source.status === 'absent' ? 'not recorded' : undefined} />
          <a
            href="/logs"
            className="font-semibold text-accent focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-accent"
          >
            Back to all sources
          </a>
        </div>
      </SectionCard>

      <SectionCard title="Controls">
        <div className="flex flex-col gap-2">
          <RangeFilters value={props.range} onChange={props.onRange} />
          <SeverityFilters value={props.severityFloor} onChange={props.onSeverity} />
          <FilterInput label="Search entry text and details" value={props.searchText} onChange={props.onSearch} />
        </div>
      </SectionCard>

      <div className="grid gap-2 @min-[40rem]:grid-cols-2 @min-[64rem]:grid-cols-3">
        {kpiTiles.map((tile) => (
          <KpiTile key={tile.key} tile={{ key: tile.key, label: tile.label, value: tile.value }} />
        ))}
      </div>

      <SectionCard title="Entries">
        {data.source.status === 'absent' ? (
          <p className="text-sm text-fg-subtle">This source is not recorded on this machine — nothing to read at {data.source.path}.</p>
        ) : data.source.status === 'error' ? (
          <p className="text-sm text-fg-subtle">{data.source.error ?? 'This source could not be read.'}</p>
        ) : data.events.length === 0 ? (
          <p className="text-sm text-fg-muted">No entries matched these filters in this range.</p>
        ) : (
          <>
            <p className="mb-2 text-sm text-fg-muted tabular-nums">
              Showing {pageStart}–{pageEnd} of {data.total}
            </p>
            <DataTable caption={`${data.source.label} entries`} columns={entryColumns} rows={data.events} getRowId={(row) => row.id} capabilities={ENTRY_CAPABILITIES} />
            <div className="mt-2 flex items-center gap-2">
              <Button
                size="sm"
                variant="outline"
                tone="neutral"
                disabled={!hasPrevious}
                onClick={() => props.onOffset(Math.max(0, data.offset - data.limit))}
              >
                Newer
              </Button>
              <Button
                size="sm"
                variant="outline"
                tone="neutral"
                disabled={!hasNext}
                onClick={() => props.onOffset(data.offset + data.limit)}
              >
                Older
              </Button>
            </div>
          </>
        )}
      </SectionCard>

      <SectionCard title="Skipped records">
        <p className="mb-2 text-xs text-fg-subtle">
          Records this reader could not turn into entries, listed with the reason so nothing is dropped silently. A
          skipped record usually has no usable timestamp, so this list covers the whole file and ignores the range filter
          above.
        </p>
        {data.skippedTotal === 0 ? (
          <p className="text-sm text-fg-muted">No records were skipped in this source.</p>
        ) : (
          <>
            {data.skippedTruncated ? (
              <p className="mb-2 text-sm text-fg-muted tabular-nums">
                Showing the first {data.skipped.length} of {data.skippedTotal} skipped records.
              </p>
            ) : null}
            <DataTable caption={`${data.source.label} skipped records`} columns={skippedColumns} rows={data.skipped} getRowId={(row) => row.id} capabilities={SKIPPED_CAPABILITIES} />
          </>
        )}
      </SectionCard>

      {selected ? <DetailDrawer eyebrow={`${data.source.label} entry`} title={selected.title} titleId={titleId} onClose={() => { setSelectedId(null); props.onSelect(null) }}><div className="border-t border-border bg-surface px-2 py-2"><KvPanel rows={activityDetailRows(selected)} /></div></DetailDrawer> : null}

      <DeckTooltipLayer />
    </div>
  )
}

function SourceEntriesSkeleton(): JSX.Element {
  return <SectionCard title="Source">Loading source entries…</SectionCard>
}

export function sourceEntriesInitialState(search: string): { range: EntryRange; recordId: string | null } {
  const recordId = new URLSearchParams(search).get('event')
  return { range: recordId ? 'all' : '7d', recordId }
}

export function SourceEntriesContent({ sourceId }: { sourceId: string }) {
  const initial = useMemo(() => sourceEntriesInitialState(window.location.search), [])
  const initialRecordId = initial.recordId
  const [range, setRange] = useState<EntryRange>(initial.range)
  const [severityFloor, setSeverityFloor] = useState<ActivitySeverity | undefined>(undefined)
  const [searchText, setSearchText] = useState('')
  const [debouncedSearch, setDebouncedSearch] = useState('')
  const [offset, setOffset] = useState(0)
  const [recordId, setRecordId] = useState<string | null>(initialRecordId)
  const [selectedId, setSelectedId] = useState<string | null>(initialRecordId)
  const [now, setNow] = useState(() => Date.now())

  const clearRecord = () => {
    setRecordId(null)
    setSelectedId(null)
    const params = new URLSearchParams(window.location.search)
    params.delete('event')
    window.history.replaceState(null, '', `${window.location.pathname}?${params.toString()}`)
  }

  useEffect(() => {
    const timer = window.setInterval(() => setNow(Date.now()), 15_000)
    return () => window.clearInterval(timer)
  }, [])

  useEffect(() => {
    const timer = window.setTimeout(() => setDebouncedSearch(searchText.trim()), 250)
    return () => window.clearTimeout(timer)
  }, [searchText])

  useEffect(() => {
    setOffset(0)
  }, [range, severityFloor, debouncedSearch])

  const from = useMemo(
    () => (range === 'all' ? undefined : new Date(now - RANGE_MS[range]).toISOString()),
    [now, range],
  )

  const query = useMemo<ActivitySourceEntriesQuery>(() => ({
    sourceId,
    limit: PAGE_SIZE,
    offset,
    ...(from === undefined ? {} : { from }),
    ...(recordId === null ? {} : { recordId }),
    ...(severityFloor === undefined ? {} : { severity: severityFloor }),
    ...(debouncedSearch === '' ? {} : { q: debouncedSearch }),
  }), [sourceId, from, recordId, severityFloor, debouncedSearch, offset])

  const entriesQuery = useActivitySourceEntries(query)

  return (
    <CollectorQueryBoundary query={entriesQuery} skeleton={<SourceEntriesSkeleton />}>
      {(data) => (
        <SourceEntriesBody
          data={data}
          sourceId={sourceId}
          range={range}
          severityFloor={severityFloor}
          searchText={searchText}
          offset={offset}
          selectedId={selectedId}
          onRange={(value) => { clearRecord(); setRange(value) }}
          onSeverity={(value) => { clearRecord(); setSeverityFloor(value) }}
          onSearch={(value) => { clearRecord(); setSearchText(value) }}
          onOffset={(value) => { clearRecord(); setOffset(value) }}
          onSelect={(value) => { if (value === null) clearRecord(); else setSelectedId(value) }}
        />
      )}
    </CollectorQueryBoundary>
  )
}
