import {
  AgentComposer,
  AgentFeed,
  AgentMetaSidebar,
  AgentModeToggle,
  AgentStatusBar,
  DeckTooltipLayer,
  IdleTimer,
  SectionCard,
  SectionHeading,
  UndoToast,
  type AgentTurn,
  type AgentViewMode,
  type UndoToastHandle,
  useSseStream,
} from '@overdeck/deck-ui'
import { QueryClientProvider, useQuery } from '@tanstack/react-query'
import React, { useEffect, useMemo, useRef, useState } from 'react'
import { CollectorHttpError, fetchCollectorState, fetchHarnessEvents, harnessTaskStreamUrl, postCollectorAction } from '../../lib/collector-client'
import type { HarnessCapabilities, HarnessEvent } from '../../lib/harness-types'
import type {
  ForensicsPanelData,
  HarnessPlanRun,
  HarnessWaveTask,
} from '../../lib/panel-data'
import { createOverdeckQueryClient } from '../../lib/query-client'
import { CollectorRealtimeBridge } from '../overview/CollectorRealtimeBridge'

const PHASE_SUMMARY: Record<string, string> = {
  'llm-implement': 'Implementation activity recorded.',
  gate0: 'Build and test gate activity recorded.',
  'llm-review': 'Review activity recorded.',
  'llm-fixer': 'Fixer activity recorded.',
  'idle-restart': 'Restart recovery activity recorded.',
}
const MAX_STREAM_EVENTS = 1024

export function eventTurn(event: HarnessEvent): AgentTurn {
  const payload = event.payload
  const at = Date.parse(event.ts)
  const duration = payload.durationMs
  const summary = typeof payload.summary === 'string'
    ? payload.summary
    : typeof payload.text === 'string'
      ? payload.text
      : `${event.kind} recorded.`
  return {
    at: Number.isFinite(at) ? at : 0,
    durMs: typeof duration === 'number' && Number.isFinite(duration) ? duration : null,
    phase: event.kind,
    summary,
    detail: JSON.stringify(payload),
  }
}

export function appendStreamEvent(previous: HarnessEvent[], event: HarnessEvent): HarnessEvent[] {
  if (previous.some((item) => item.id === event.id)) return previous
  return [...previous, event].slice(-MAX_STREAM_EVENTS)
}

export interface AgentSnapshot {
  knownAgentIds: string[]
  live: boolean
  model: string
  task: HarnessWaveTask | null
  turns: AgentTurn[]
}

export function selectExecutingAccount(events: HarnessEvent[], taskId: string): string | null {
  for (let index = events.length - 1; index >= 0; index -= 1) {
    const event = events[index]!
    if (event.taskId !== taskId) continue
    if (event.kind !== 'dispatch.attempt' && event.kind !== 'attempt.prompt' && event.kind !== 'attempt.reply') continue
    const account = event.payload.account
    if (typeof account === 'string' && account.length > 0) return account
  }
  return null
}

export function selectSafeTaskAttempt(
  events: HarnessEvent[],
  taskId: string,
  capabilities: HarnessCapabilities | undefined,
): { attemptId: string | null; events: HarnessEvent[] } {
  if (capabilities?.attemptCorrelation !== true) return { attemptId: null, events: [] }
  const taskEvents = events.filter((event) => event.taskId === taskId)
  const started = taskEvents.filter(
    (event) => event.kind === 'attempt.started' && typeof event.attemptId === 'string' && event.attemptId.length > 0,
  )
  for (let index = started.length - 1; index >= 0; index -= 1) {
    const candidate = started[index]!
    const attemptId = candidate.attemptId
    const phase = candidate.payload.phase
    if (!attemptId || typeof phase !== 'string') continue
    const completed = taskEvents.some(
      (event) => event.kind === 'attempt.completed'
        && event.attemptId === attemptId
        && event.payload.phase === phase,
    )
    if (!completed) {
      return {
        attemptId,
        events: taskEvents.filter((event) => event.attemptId === attemptId),
      }
    }
  }
  return { attemptId: null, events: [] }
}

export function deriveAgentSnapshot(
  run: HarnessPlanRun,
  forensics: ForensicsPanelData | undefined,
  agentId: string,
): AgentSnapshot {
  const segments = forensics?.segments ?? []
  const knownAgentIds = [...new Set(segments.flatMap((segment) => segment.agentId ?? []))].sort()
  const agentSegments = segments
    .filter((segment) => segment.agentId === agentId)
    .sort((left, right) => left.t0 - right.t0)
  const taskId = agentSegments.find((segment) => segment.taskId)?.taskId
  const task = taskId
    ? run.waves.flatMap((wave) => wave.tasks).find((candidate) => candidate.id === taskId) ?? null
    : null
  const model = task?.model ?? task?.binding?.model ?? ''

  return {
    knownAgentIds,
    task,
    model,
    live: task?.status === 'running' || task?.status === 'paused',
    turns: agentSegments.map((segment) => ({
      at: segment.t0,
      durMs: segment.durMs,
      phase: segment.cat,
      summary: PHASE_SUMMARY[segment.cat] ?? `${segment.cat} activity recorded.`,
    })),
  }
}

function AgentContent({ runId, agentId }: { runId: string; agentId: string }) {
  const stateQuery = useQuery({ queryKey: ['collector-state'], queryFn: fetchCollectorState })
  const [mode, setMode] = useState<AgentViewMode>('live')
  const undoRef = useRef<UndoToastHandle>(null)
  const toastHandle = useMemo<UndoToastHandle>(
    () => ({
      toast(message) {
        undoRef.current?.toast(message)
      },
      toastUndo(message, options) {
        undoRef.current?.toastUndo(message, options)
      },
    }),
    [],
  )

  const plansPanel = stateQuery.data?.panels.find((panel) => panel.id === 'plans')
  const run = (plansPanel?.data as { runs?: HarnessPlanRun[] } | undefined)?.runs?.find(
    (candidate) => candidate.runId === runId,
  )
  const forensicsPanel = stateQuery.data?.panels.find((panel) => panel.id === `forensics:${runId}`)
  const forensics = forensicsPanel?.data as ForensicsPanelData | undefined
  const snapshot = useMemo(
    () => (run ? deriveAgentSnapshot(run, forensics, agentId) : null),
    [agentId, forensics, run],
  )
  const eventsQuery = useQuery({
    queryKey: ['harness-events', runId],
    queryFn: () => fetchHarnessEvents(runId),
    enabled: snapshot?.task != null,
  })
  const [streamEvents, setStreamEvents] = useState<HarnessEvent[]>([])
  const events = useMemo(() => {
    const byId = new Map<string, HarnessEvent>()
    for (const event of [...(eventsQuery.data?.events ?? []), ...streamEvents]) byId.set(event.id, event)
    return [...byId.values()]
  }, [eventsQuery.data?.events, streamEvents])
  const selectedAttempt = snapshot?.task
    ? selectSafeTaskAttempt(events, snapshot.task.id, eventsQuery.data?.capabilities)
    : { attemptId: null, events: [] }
  const streamUrl = snapshot?.live && snapshot.task && eventsQuery.isSuccess
    ? harnessTaskStreamUrl(runId, snapshot.task.id)
    : null
  useSseStream<HarnessEvent>({
    url: streamUrl,
    onMessage: (event) => setStreamEvents((previous) => appendStreamEvent(previous, event)),
  })

  useEffect(() => {
    if (snapshot && !snapshot.live) setMode('historical')
  }, [snapshot?.live])

  useEffect(() => {
    if (typeof window === 'undefined') return
    const requestedMode = new URLSearchParams(window.location.search).get('mode')
    if (requestedMode === 'historical') setMode('historical')
  }, [])

  if (stateQuery.isLoading) {
    return <SectionCard title="Agent activity">Loading agent data…</SectionCard>
  }
  if (stateQuery.isError) {
    return (
      <SectionCard title="Agent activity" action={{ label: 'Retry', onClick: () => void stateQuery.refetch() }}>
        Collector state unavailable.
      </SectionCard>
    )
  }
  if (!run || !snapshot) {
    return <SectionCard title="Agent activity">Run {runId} is not present in collector state.</SectionCard>
  }
  if (!snapshot.task) {
    const known = snapshot.knownAgentIds.length > 0 ? snapshot.knownAgentIds.join(', ') : 'none recorded'
    return (
      <div className="grid gap-4">
        <SectionHeading title={`Agent ${agentId}`} action={{ label: 'Back to run', href: `/plans/${encodeURIComponent(runId)}` }} />
        <SectionCard title="Agent not found">
          Agent {agentId} is not recorded for this run. Known agents: {known}.
        </SectionCard>
      </div>
    )
  }

  const task = snapshot.task
  const executingAccount = selectExecutingAccount(events, task.id)
  const liveView = snapshot.live && mode === 'live'
  const changeMode = (nextMode: AgentViewMode) => {
    if (nextMode === 'live' && !snapshot.live) return
    setMode(nextMode)
    if (typeof window !== 'undefined') {
      const url = new URL(window.location.href)
      url.searchParams.set('mode', nextMode)
      window.history.replaceState(null, '', url)
    }
  }
  const restart = () => {
    void postCollectorAction(
      'steer',
      { runId, taskId: task.id, restart: 'true' },
      'overdeck-web',
    )
      .then(() => toastHandle.toast('Restart requested.'))
      .catch((error) => toastHandle.toast(error instanceof Error ? error.message : String(error)))
  }
  const send = async (text: string): Promise<void> => {
    await postCollectorAction('steer', { runId, taskId: task.id, text }, 'overdeck-web')
  }
  const capability = (name: string) => eventsQuery.data?.capabilities[name] === true
  const activeAttemptId = task.status === 'running' || task.status === 'paused' ? selectedAttempt.attemptId : null
  const controlUnavailable = !activeAttemptId
    ? 'A single active attempt cannot be identified from current event data.'
    : 'Task control is not supported by this run.'
  const refreshAfterStale = async (error: unknown) => {
    if (error instanceof CollectorHttpError && error.status === 409) await Promise.all([stateQuery.refetch(), eventsQuery.refetch()])
    throw error
  }
  const control = activeAttemptId
    ? {
        mode: task.status === 'paused' ? 'resume' as const : 'pause' as const,
        onToggle: capability(task.status === 'paused' ? 'task.resume' : 'task.pause')
          ? () => void postCollectorAction(task.status === 'paused' ? 'harness.task.resume' : 'harness.task.pause', { runId, taskId: task.id, attemptId: activeAttemptId, requestId: crypto.randomUUID() }, 'overdeck-web').then(() => stateQuery.refetch()).catch(refreshAfterStale).catch((error) => toastHandle.toast(error instanceof Error ? error.message : String(error)))
          : undefined,
        onKill: capability('task.kill')
          ? () => void postCollectorAction('harness.task.kill', { runId, taskId: task.id, attemptId: activeAttemptId, requestId: crypto.randomUUID() }, 'overdeck-web').then(() => stateQuery.refetch()).catch(refreshAfterStale).catch((error) => toastHandle.toast(error instanceof Error ? error.message : String(error)))
          : undefined,
        unavailableReason: controlUnavailable,
      }
    : { mode: 'pause' as const, unavailableReason: controlUnavailable }

  return (
    <div className="grid min-h-0 gap-4">
      <div className="flex flex-wrap items-center justify-between gap-3">
        <SectionHeading
          title={`Agent ${agentId}`}
          subtitle={`· ${run.title}`}
          action={{ label: 'Back to run', href: `/plans/${encodeURIComponent(runId)}` }}
        />
        <AgentModeToggle mode={mode} liveAvailable={snapshot.live} onModeChange={changeMode} />
      </div>
      <AgentStatusBar
        agent={{
          agentId,
          seat: task.seat ?? 'not recorded',
          taskId: task.id,
          model: snapshot.model,
          live: liveView,
          stateLabel: task.status === 'paused' ? 'Paused' : liveView ? 'Working' : 'Ended',
        }}
        onRestart={restart}
        toastHandle={toastHandle}
        controls={control}
      />
      <div className="grid min-h-0 gap-4 xl:grid-cols-[minmax(0,1fr)_minmax(20rem,23rem)]">
        <div data-agent-left-pane className="flex min-h-[42rem] flex-col gap-0">
          <AgentFeed
            turns={selectedAttempt.events.length > 0 ? selectedAttempt.events.map(eventTurn) : snapshot.turns}
            live={liveView}
            rawLogAvailable={selectedAttempt.events.length > 0}
            events={selectedAttempt.events.map((event) => ({ id: event.id, event: event.kind, text: JSON.stringify(event.payload) }))}
          />
          {liveView && <AgentComposer onSend={send} />}
        </div>
        <div className="grid content-start gap-4">
          <SectionCard title="Watchdog">
            <IdleTimer idle={null} />
          </SectionCard>
          <AgentMetaSidebar
            seat={task.seat ?? 'not recorded'}
            model={snapshot.model}
            attempt={task.attempt}
            branch={task.branch}
            taskId={task.id}
            taskHref={`/plans/${encodeURIComponent(runId)}`}
            account={executingAccount}
          />
        </div>
      </div>
      <UndoToast handleRef={undoRef} />
      <DeckTooltipLayer />
    </div>
  )
}

export function AgentApp({ runId, agentId }: { runId: string; agentId: string }) {
  const [queryClient] = useState(createOverdeckQueryClient)
  return (
    <QueryClientProvider client={queryClient}>
      <CollectorRealtimeBridge />
      <AgentContent runId={runId} agentId={agentId} />
    </QueryClientProvider>
  )
}
