import { useEffect } from 'react'
import { DataTable, withColumnResizing, withSearch, withSorting, type DataTableColumn } from './internal/DataTable'
import type { FleetHost, OffloadAction } from './offload-types'
import { formatPercent, ratioPercent } from './format'
import { sessionsCell } from './MachineCard'

export interface MachineDetailModalProps {
  machine: FleetHost | null
  open: boolean
  note?: string
  actions: OffloadAction[]
  onClose: () => void
  onAction: (action: OffloadAction) => void
  pendingVerb: string | null
  actionError: { verb: string; message: string } | null
}

type DiskRow = FleetHost['disk'][number]
type GuardSlice = NonNullable<FleetHost['guard']>['workSlices'][number]
const TABLE_CAPABILITIES = [withSorting(), withSearch({ label: 'Search machine details' }), withColumnResizing()]
const GUARD_SLICE_COLUMNS: DataTableColumn<GuardSlice>[] = [
  { id: 'slice', header: 'Slice', minWidth: 128, sortable: true, sortValue: (row) => row.slice.toLowerCase(), searchValue: (row) => row.slice, cell: (row) => <span className="text-fg">{row.slice}</span> },
  { id: 'memory', header: 'Memory', minWidth: 90, sortable: true, sortValue: (row) => row.memoryBytes ?? 0, cell: (row) => <span className="tabular-nums">{row.memoryBytes === null ? '—' : `${bytesToGb(row.memoryBytes)} GB`}</span> },
  { id: 'pids', header: 'Tasks', minWidth: 70, sortable: true, sortValue: (row) => row.pidsCurrent ?? 0, cell: (row) => <span className="tabular-nums">{row.pidsCurrent ?? '—'}</span> },
  { id: 'oom', header: 'OOM kills', minWidth: 90, sortable: true, sortValue: (row) => row.oomKillTotal ?? 0, cell: (row) => <span className="block text-right tabular-nums text-fg">{row.oomKillTotal ?? '—'}</span> },
]
const DISK_COLUMNS: DataTableColumn<DiskRow>[] = [
  { id: 'mountpoint', header: 'Mountpoint', minWidth: 80, sortable: true, sortValue: (row) => row.mountpoint.toLowerCase(), searchValue: (row) => row.mountpoint, cell: (row) => <span className="text-fg">{row.mountpoint}</span> },
  { id: 'free', header: 'Free', minWidth: 90, sortable: true, sortValue: (row) => row.freeBytes, cell: (row) => <span className="tabular-nums">{Math.round(row.freeBytes / 1_000_000_000)} G free</span> },
  { id: 'meter', header: 'Usage', minWidth: 140, sortable: true, sortValue: (row) => ratioPercent(row.sizeBytes - row.freeBytes, row.sizeBytes), cell: (row) => { const pct = ratioPercent(row.sizeBytes - row.freeBytes, row.sizeBytes); return <div className="h-[5px] w-[120px] rounded-[3px] bg-surface-raised"><i className={`block h-full rounded-[3px] ${barColor(pct)}`} style={{ width: `${pct}%` }} /></div> } },
  { id: 'used', header: 'Used', minWidth: 60, sortable: true, sortValue: (row) => ratioPercent(row.sizeBytes - row.freeBytes, row.sizeBytes), cell: (row) => <span className="block text-right tabular-nums text-fg">{formatPercent(ratioPercent(row.sizeBytes - row.freeBytes, row.sizeBytes))}</span> },
]

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

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

const COMMAND_SCOPED_VERBS = new Set(['host-quarantine', 'host-unquarantine'])

function isActionDisabled(verb: string, machine: FleetHost | null, pendingVerb: string | null): boolean {
  if (pendingVerb !== null) return true
  if (!SUPPORTED_OFFLOAD_VERBS.has(verb)) return true
  if (COMMAND_SCOPED_VERBS.has(verb)) {
    return machine === null || !machine.capability.missingCommand
  }
  if (HOST_SCOPED_VERBS.has(verb)) return machine === null
  return false
}

function barColor(percent: number): string {
  if (percent >= 85) return 'bg-danger'
  if (percent >= 60) return 'bg-warning'
  return 'bg-success'
}

function bytesToGb(bytes: number): string {
  return (bytes / 1_000_000_000).toFixed(1)
}

function mbPerSecond(bytes: number): string {
  return (bytes / 1_000_000).toFixed(1)
}

/** Full machine detail modal — per-core load, RAM/swap, network, disk df, CPU temp. */
export function MachineDetailModal({
  machine,
  open,
  note,
  actions,
  onClose,
  onAction,
  pendingVerb,
  actionError,
}: MachineDetailModalProps) {
  useEffect(() => {
    if (!open) return
    const onKey = (event: KeyboardEvent) => {
      if (event.key === 'Escape') onClose()
    }
    window.addEventListener('keydown', onKey)
    return () => window.removeEventListener('keydown', onKey)
  }, [open, onClose])

  if (!open || !machine) return null

  const enrolling = machine.enrolling === true
  const crit =
    machine.temp.pkg !== null && machine.temp.crit !== null && machine.temp.pkg >= machine.temp.crit
  const tempColor = crit ? 'text-danger' : 'text-success'
  const hotCores = machine.coreLoads.filter((load) => load >= 85).length
  const sessions = sessionsCell(machine, enrolling)

  return (
    <div
      className="fixed inset-0 z-[60] flex items-center justify-center bg-[rgba(4,5,8,0.62)] p-6"
      data-testid="machine-detail-backdrop"
      onClick={(event) => {
        if (event.target === event.currentTarget) onClose()
      }}
    >
      <div
        role="dialog"
        aria-modal="true"
        aria-label="Machine detail"
        data-testid="machine-detail-modal"
        className="max-h-[88vh] w-full max-w-[700px] overflow-auto rounded-[14px] border border-border bg-surface shadow-[0_30px_80px_rgba(0,0,0,0.6)]"
      >
        <div className="sticky top-0 z-[1] flex items-center gap-2.5 border-b border-border bg-surface px-[18px] py-[15px]">
          <span className={`h-2 w-2 rounded-full ${enrolling ? 'bg-fg-subtle' : 'bg-warning'}`} />
          <span className="text-[16px] font-bold">{machine.host}</span>
          <span className="rounded-[5px] border border-border px-1.5 py-px text-[9.5px] uppercase tracking-[0.06em] text-fg-subtle">
            {machine.enrolling
              ? 'builder · joining'
              : machine.role === 'workstation'
                ? 'workstation'
                : machine.primary
                  ? 'builder · primary'
                  : 'builder'}
          </span>
          {note ? <span className="text-[11.5px] text-fg-muted">{note}</span> : null}
          <button
            type="button"
            aria-label="Close"
            onClick={onClose}
            className="ml-auto cursor-pointer border-0 bg-transparent px-1 text-[22px] leading-none text-fg-muted"
          >
            ×
          </button>
        </div>

        {enrolling ? (
          <div className="px-[18px] py-4 text-[12.5px] text-fg-muted">
            Capability probe pending. Telemetry begins once the host is enrolled and passes the toolchain/disk/systemd
            checks.
          </div>
        ) : (
          <div className="flex flex-col gap-[17px] px-[18px] py-4">
            <section data-testid="machine-sessions-section">
              <div className="flex justify-between text-[10.5px] font-semibold uppercase tracking-[0.09em] text-fg-subtle">
                <span>Live agent sessions</span>
                <b className="font-semibold tabular-nums text-fg">{sessions.value}</b>
              </div>
              <div className="mt-1 text-[10.5px] normal-case tracking-normal text-fg-muted">
                {sessions.cause ?? sessions.breakdown}
              </div>
            </section>
            <section>
              <div className="mb-2.5 flex justify-between text-[10.5px] font-semibold uppercase tracking-[0.09em] text-fg-subtle">
                <span>CPU · {machine.cores ?? machine.coreLoads.length} cores</span>
                <b className="font-semibold tabular-nums text-fg">
                  {hotCores} saturated · load {machine.load?.toFixed(1) ?? '—'}
                </b>
              </div>
              <div className="grid grid-cols-4 gap-x-4 gap-y-[7px]" data-testid="machine-core-grid">
                {machine.coreLoads.map((load, index) => (
                  <div key={index} className="flex items-center gap-1.5 text-[10px] tabular-nums text-fg-muted">
                    <span className="w-[22px] shrink-0 text-fg-subtle">c{index}</span>
                    <span className="h-[5px] flex-1 rounded-[3px] bg-surface-raised">
                      <i className={`block h-full rounded-[3px] ${barColor(load)}`} style={{ width: `${load}%` }} />
                    </span>
                    <span className="w-8 text-right text-fg">{load}%</span>
                  </div>
                ))}
              </div>
            </section>

            <section data-testid="machine-memory-section">
              <div className="mb-2.5 text-[10.5px] font-semibold uppercase tracking-[0.09em] text-fg-subtle">Memory</div>
              <div className="grid grid-cols-2 gap-[18px]">
                <Meter
                  label="RAM"
                  used={machine.memUsedBytes}
                  total={machine.memTotalBytes}
                  unit="GB"
                  toDisplay={bytesToGb}
                />
                <Meter
                  label="Swap"
                  used={machine.swapUsedBytes}
                  total={machine.swapTotalBytes}
                  unit="GB"
                  toDisplay={bytesToGb}
                />
              </div>
            </section>

            <section data-testid="machine-guard-section">
              <div className="mb-2.5 flex justify-between text-[10.5px] font-semibold uppercase tracking-[0.09em] text-fg-subtle">
                <span>Agent workload guard</span>
                <b className="font-semibold tabular-nums text-fg">
                  {machine.guard?.stall.full300 === null || machine.guard === undefined || machine.guard === null
                    ? '—'
                    : `stalled ${machine.guard.stall.full300.toFixed(1)}% of last 5 min`}
                </b>
              </div>
              {machine.guard ? (
                <div className="flex flex-col gap-2.5">
                  <div className="grid grid-cols-2 gap-[18px]">
                    <Meter
                      label="/tmp"
                      used={machine.guard.tmpUsedBytes}
                      total={machine.guard.tmpSizeBytes}
                      unit="GB"
                      toDisplay={bytesToGb}
                    />
                    <div className="text-[10px] uppercase tracking-[0.09em] text-fg-subtle">
                      Memory stall
                      <div className="mt-1 text-[11.5px] normal-case tracking-normal tabular-nums text-fg-muted">
                        {machine.guard.stall.some60 === null ? '—' : `some ${machine.guard.stall.some60.toFixed(1)}%`}
                        {' · '}
                        {machine.guard.stall.full60 === null ? '—' : `full ${machine.guard.stall.full60.toFixed(1)}%`}
                        <span className="text-fg-subtle"> (60s)</span>
                      </div>
                    </div>
                  </div>
                  {machine.guard.workSlices.length > 0 ? (
                    <DataTable caption="Agent slice usage" columns={GUARD_SLICE_COLUMNS} rows={machine.guard.workSlices} getRowId={(slice) => slice.slice} capabilities={TABLE_CAPABILITIES} />
                  ) : (
                    <div className="text-[11.5px] text-fg-muted">
                      No agent or build slice on this box — it runs no dispatched agent work yet.
                    </div>
                  )}
                </div>
              ) : (
                <div className="text-[11.5px] text-fg-muted">Guard facts not recorded for this host.</div>
              )}
            </section>

            <section data-testid="machine-network-section">
              <div className="mb-2.5 text-[10.5px] font-semibold uppercase tracking-[0.09em] text-fg-subtle">Network</div>
              <div className="flex gap-[22px]">
                <NetStat
                  label="MB/s up"
                  value={machine.netTxBytesPerSecond ? `↑ ${mbPerSecond(machine.netTxBytesPerSecond)}` : '—'}
                />
                <NetStat
                  label="MB/s down"
                  value={machine.netRxBytesPerSecond ? `↓ ${mbPerSecond(machine.netRxBytesPerSecond)}` : '—'}
                />
              </div>
            </section>

            <section>
              <div className="mb-2.5 flex justify-between text-[10.5px] font-semibold uppercase tracking-[0.09em] text-fg-subtle">
                <span>CPU temp (pkg / max)</span>
                {machine.temp.pkg !== null && machine.temp.max !== null && machine.temp.crit !== null ? (
                  <b className={`font-semibold tabular-nums ${tempColor}`}>
                    {machine.temp.pkg}° / {machine.temp.max}° · crit {machine.temp.crit}°
                  </b>
                ) : null}
              </div>
              {machine.temp.pkg !== null && machine.temp.crit !== null ? (
                <div className="h-[7px] rounded bg-surface-raised">
                  <i
                    className={`block h-full rounded ${crit ? 'bg-danger' : 'bg-success'}`}
                    style={{ width: `${Math.min(100, ratioPercent(machine.temp.pkg, machine.temp.crit))}%` }}
                  />
                </div>
              ) : null}
            </section>

            <section data-testid="machine-disk-section">
              <div className="mb-2.5 text-[10.5px] font-semibold uppercase tracking-[0.09em] text-fg-subtle">Disk</div>
              {crit ? (
                <div
                  data-testid="machine-crit-temp-banner"
                  className="mb-2.5 rounded-lg border border-danger bg-[var(--mod-color-nhot-bg)] px-[11px] py-2 text-[12px] font-semibold text-danger"
                >
                  CRITICAL temperature — throttling / new dispatch paused until it clears
                </div>
              ) : null}
              <DataTable caption="Disk usage" columns={DISK_COLUMNS} rows={machine.disk} getRowId={(entry) => entry.mountpoint} capabilities={TABLE_CAPABILITIES} />
            </section>
          </div>
        )}

        <div className="sticky bottom-0 border-t border-border bg-surface px-[18px] py-3.5">
          {actionError ? (
            <div role="alert" data-testid="machine-detail-action-error" className="mb-2 text-[12px] text-danger">
              {actionError.message}
            </div>
          ) : null}
          <div className="flex flex-wrap gap-2" data-testid="machine-detail-actions">
            {actions.map((action) => {
              const disabled = isActionDisabled(action.verb, machine, pendingVerb)
              const pending = pendingVerb === action.verb
              return (
                <button
                  key={action.label}
                  type="button"
                  data-action-verb={action.verb}
                  disabled={disabled}
                  aria-busy={pending || undefined}
                  onClick={() => {
                    if (!disabled) onAction(action)
                  }}
                  className={
                    action.danger
                      ? `rounded-[7px] border border-danger px-3 py-1 text-[11.5px] text-danger ${
                          disabled ? 'cursor-not-allowed opacity-50' : 'cursor-pointer'
                        }`
                      : action.primary
                        ? `rounded-[7px] border border-[var(--mod-color-accent-tint)] bg-[var(--mod-color-accent-tint)] px-3 py-1 text-[11.5px] font-semibold text-accent-fg ${
                            disabled ? 'cursor-not-allowed opacity-50' : 'cursor-pointer'
                          }`
                        : `rounded-[7px] border border-border-strong bg-surface-raised px-3 py-1 text-[11.5px] text-fg ${
                            disabled ? 'cursor-not-allowed opacity-50' : 'cursor-pointer'
                          }`
                  }
                >
                  {pending ? 'Running…' : action.label}
                </button>
              )
            })}
          </div>
        </div>
      </div>
    </div>
  )
}

function Meter({
  label,
  used,
  total,
  unit,
  toDisplay,
}: {
  label: string
  used: number | null
  total: number | null
  unit: string
  toDisplay: (n: number) => string
}) {
  if (used === null || total === null) {
    return (
      <div>
        <div className="mb-[5px] flex justify-between text-[12px] text-fg-muted">
          <span>{label}</span>
          <b className="font-medium text-fg">—</b>
        </div>
      </div>
    )
  }
  const pct = ratioPercent(used, total)
  return (
    <div>
      <div className="mb-[5px] flex justify-between text-[12px] text-fg-muted">
        <span>{label}</span>
        <b className="font-medium tabular-nums text-fg">
          {toDisplay(used)} / {toDisplay(total)} {unit} · {formatPercent(pct)}
        </b>
      </div>
      <div className="h-[7px] rounded bg-surface-raised">
        <i className={`block h-full rounded ${barColor(pct)}`} style={{ width: `${pct}%` }} />
      </div>
    </div>
  )
}

function NetStat({ label, value }: { label: string; value: string }) {
  return (
    <div>
      <div className="text-[18px] font-bold tabular-nums">{value}</div>
      <div className="text-[10.5px] uppercase tracking-[0.05em] text-fg-subtle">{label}</div>
    </div>
  )
}

/** Hover summary popover for a fleet host. */
export function MachineSummaryPopover({
  machine,
  x,
  y,
  visible,
}: {
  machine: FleetHost
  x: number
  y: number
  visible: boolean
}) {
  if (!visible || machine.enrolling) return null
  const hotCores = machine.coreLoads.filter((load) => load >= 85).length
  const memPct =
    machine.memUsedBytes !== null && machine.memTotalBytes
      ? ratioPercent(machine.memUsedBytes, machine.memTotalBytes)
      : null
  const swapPct =
    machine.swapUsedBytes !== null && machine.swapTotalBytes
      ? ratioPercent(machine.swapUsedBytes, machine.swapTotalBytes)
      : null
  const diskPct =
    machine.disk[0] && machine.disk[0].sizeBytes
      ? ratioPercent(machine.disk[0].sizeBytes - machine.disk[0].freeBytes, machine.disk[0].sizeBytes)
      : null
  const tempCrit =
    machine.temp.pkg !== null && machine.temp.crit !== null && machine.temp.pkg >= machine.temp.crit

  return (
    <div
      className="pointer-events-none fixed z-50 min-w-[190px] rounded-[9px] border border-border bg-surface-raised px-[11px] py-[9px] text-[11px] text-fg-muted shadow-[0_12px_30px_rgba(0,0,0,0.4)]"
      style={{ left: x, top: y }}
      data-testid="machine-summary-popover"
    >
      <div className="mb-[5px] text-[12px] font-bold text-fg">
        {machine.host} · load {machine.load?.toFixed(1) ?? '—'}
      </div>
      <PopoverRow label="cores hot" value={`${hotCores} / ${machine.coreLoads.length || machine.cores || 0}`} />
      {memPct !== null && swapPct !== null ? (
        <PopoverRow label="mem / swap" value={`${memPct}% / ${swapPct}%`} />
      ) : null}
      {machine.netTxBytesPerSecond !== null && machine.netRxBytesPerSecond !== null ? (
        <PopoverRow
          label="net ↑ / ↓"
          value={`${mbPerSecond(machine.netTxBytesPerSecond)} / ${mbPerSecond(machine.netRxBytesPerSecond)} MB/s`}
        />
      ) : null}
      {machine.disk[0] && diskPct !== null ? (
        <PopoverRow label={`disk ${machine.disk[0].mountpoint}`} value={`${diskPct}%`} />
      ) : null}
      {machine.temp.pkg !== null ? (
        <PopoverRow label="cpu temp" value={`${machine.temp.pkg}°C`} warn={tempCrit} />
      ) : null}
    </div>
  )
}

function PopoverRow({ label, value, warn }: { label: string; value: string; warn?: boolean }) {
  return (
    <div className="flex justify-between gap-4 py-0.5">
      <span>{label}</span>
      <b className={`font-medium tabular-nums ${warn ? 'text-danger' : 'text-fg'}`}>{value}</b>
    </div>
  )
}
