import {
  DataTable,
  withColumnResizing,
  withSearch,
  withSorting,
  type DataTableColumn,
} from '@overdeck/deck-ui'
import {
  DataCoveragePanel,
  DetailDrawer,
  KpiTile,
  KvPanel,
  LinkButton,
  safeHttpUrl,
  SectionCard,
  StatusChip,
  formatRelativeTime,
  useDeckTooltip,
} from '@overdeck/deck-ui'
import { useMemo, useState, type JSX } from 'react'
import type { BuildPlacement, ClusterNode, ClusterSnapshot, ClusterSources, ResourceValues, SessionPlacement, SourceCoverage, WorkloadPlacement } from '../../lib/cluster-types'
import { useCluster, useClusterNode, useCollectorState } from '../../lib/collector-queries'
import { MachinesWidget } from '../machines/MachinesWidget'
import { CollectorQueryBoundary } from '../shared/CollectorQueryBoundary'
import { openSessionTerminal } from '../sessions/session-terminal'

const DASH = '—'
const LOG_RANGE = '7d'
function logsHref(filters: { node?: string; workload?: string; build?: string }): string {
  const params = new URLSearchParams({ range: LOG_RANGE })
  for (const [key, value] of Object.entries(filters)) if (value) params.set(key, value)
  return `/logs?${params.toString()}`
}
const TABLE_CAPABILITIES = [withSorting({ defaultSort: { columnId: 'node', direction: 'asc' } }), withSearch({ label: 'Search cluster nodes' }), withColumnResizing()]

function valueOrDash(value: number | null, suffix = ''): string {
  return value === null ? DASH : `${value.toLocaleString(undefined, { maximumFractionDigits: 2 })}${suffix}`
}

function bytesOrDash(value: number | null): string {
  if (value === null) return DASH
  const gib = value / (1024 ** 3)
  return `${gib.toLocaleString(undefined, { maximumFractionDigits: 2 })} GiB`
}

function resourceRows(label: string, values: ResourceValues) {
  return [
    { label: `${label} CPU`, value: valueOrDash(values.cpuCores, ' cores') },
    { label: `${label} memory`, value: bytesOrDash(values.memoryBytes) },
  ]
}

function Timestamp({ value }: { value: string | undefined | null }) {
  const parsed = value ? Date.parse(value) : Number.NaN
  const tooltip = useDeckTooltip(Number.isNaN(parsed) ? '' : new Date(parsed).toISOString(), 'Observed at')
  if (Number.isNaN(parsed)) return <span className="text-fg-subtle">{DASH}</span>
  return <time dateTime={new Date(parsed).toISOString()} className="tabular-nums" {...tooltip}>{formatRelativeTime(parsed)}</time>
}

function SourceRow({ name, source }: { name: string; source: SourceCoverage }) {
  const reason = source.error
    ?? (source.status === 'partial' && source.scopes?.incomplete.length
      ? `Incomplete scopes: ${source.scopes.incomplete.join(', ')}`
      : source.status === 'absent' ? `${name} did not report coverage.` : null)
  return (
    <div id={`cluster-source-${name}`} className="flex min-w-0 flex-wrap items-center justify-between gap-2 py-1.5 text-sm">
      <span className="font-medium text-fg">{name}</span>
      <span className="flex min-w-0 flex-wrap items-center justify-end gap-2 text-fg-muted">
        <StatusChip status={source.status} label={source.status} />
        {source.observedAt ? <Timestamp value={source.observedAt} /> : null}
        {reason ? <span className="max-w-full text-right text-xs text-fg-subtle">{reason}</span> : null}
      </span>
    </div>
  )
}

function sourceGaps(sources: ClusterSources) {
  return Object.entries(sources).flatMap(([name, source]) => {
    if (source.status === 'present' && !source.error) return []
    const label = source.error
      ?? (source.scopes?.incomplete.length ? `Incomplete scopes: ${source.scopes.incomplete.join(', ')}` : `${name} source is absent`)
    return [{ id: name, label, specRef: `#cluster-source-${name}` }]
  })
}

function nodeColumns(onOpen: (name: string) => void): Array<DataTableColumn<ClusterNode>> {
  return [
    { id: 'node', header: 'Node', sortable: true, sortValue: (row) => row.name.toLowerCase(), searchValue: (row) => row.name, minWidth: 180, cell: (row) => <button type="button" onClick={() => onOpen(row.name)} className="min-h-11 text-left font-semibold text-accent focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-accent">{row.name}</button> },
    { id: 'ready', header: 'Readiness', sortable: true, sortValue: (row) => row.ready === null ? null : Number(row.ready), searchValue: (row) => row.ready === null ? 'unknown' : row.ready ? 'ready' : 'not ready', minWidth: 110, cell: (row) => row.ready === null ? DASH : <StatusChip status={row.ready ? 'healthy' : 'failed'} label={row.ready ? 'Ready' : 'Not ready'} /> },
    { id: 'cpu', header: 'CPU used', sortable: true, sortValue: (row) => row.utilization.cpuCores, minWidth: 100, cell: (row) => <span className="tabular-nums">{valueOrDash(row.utilization.cpuCores, ' cores')}</span> },
    { id: 'memory', header: 'Memory used', sortable: true, sortValue: (row) => row.utilization.memoryBytes, minWidth: 120, cell: (row) => <span className="tabular-nums">{bytesOrDash(row.utilization.memoryBytes)}</span> },
    { id: 'pressure', header: 'Pressure', sortable: true, sortValue: (row) => row.conditions.filter((condition) => condition.type.endsWith('Pressure') && condition.status === 'True').length, searchValue: (row) => row.conditions.map((condition) => `${condition.type} ${condition.reason ?? ''}`).join(' '), minWidth: 110, cell: (row) => { const count = row.conditions.filter((condition) => condition.type.endsWith('Pressure') && condition.status === 'True').length; return <StatusChip status={count > 0 ? 'warn' : 'ok'} label={count > 0 ? `${count} active` : 'None reported'} /> } },
    { id: 'workloads', header: 'Workloads', sortable: true, sortValue: (row) => row.workloads.length, minWidth: 105, cell: (row) => <span className="tabular-nums">{row.workloads.length}</span> },
    { id: 'builds', header: 'Builds', sortable: true, sortValue: (row) => row.builds.length, minWidth: 80, cell: (row) => <span className="tabular-nums">{row.builds.length}</span> },
    { id: 'sessions', header: 'Sessions', sortable: true, sortValue: (row) => row.sessions.length, minWidth: 90, cell: (row) => <span className="tabular-nums">{row.sessions.length}</span> },
  ]
}

const WORKLOAD_CAPABILITIES = [withSorting({ defaultSort: { columnId: 'workload', direction: 'asc' } }), withSearch({ label: 'Search node workloads' }), withColumnResizing()]
const WORKLOAD_COLUMNS: Array<DataTableColumn<WorkloadPlacement>> = [
  { id: 'workload', header: 'Workload', sortable: true, sortValue: (row) => row.name, searchValue: (row) => `${row.namespace} ${row.kind} ${row.name} ${row.buildKey ?? ''}`, minWidth: 220, cell: (row) => <span className="min-w-0"><a className="block truncate font-medium text-accent focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-accent" href={logsHref({ workload: row.uid })}>{row.name}</a><span className="block truncate text-xs text-fg-subtle">{row.namespace} · {row.kind}{row.historical ? ' · historical' : ''}</span></span> },
  { id: 'phase', header: 'Phase', sortable: true, sortValue: (row) => row.phase, minWidth: 100, cell: (row) => row.phase ? <StatusChip status={row.phase} label={row.phase} /> : DASH },
  { id: 'cpu', header: 'CPU used', sortable: true, sortValue: (row) => row.utilization.cpuCores, minWidth: 100, cell: (row) => <span className="tabular-nums">{valueOrDash(row.utilization.cpuCores, ' cores')}</span> },
  { id: 'memory', header: 'Memory used', sortable: true, sortValue: (row) => row.utilization.memoryBytes, minWidth: 120, cell: (row) => <span className="tabular-nums">{bytesOrDash(row.utilization.memoryBytes)}</span> },
  { id: 'build', header: 'Build', sortable: true, sortValue: (row) => row.buildKey, minWidth: 160, cell: (row) => row.buildKey ?? DASH },
]

export function sessionHref(row: SessionPlacement): string | null {
  return row.session.runId ? `/plans/${encodeURIComponent(row.session.runId)}` : null
}

function sessionStatus(state: string): string {
  if (state === 'ALIVE-WORKING' || state === 'ALIVE-IDLE' || state === 'DETACHED-ALIVE') return 'running'
  if (state === 'FINISHED') return 'completed'
  if (state === 'ORPHANED') return 'failed'
  return 'unknown'
}

function SessionActions({ row }: { row: SessionPlacement }) {
  const href = sessionHref(row)
  return (
    <span className="flex flex-wrap gap-2">
      {href ? <LinkButton href={href}>Open agent observability</LinkButton> : <span className="flex items-center gap-2"><LinkButton href="/sessions">Open session record</LinkButton><span className="text-xs text-fg-subtle">Agent observability was not recorded.</span></span>}
      <LinkButton onClick={() => openSessionTerminal(row.sessionId)}>Open session / tmux</LinkButton>
    </span>
  )
}

const SESSION_CAPABILITIES = [withSorting({ defaultSort: { columnId: 'session', direction: 'asc' } }), withSearch({ label: 'Search node sessions' }), withColumnResizing()]
const SESSION_COLUMNS: Array<DataTableColumn<SessionPlacement>> = [
  { id: 'session', header: 'Session', sortable: true, sortValue: (row) => row.session.title ?? row.sessionId, searchValue: (row) => `${row.sessionId} ${row.session.title ?? ''} ${row.session.runtime}`, minWidth: 220, cell: (row) => <span><span className="block font-medium">{row.session.title ?? row.sessionId}</span><span className="block text-xs text-fg-subtle">{row.session.runtime} · {row.sessionId}</span></span> },
  { id: 'confidence', header: 'Placement', sortable: true, sortValue: (row) => row.confidence, minWidth: 110, cell: (row) => <StatusChip status={row.confidence === 'exact' ? 'ok' : row.confidence === 'host-only' ? 'warn' : 'unknown'} label={row.confidence} /> },
  { id: 'state', header: 'State', sortable: true, sortValue: (row) => row.session.state, minWidth: 120, cell: (row) => <StatusChip status={sessionStatus(row.session.state)} label={row.session.state} /> },
  { id: 'actions', header: 'Actions', minWidth: 330, cell: (row) => <SessionActions row={row} /> },
]

const BUILD_CAPABILITIES = [withSorting({ defaultSort: { columnId: 'build', direction: 'asc' } }), withSearch({ label: 'Search node builds' }), withColumnResizing()]
const BUILD_COLUMNS: Array<DataTableColumn<BuildPlacement>> = [
  { id: 'build', header: 'Build key', sortable: true, sortValue: (row) => row.buildKey, searchValue: (row) => `${row.buildKey} ${row.sessionId ?? ''}`, minWidth: 220, cell: (row) => <span><a className="block font-medium text-accent focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-accent" href={logsHref({ build: row.buildKey })}>{row.buildKey}</a>{row.historical ? <span className="block text-xs text-fg-subtle">Historical — current utilization is not claimed</span> : null}</span> },
  { id: 'placement', header: 'Placement', sortable: true, sortValue: (row) => row.confidence, minWidth: 110, cell: (row) => <StatusChip status={row.confidence === 'exact' ? 'ok' : 'unknown'} label={row.confidence} /> },
  { id: 'workload', header: 'Workload UID', sortable: true, sortValue: (row) => row.workloadUid, minWidth: 180, cell: (row) => row.workloadUid ?? DASH },
  { id: 'session', header: 'Session', sortable: true, sortValue: (row) => row.sessionId, minWidth: 160, cell: (row) => row.sessionId ?? DASH },
]

function ExternalAction({ label, href }: { label: string; href: string }) {
  return <LinkButton onClick={() => window.open(href, '_blank', 'noopener,noreferrer')}>{label}</LinkButton>
}

function NodeDetail({ name, onClose }: { name: string; onClose(): void }) {
  const query = useClusterNode(name)
  return (
    <DetailDrawer modal size="wide" eyebrow="Cluster node" title={name} titleId={`cluster-node-${name}`} onClose={onClose}>
      <div className="mt-4 min-w-0 overflow-y-auto">
        <CollectorQueryBoundary query={query}>
          {({ node, sources }) => (
            <div className="flex min-w-0 flex-col gap-3.5">
              <SectionCard title="Activity"><LinkButton href={logsHref({ node: node.name })}>Open bounded node history in Logs</LinkButton><p className="mt-2 text-xs text-fg-subtle">Showing links to a bounded 7-day event read. Activity charts remain deferred pending owner approval of a TimeSeriesChart primitive.</p>{/* TODO(observability-phase-3): add charts only after the owner-approval primitive gate in the design spec. */}</SectionCard>
              <SectionCard title="Resources">
                <KvPanel rows={[
                  { label: 'Ready', value: node.ready === null ? DASH : node.ready ? 'Ready' : 'Not ready' },
                  ...resourceRows('Capacity', node.capacity), ...resourceRows('Allocatable', node.allocatable),
                  ...resourceRows('Requested', node.requests), ...resourceRows('Limited', node.limits), ...resourceRows('Used', node.utilization),
                  { label: 'Restarts', value: valueOrDash(node.restartCount) },
                ]} />
              </SectionCard>
              <SectionCard title="Conditions">
                {node.conditions.length ? node.conditions.map((condition) => <div key={condition.type} className="flex flex-wrap items-center justify-between gap-2 py-1 text-sm"><span>{condition.type}</span><span className="flex items-center gap-2"><StatusChip status={condition.status === 'True' ? (condition.type === 'Ready' ? 'ok' : 'warn') : 'unknown'} label={condition.status} />{condition.reason ? <span className="text-xs text-fg-subtle">{condition.reason}</span> : null}<Timestamp value={condition.observedAt} /></span></div>) : <p className="text-sm text-fg-muted">No Kubernetes conditions were recorded.</p>}
              </SectionCard>
              <SectionCard title={`Workloads · ${node.workloads.length}`}><DataTable caption={`Workloads on ${node.name}`} columns={WORKLOAD_COLUMNS} rows={node.workloads} getRowId={(row) => row.uid} capabilities={WORKLOAD_CAPABILITIES} emptyState={<span className="text-fg-muted">No workloads recorded on this node.</span>} /></SectionCard>
              <SectionCard title={`Builds · ${node.builds.length}`}><DataTable caption={`Builds placed on ${node.name}`} columns={BUILD_COLUMNS} rows={node.builds} getRowId={(row) => row.buildKey} capabilities={BUILD_CAPABILITIES} emptyState={<span className="text-fg-muted">No builds recorded on this node.</span>} /></SectionCard>
              <SectionCard title={`Sessions · ${node.sessions.length}`}><DataTable caption={`Sessions placed on ${node.name}`} columns={SESSION_COLUMNS} rows={node.sessions} getRowId={(row) => row.sessionId} capabilities={SESSION_CAPABILITIES} emptyState={<span className="text-fg-muted">No sessions recorded on this node.</span>} /></SectionCard>
              <SectionCard title="Correlation and coverage">
                {node.sessions.flatMap((session) => session.evidence.map((entry, index) => <p key={`${session.sessionId}-${index}`} className="text-xs text-fg-muted">{session.sessionId}: {entry.source}.{entry.field} = {entry.value}</p>))}
                {node.coverageGaps.map((gap, index) => <p key={`${gap.source}-${index}`} className="text-xs text-fg-subtle">{gap.source}{gap.scope ? ` (${gap.scope})` : ''}: {gap.reason}</p>)}
                {node.sessions.every((session) => session.evidence.length === 0) && node.coverageGaps.length === 0 ? <p className="text-sm text-fg-muted">No correlation evidence or node-specific coverage gaps were reported.</p> : null}
                <div className="mt-2">{Object.entries(sources).map(([source, coverage]) => <SourceRow key={source} name={source} source={coverage} />)}</div>
              </SectionCard>
            </div>
          )}
        </CollectorQueryBoundary>
      </div>
    </DetailDrawer>
  )
}

export function ClusterPanel({ snapshot }: { snapshot: ClusterSnapshot }): JSX.Element {
  const [selectedNode, setSelectedNode] = useState<string | null>(() => {
    const params = new URLSearchParams(typeof window === 'undefined' ? '' : window.location.search)
    const named = params.get('node')
    if (named && snapshot.nodes.some((node) => node.name === named)) return named
    const workload = params.get('workload')
    if (workload) return snapshot.nodes.find((node) => node.workloads.some((entry) => entry.uid === workload))?.name ?? null
    const build = params.get('build')
    if (build) return snapshot.nodes.find((node) => node.builds.some((entry) => entry.buildKey === build))?.name ?? null
    return null
  })
  const columns = useMemo(() => nodeColumns(setSelectedNode), [])
  const tiles = [
    snapshot.summary.readyNodes === null ? null : { key: 'readyNodes', value: snapshot.summary.readyNodes, label: snapshot.summary.totalNodes === null ? 'Ready nodes' : `Ready of ${snapshot.summary.totalNodes} nodes` },
    snapshot.summary.runningWorkloads === null ? null : { key: 'runningWorkloads', value: snapshot.summary.runningWorkloads, label: 'Running workloads' },
    snapshot.summary.pendingWorkloads === null ? null : { key: 'pendingWorkloads', value: snapshot.summary.pendingWorkloads, label: 'Pending workloads' },
    snapshot.summary.failedWorkloads === null ? null : { key: 'failedWorkloads', value: snapshot.summary.failedWorkloads, label: 'Failed workloads' },
  ].filter((tile): tile is { key: string; value: number; label: string } => tile !== null)
  const grafana = safeHttpUrl(snapshot.links?.grafana) ?? null
  const headlamp = safeHttpUrl(snapshot.links?.headlamp) ?? null
  return (
    <div className="flex min-w-0 flex-col gap-3.5 overflow-x-hidden" data-testid="cluster-content">
      <SectionCard title={`${snapshot.cluster.id} cluster`} titleBadge={<StatusChip status={snapshot.cluster.state} label={snapshot.cluster.state} />}>
        <div className="flex flex-wrap items-center justify-between gap-3 text-sm text-fg-muted">
          <span>Observed <Timestamp value={snapshot.observedAt} />{snapshot.cluster.version ? ` · Kubernetes ${snapshot.cluster.version}` : ''}</span>
          <span className="flex flex-wrap gap-2"><DataCoveragePanel gaps={sourceGaps(snapshot.sources)} />{grafana ? <ExternalAction label="Open in Grafana" href={grafana} /> : null}{headlamp ? <ExternalAction label="Open in Headlamp" href={headlamp} /> : null}</span>
        </div>
        {tiles.length ? <div className="mt-3 grid gap-3 sm:grid-cols-2 xl:grid-cols-4">{tiles.map((tile) => <KpiTile key={tile.key} tile={tile} />)}</div> : <p className="mt-3 text-sm text-fg-muted">Cluster totals were not measured.</p>}
      </SectionCard>
      <SectionCard title="Source coverage">{Object.entries(snapshot.sources).map(([source, coverage]) => <SourceRow key={source} name={source} source={coverage} />)}</SectionCard>
      <SectionCard title={`Nodes · ${snapshot.nodes.length}`}>
        <DataTable caption="Cluster nodes" columns={columns} rows={snapshot.nodes} getRowId={(row) => row.uid} capabilities={TABLE_CAPABILITIES} emptyState={<span className="text-fg-muted">No nodes were returned by Kubernetes.</span>} />
      </SectionCard>
      {snapshot.unplacedSessions.length || snapshot.unmatchedWorkloads.length || snapshot.unplacedBuilds.length ? <SectionCard title="Unplaced records"><p className="text-sm text-fg-muted">{snapshot.unplacedSessions.length} session(s), {snapshot.unplacedBuilds.length} build(s), and {snapshot.unmatchedWorkloads.length} workload(s) could not be placed from exact evidence.</p></SectionCard> : null}
      {selectedNode ? <NodeDetail name={selectedNode} onClose={() => setSelectedNode(null)} /> : null}
    </div>
  )
}

export function ClusterContent() {
  const query = useCluster()
  const stateQuery = useCollectorState()
  return <CollectorQueryBoundary query={query}>{(snapshot) => (
    <div className="flex min-w-0 flex-col gap-3.5 overflow-x-hidden" data-testid="cluster-page">
      <CollectorQueryBoundary query={stateQuery} skeleton={<div className="text-fg-muted">Loading Machines…</div>}>
        {(state) => <MachinesWidget panels={state.panels} />}
      </CollectorQueryBoundary>
      <ClusterPanel snapshot={snapshot} />
    </div>
  )}</CollectorQueryBoundary>
}
