import {
  CiCoverageBanner,
  HostLogsModal,
  MachineCard,
  MachineDetailModal,
  MachineSummaryPopover,
  type FleetHost,
  type OffloadAction,
  type OffloadControlModel,
} from '@overdeck/deck-ui'
import { useDeckToast } from '../../lib/use-deck-toast'
import { useQueryClient } from '@tanstack/react-query'
import { useCallback, useEffect, useMemo, useState, type ReactNode } from 'react'
import { postCollectorAction } from '../../lib/action-client'
import { fetchHostLogs } from '../../lib/collector-client'
import type { Panel } from '../../lib/collector-types'
import { ActionConfirmDialog } from '../inbox/ActionConfirmDialog'
import {
  clusterQueueFromPanel,
  fleetFromPanel,
  fleetRegistryError,
  machineCardMeta,
  machineModalActions,
  offloadControlFromPanel,
} from '../ci/ci-mappers'

const SUPPORTED_OFFLOAD_VERBS = new Set(['box-drain', 'box-restore', 'host-quarantine', 'host-unquarantine', 'admission-reconcile', 'ci-reconcile', 'recall-spill'])
const REVERSIBLE_OFFLOAD_VERBS = new Set(['box-drain', 'box-restore'])

export function buildOffloadActionArgs(verb: string, revision: number, host: FleetHost | null): Record<string, string> | null {
  if (!SUPPORTED_OFFLOAD_VERBS.has(verb) || (host !== null && host.registryState !== 'reachable')) return null
  const expectedRevision = String(revision)
  switch (verb) {
    case 'box-drain': case 'box-restore': case 'ci-reconcile': case 'recall-spill':
      return host ? { host: host.host, expectedRevision } : null
    case 'host-quarantine': case 'host-unquarantine':
      return host?.capability.missingCommand ? { host: host.host, command: host.capability.missingCommand, expectedRevision } : null
    case 'admission-reconcile': return { expectedRevision }
    default: return null
  }
}

function offloadActionNeedsHost(verb: string) { return verb !== 'admission-reconcile' }
interface PendingOffloadConfirm { action: OffloadAction; host: FleetHost | null }
export interface MachinesWidgetRenderProps {
  control: OffloadControlModel | undefined
  selectedMachine: FleetHost | null
  onAction(action: OffloadAction, host: FleetHost | null): void
  pendingVerb: string | null
  actionError: { verb: string; message: string } | null
}

/** Shared live buildbox fleet surface used by CI and Cluster. */
export function MachinesWidget({ panels, children }: { panels: Panel[]; children?: (props: MachinesWidgetRenderProps) => ReactNode }) {
  const queryClient = useQueryClient()
  const { toast } = useDeckToast()
  const control = useMemo(() => offloadControlFromPanel(panels), [panels])
  const queue = useMemo(() => clusterQueueFromPanel(panels), [panels])
  const fleet = useMemo(() => fleetFromPanel(panels), [panels])
  const registryError = useMemo(() => fleetRegistryError(panels), [panels])
  const fleetRecorded = panels.some((panel) => panel.id === 'fleet') && registryError === null
  const [selectedHost, setSelectedHost] = useState<string | null>(null)
  const [modalOpen, setModalOpen] = useState(false)
  const [popover, setPopover] = useState<{ host: string; x: number; y: number } | null>(null)
  const [pendingConfirm, setPendingConfirm] = useState<PendingOffloadConfirm | null>(null)
  const [pendingVerb, setPendingVerb] = useState<string | null>(null)
  const [actionError, setActionError] = useState<{ verb: string; message: string } | null>(null)
  const [logsModalHost, setLogsModalHost] = useState<string | null>(null)
  const [logsLines, setLogsLines] = useState<string[]>([])
  const [logsLoading, setLogsLoading] = useState(false)
  const [logsError, setLogsError] = useState<string | null>(null)
  const selectedMachine = fleet.find((host) => host.host === selectedHost) ?? null
  const hoverMachine = fleet.find((host) => host.host === popover?.host) ?? null
  const buildersUnobserved = fleet.length > 0 && fleet.every((host) => host.enrolled === null)
  const buildersOnline = fleet.filter((host) => host.role === 'builder' && !host.enrolling && host.enrolled !== false && host.enrolled !== null).length
  const joiningCount = fleet.filter((host) => host.enrolling).length

  useEffect(() => { if (selectedHost && !fleet.some((host) => host.host === selectedHost)) { setSelectedHost(null); setModalOpen(false) } }, [fleet, selectedHost])
  const loadHostLogs = useCallback(async (host: string) => {
    setLogsLoading(true); setLogsError(null); setLogsLines([])
    try { setLogsLines(await fetchHostLogs(host)) } catch (err) { setLogsError(err instanceof Error ? err.message : String(err)) } finally { setLogsLoading(false) }
  }, [])
  const onAction = useCallback((action: OffloadAction, host: FleetHost | null) => {
    if (pendingVerb) return
    if (action.verb === 'host-logs') { if (host) { setLogsModalHost(host.host); void loadHostLogs(host.host) }; return }
    if (SUPPORTED_OFFLOAD_VERBS.has(action.verb)) setPendingConfirm({ action, host })
  }, [loadHostLogs, pendingVerb])
  const runAction = useCallback(async () => {
    if (!pendingConfirm || !control) return
    const { action, host } = pendingConfirm
    const args = buildOffloadActionArgs(action.verb, control.revision, host)
    if (!args) { setPendingConfirm(null); return }
    setPendingVerb(action.verb)
    try {
      const response = await postCollectorAction(action.verb, { args, requestedBy: 'overdeck-web' })
      setActionError(null)
      toast({ title: `${action.label} succeeded`, description: typeof response.result === 'string' ? response.result : undefined, tone: 'success' })
      await queryClient.invalidateQueries({ queryKey: ['collector-state'] })
    } catch (err) {
      const message = err instanceof Error ? err.message : String(err)
      setActionError({ verb: action.verb, message })
      toast({ title: `${action.label} failed`, description: message, tone: 'danger' })
    } finally { setPendingVerb(null); setPendingConfirm(null) }
  }, [control, pendingConfirm, queryClient, toast])

  return <>
    {registryError ? <CiCoverageBanner gaps={[{ id: 'buildbox-registry-unreadable', headline: 'Buildbox registry unreadable — host operations are blocked', detail: 'Host identity comes only from the registry, so no partial fleet is shown.', action: `${registryError} — the collector did not query the build controller; no host is listed and no host action is offered.` }]} /> : null}
    {fleetRecorded ? <div data-testid="machines-widget">
      <div className="mb-1 flex items-baseline justify-between px-0.5"><div className="text-[12px] font-semibold text-fg">Machines <span className="font-normal text-fg-subtle">· {buildersUnobserved ? 'builders online unknown — the build controller did not answer' : `${buildersOnline} builder${buildersOnline === 1 ? '' : 's'} online`}{joiningCount > 0 ? `, ${joiningCount} joining` : ''} · one global queue, dispatched by free capacity · click a card for full detail</span></div></div>
      <div className="grid grid-cols-[repeat(auto-fill,minmax(248px,1fr))] gap-3" data-testid="ci-build-fleet">{fleet.map((machine) => { const meta = machineCardMeta(machine, queue, control); return <MachineCard key={machine.host} machine={machine} statusPill={meta.statusPill} statusPillIntent={meta.statusPillIntent} warn={meta.warn} note={meta.note} actions={meta.actions} onClick={() => { setSelectedHost(machine.host); setModalOpen(true); setPopover(null) }} onMouseEnter={() => setPopover((current) => ({ host: machine.host, x: current?.x ?? 0, y: current?.y ?? 0 }))} onMouseMove={(event) => setPopover({ host: machine.host, x: Math.min(event.clientX + 16, window.innerWidth - 230), y: event.clientY + 16 })} onMouseLeave={() => setPopover((current) => current?.host === machine.host ? null : current)} /> })}</div>
    </div> : <div className="text-[12px] text-fg-muted">Fleet not recorded.</div>}
    {children?.({ control, selectedMachine, onAction, pendingVerb, actionError })}
    {hoverMachine && popover ? <MachineSummaryPopover machine={hoverMachine} x={popover.x} y={popover.y} visible /> : null}
    <MachineDetailModal open={modalOpen && selectedMachine !== null} machine={selectedMachine} note={selectedMachine ? machineCardMeta(selectedMachine, queue, control).note : undefined} actions={selectedMachine ? machineModalActions(selectedMachine) : []} onClose={() => setModalOpen(false)} onAction={(action) => onAction(action, selectedMachine)} pendingVerb={pendingVerb} actionError={actionError} />
    {pendingConfirm && control ? <ActionConfirmDialog itemTitle={pendingConfirm.host?.host ?? 'offload control'} action={{ verb: pendingConfirm.action.verb, label: pendingConfirm.action.label, args: {} }} open busy={pendingVerb !== null} host={offloadActionNeedsHost(pendingConfirm.action.verb) ? pendingConfirm.host?.host : undefined} revision={control.revision} reversible={REVERSIBLE_OFFLOAD_VERBS.has(pendingConfirm.action.verb)} onConfirm={() => void runAction()} onCancel={() => { if (!pendingVerb) setPendingConfirm(null) }} /> : null}
    <HostLogsModal open={logsModalHost !== null} host={logsModalHost} lines={logsLines} loading={logsLoading} error={logsError} onClose={() => { setLogsModalHost(null); setLogsLines([]); setLogsError(null) }} onRefresh={() => { if (logsModalHost) void loadHostLogs(logsModalHost) }} />
  </>
}
