'use client'

import { useCallback, useEffect, useMemo, useState } from 'react'

import { Button, Notice, Segmented, Select } from '@/components/ui/controls'
import {
  Badge,
  EmptyState,
  Meter,
  Panel,
  PanelBody,
  PanelHeader,
  StatCard,
} from '@/components/ui/display'
import { useI18n } from '@/components/providers/i18n-provider'
import { api } from '@/lib/api/client'
import { cn } from '@/lib/cn'

/**
 * The sensor inspector and provenance graph (5.0 §4–§5).
 *
 * Two questions, one screen. *What is this instrument doing?* — its readings, its age, its
 * health and every finding against it. And *what is behind this decision?* — the chain from
 * instrument to recommendation, with its confidence capped by the weakest link.
 *
 * Three rules this surface never bends:
 *
 * **A finding about the data is never a finding about the plant** (§6). Every anomaly is
 * labelled DATA ANOMALY, in those words. A frozen reading means the platform stopped
 * learning about the asset, not that the asset stopped changing, and this screen says so
 * beside every finding rather than in a footnote.
 *
 * **Estimated is never shown as measured** (§21). The origin badge comes from the payload;
 * this component has no rule that would let it upgrade one to the other.
 *
 * **The live refusal is the server's** (§7). The mode selector below can be set to LIVE and
 * the fault buttons stay enabled — because the refusal that matters is the one the server
 * performs, and showing it is more honest than hiding the button and implying a client-side
 * guard is the protection.
 */

// ─────────────────────────────────────────────────────────────────────────────
// The payload
// ─────────────────────────────────────────────────────────────────────────────

interface SensorView {
  code: string
  assetCode: string
  assetName: string
  assetNameAr: string
  kind: string
  unit: string
  accuracy: number
  status: string
  firmware: string
  samplingIntervalMin: number
  lastCalibratedAt: number | null
  lastSeenAt: number | null
  connectorCode: string | null
  connectorLabel: string | null
  protocol: string | null
  trustScore: number | null
  trustBand: string | null
}

interface Anomaly {
  kind: string
  classification: string
  severity: 'info' | 'warning' | 'critical'
  confidencePenalty: number
  message: string
  messageAr: string
}

interface SensorDetail extends SensorView {
  latestValue: number | null
  latestTs: number | null
  dataAgeMin: number
  origin: 'measured' | 'synthetic'
  quality: string
  health: {
    healthScore: number
    status: string
    anomalies: Anomaly[]
    dataAgeMin: number
    completenessPct: number
    meanValue: number | null
    stdDev: number | null
  }
  readings: Array<{ ts: number; value: number }>
  faultKind: string | null
  faultLabel: string | null
}

interface ProvenanceNode {
  stage: string
  id: string
  label: string
  labelAr: string
  confidence: number
  kind: string
  detail: string
  detailAr: string
}

interface Provenance {
  assetCode: string
  subject: string
  chain: {
    subject: string
    nodes: ProvenanceNode[]
    sensors: string[]
    overallConfidence: number
    weakestStage: string | null
    weakestConfidence: number
  }
  sensorHealth: Array<{ sensorCode: string; kind: string; healthScore: number; status: string }>
}

const FAULT_KINDS = [
  'offline',
  'frozen',
  'spike',
  'drift',
  'noise',
  'delay',
  'loss',
  'wrong_unit',
  'bad_timestamp',
  'partial_data',
] as const

type Mode = 'demo' | 'simulation' | 'live'

const WINDOWS = [15, 30, 60, 120] as const

function severityTone(severity: string): string {
  return severity === 'critical' ? 'text-critical' : severity === 'warning' ? 'text-warning' : 'text-muted'
}

function confidenceTone(value: number): string {
  return value >= 80 ? 'text-normal' : value >= 55 ? 'text-watch' : 'text-warning'
}

/**
 * The reading window, drawn.
 *
 * A plain polyline rather than a chart library: the shape of a frozen, drifting or spiking
 * stream is the whole point, and it reads at this size without axes.
 */
function ReadingTrace({ readings, unit }: { readings: Array<{ ts: number; value: number }>; unit: string }) {
  const path = useMemo(() => {
    if (readings.length < 2) return null
    const values = readings.map((reading) => reading.value)
    const min = Math.min(...values)
    const max = Math.max(...values)
    // A flat stream has no range to scale against; give it the middle of the box rather
    // than dividing by zero, so a frozen sensor draws as the flat line it is.
    const span = max - min || 1
    return readings
      .map((reading, index) => {
        const x = (index / (readings.length - 1)) * 100
        const y = 100 - ((reading.value - min) / span) * 100
        return `${index === 0 ? 'M' : 'L'}${x.toFixed(2)},${y.toFixed(2)}`
      })
      .join(' ')
  }, [readings])

  if (!path) return null

  const values = readings.map((reading) => reading.value)
  return (
    <div className="space-y-1" data-testid="sensor-trace">
      <svg viewBox="0 0 100 100" preserveAspectRatio="none" className="h-20 w-full" role="img" aria-hidden>
        <path d={path} fill="none" stroke="currentColor" strokeWidth="1.2" className="text-info" vectorEffect="non-scaling-stroke" />
      </svg>
      <div className="flex justify-between font-mono text-[10px] text-muted tabular-nums">
        <span>
          {Math.min(...values)} {unit}
        </span>
        <span>{readings.length}</span>
        <span>
          {Math.max(...values)} {unit}
        </span>
      </div>
    </div>
  )
}

// ─────────────────────────────────────────────────────────────────────────────
// The console
// ─────────────────────────────────────────────────────────────────────────────

export function SensorConsole({
  assets,
  initialAsset,
  canSimulate,
}: {
  assets: Array<{ code: string; name: string; nameAr: string }>
  initialAsset: string
  canSimulate: boolean
}) {
  const { t, locale, n, relative } = useI18n()
  const ar = locale === 'ar'

  const [asset, setAsset] = useState(initialAsset)
  const [sensors, setSensors] = useState<SensorView[]>([])
  const [selected, setSelected] = useState<string | null>(null)
  const [detail, setDetail] = useState<SensorDetail | null>(null)
  const [provenance, setProvenance] = useState<Provenance | null>(null)
  const [windowMin, setWindowMin] = useState<number>(30)
  const [mode, setMode] = useState<Mode>('demo')
  const [fault, setFault] = useState<string>('offline')
  const [busy, setBusy] = useState<string | null>(null)
  const [error, setError] = useState<string | null>(null)
  const [refusal, setRefusal] = useState<string | null>(null)

  const loadSensors = useCallback(async () => {
    setBusy('sensors')
    setError(null)
    try {
      const [listing, chain] = await Promise.all([
        api.get<{ sensors: SensorView[] }>(`/api/sensors?assets=${encodeURIComponent(asset)}`),
        api
          .get<Provenance>(`/api/provenance/${encodeURIComponent(asset)}?clock=demo`)
          .catch(() => null),
      ])
      setSensors(listing.sensors)
      setProvenance(chain)
      setSelected((current) =>
        current && listing.sensors.some((sensor) => sensor.code === current)
          ? current
          : (listing.sensors[0]?.code ?? null),
      )
    } catch (caught) {
      setError(caught instanceof Error ? caught.message : 'Request failed')
    } finally {
      setBusy(null)
    }
  }, [asset])

  const loadDetail = useCallback(async () => {
    if (!selected) {
      setDetail(null)
      return
    }
    setBusy('detail')
    setError(null)
    try {
      setDetail(
        await api.get<SensorDetail>(
          `/api/sensors/${encodeURIComponent(selected)}?clock=demo&window=${windowMin}`,
        ),
      )
    } catch (caught) {
      setError(caught instanceof Error ? caught.message : 'Request failed')
    } finally {
      setBusy(null)
    }
  }, [selected, windowMin])

  useEffect(() => {
    void loadSensors()
  }, [loadSensors])

  useEffect(() => {
    void loadDetail()
  }, [loadDetail])

  /** Inject or clear a fault, then re-read the instrument so the effect is visible. */
  const applyFault = useCallback(
    async (kind: string | null) => {
      if (!selected) return
      setBusy('fault')
      setError(null)
      setRefusal(null)
      try {
        await api.post(`/api/sensors/${encodeURIComponent(selected)}/fault`, {
          faultKind: kind,
          mode,
        })
        await loadDetail()
        await loadSensors()
      } catch (caught) {
        // A refusal in LIVE is the designed outcome, not a failure of the screen. It is
        // reported as the server's decision rather than as a broken request.
        const message = caught instanceof Error ? caught.message : 'Request failed'
        if (mode === 'live') setRefusal(message)
        else setError(message)
      } finally {
        setBusy(null)
      }
    },
    [selected, mode, loadDetail, loadSensors],
  )

  const health = detail?.health ?? null

  return (
    <div className="space-y-4" data-testid="sensor-console">
      {/* ── Instrument picker ────────────────────────────────────────────── */}
      <Panel>
        <PanelHeader title={t('sensor.inspector')} subtitle={t('sensor.dataAnomalyNote')} />
        <PanelBody className="space-y-3">
          <div className="flex flex-wrap items-center gap-2">
            <Select value={asset} aria-label={t('sensor.asset')} onChange={(event) => setAsset(event.target.value)}>
              {assets.map((entry) => (
                <option key={entry.code} value={entry.code}>
                  {entry.code} — {ar ? entry.nameAr : entry.name}
                </option>
              ))}
            </Select>
            <Segmented
              value={String(windowMin)}
              onChange={(value) => setWindowMin(Number(value))}
              ariaLabel={t('sensor.readings')}
              size="sm"
              options={WINDOWS.map((minutes) => ({ value: String(minutes), label: `${minutes}m` }))}
            />
          </div>

          {error ? <Notice tone="danger">{error}</Notice> : null}

          {sensors.length === 0 ? (
            <EmptyState title={t('sensor.title')} body={t('sensor.empty')} />
          ) : (
            <div className="flex flex-wrap gap-1" data-testid="sensor-list">
              {sensors.map((sensor) => (
                <button
                  key={sensor.code}
                  type="button"
                  data-sensor={sensor.code}
                  onClick={() => setSelected(sensor.code)}
                  className={cn(
                    'rounded-lg border px-2.5 py-1.5 text-start text-[11px] transition-colors',
                    sensor.code === selected
                      ? 'border-brand bg-brand/10 text-fg'
                      : 'border-border bg-surface-2 text-muted hover:text-fg',
                  )}
                >
                  <span className="block font-medium">{t(`sensor.kindValue.${sensor.kind}`)}</span>
                  <span className="block font-mono text-[10px] opacity-70">{sensor.code}</span>
                </button>
              ))}
            </div>
          )}
        </PanelBody>
      </Panel>

      <div className="grid gap-4 xl:grid-cols-[1.15fr_1fr]">
        {/* ── The instrument ─────────────────────────────────────────────── */}
        <Panel>
          <PanelHeader
            title={detail ? `${t(`sensor.kindValue.${detail.kind}`)} — ${detail.code}` : t('sensor.title')}
            subtitle={detail ? `${detail.assetCode} · ${ar ? detail.assetNameAr : detail.assetName}` : undefined}
            action={
              detail ? (
                <span data-testid="sensor-origin">
                  <Badge tone={detail.origin === 'measured' ? 'info' : 'muted'}>
                    {detail.origin === 'measured' ? t('sensor.originMeasured') : t('sensor.originSynthetic')}
                  </Badge>
                </span>
              ) : null
            }
          />
          <PanelBody className="space-y-3">
            {!detail ? (
              <EmptyState title={t('sensor.title')} body={t('sensor.empty')} />
            ) : (
              <>
                <div className="grid grid-cols-2 gap-2 sm:grid-cols-4" data-testid="sensor-readings">
                  <StatCard
                    label={t('sensor.lastReading')}
                    value={detail.latestValue === null ? '—' : n(detail.latestValue, { maximumFractionDigits: 2 })}
                    unit={detail.unit}
                  />
                  <StatCard
                    label={t('sensor.dataAge')}
                    value={`${n(detail.dataAgeMin, { maximumFractionDigits: 1 })} min`}
                  />
                  <StatCard label={t('sensor.health')} value={n(detail.health.healthScore, { maximumFractionDigits: 1 })} />
                  <StatCard
                    label={t('sensor.quality')}
                    value={`${n(detail.health.completenessPct, { maximumFractionDigits: 0 })} %`}
                  />
                </div>

                <ReadingTrace readings={detail.readings} unit={detail.unit} />

                <div className="grid gap-x-4 gap-y-1 text-[11px] sm:grid-cols-2">
                  <div className="flex justify-between gap-2">
                    <span className="text-muted">{t('sensor.status')}</span>
                    <span className="font-medium text-fg">{t(`sensor.statusValue.${detail.health.status}`)}</span>
                  </div>
                  <div className="flex justify-between gap-2">
                    <span className="text-muted">{t('sensor.connector')}</span>
                    <span className="font-medium text-fg">{detail.connectorLabel ?? '—'}</span>
                  </div>
                  <div className="flex justify-between gap-2">
                    <span className="text-muted">{t('sensor.protocol')}</span>
                    <span className="font-mono text-fg">{detail.protocol ?? '—'}</span>
                  </div>
                  <div className="flex justify-between gap-2">
                    <span className="text-muted">{t('sensor.sampling')}</span>
                    <span className="text-fg">{n(detail.samplingIntervalMin, { maximumFractionDigits: 0 })} min</span>
                  </div>
                  <div className="flex justify-between gap-2">
                    <span className="text-muted">{t('sensor.accuracy')}</span>
                    <span className="text-fg">±{n(detail.accuracy, { maximumFractionDigits: 2 })}</span>
                  </div>
                  <div className="flex justify-between gap-2">
                    <span className="text-muted">{t('sensor.firmware')}</span>
                    <span className="font-mono text-fg">{detail.firmware}</span>
                  </div>
                  <div className="flex justify-between gap-2">
                    <span className="text-muted">{t('sensor.calibration')}</span>
                    <span className="text-fg">
                      {detail.lastCalibratedAt ? relative(detail.lastCalibratedAt) : t('sensor.neverCalibrated')}
                    </span>
                  </div>
                  <div className="flex justify-between gap-2">
                    <span className="text-muted">{t('sensor.trust')}</span>
                    <span className="text-fg">
                      {detail.trustScore === null ? '—' : n(detail.trustScore, { maximumFractionDigits: 1 })}
                    </span>
                  </div>
                </div>

                {/* ── Findings ─────────────────────────────────────────── */}
                <div className="space-y-2" data-testid="sensor-anomalies">
                  <div className="flex items-center gap-2">
                    <h3 className="text-[13px] font-semibold text-fg">{t('sensor.anomalies')}</h3>
                    {health && health.anomalies.length > 0 ? (
                      <span data-testid="data-anomaly-label">
                        <Badge tone="muted">{t('sensor.dataAnomaly')}</Badge>
                      </span>
                    ) : null}
                  </div>
                  {!health || health.anomalies.length === 0 ? (
                    <p className="text-[11px] text-muted">{t('sensor.noAnomalies')}</p>
                  ) : (
                    <ul className="space-y-1.5">
                      {health.anomalies.map((anomaly, index) => (
                        <li
                          key={`${anomaly.kind}-${index}`}
                          className="rounded-[--radius-panel] border border-border bg-surface-2 p-2.5 text-[11px]"
                          data-anomaly={anomaly.kind}
                        >
                          <div className="flex items-center justify-between gap-2">
                            <span className={cn('font-medium', severityTone(anomaly.severity))}>
                              {t(`sensor.anomaly.${anomaly.kind}`)}
                            </span>
                            <span className="font-mono text-[10px] uppercase text-muted">
                              {anomaly.classification}
                            </span>
                          </div>
                          <p className="pt-1 text-muted">{ar ? anomaly.messageAr : anomaly.message}</p>
                        </li>
                      ))}
                    </ul>
                  )}
                </div>

                {/* ── Fault injection (§7) ─────────────────────────────── */}
                <div className="space-y-2 rounded-[--radius-panel] border border-border bg-surface-2 p-3">
                  <div className="flex items-center justify-between gap-2">
                    <h3 className="text-[13px] font-semibold text-fg">{t('sensor.injectFault')}</h3>
                    {detail.faultKind ? (
                      <span data-testid="fault-active">
                        <Badge tone="accent">
                          {t('sensor.faultActive')}: {t(`sensor.fault.${detail.faultKind}`)}
                        </Badge>
                      </span>
                    ) : null}
                  </div>
                  <p className="text-[11px] leading-relaxed text-muted">{t('sensor.injectNote')}</p>
                  <div className="flex flex-wrap items-center gap-2">
                    <Segmented
                      value={mode}
                      onChange={(value) => setMode(value)}
                      ariaLabel={t('sensor.mode')}
                      size="sm"
                      options={[
                        { value: 'demo' as const, label: t('mode.demo.short') },
                        { value: 'simulation' as const, label: t('mode.simulation.short') },
                        { value: 'live' as const, label: t('mode.live.short') },
                      ]}
                    />
                    <Select value={fault} aria-label={t('sensor.injectFault')} onChange={(event) => setFault(event.target.value)}>
                      {FAULT_KINDS.map((kind) => (
                        <option key={kind} value={kind}>
                          {t(`sensor.fault.${kind}`)}
                        </option>
                      ))}
                    </Select>
                    <Button
                      size="sm"
                      onClick={() => applyFault(fault)}
                      disabled={!canSimulate || busy === 'fault'}
                      data-testid="inject-fault"
                    >
                      {t('sensor.injectFault')}
                    </Button>
                    <Button
                      size="sm"
                      variant="ghost"
                      onClick={() => applyFault(null)}
                      disabled={!canSimulate || busy === 'fault' || !detail.faultKind}
                    >
                      {t('sensor.clearFault')}
                    </Button>
                  </div>
                  {refusal ? (
                    <div data-testid="fault-refused">
                      <Notice tone="warning">{t('sensor.faultRefused')}</Notice>
                    </div>
                  ) : null}
                  {!canSimulate ? <Notice tone="warning">{t('common.permissionDenied')}</Notice> : null}
                </div>
              </>
            )}
          </PanelBody>
        </Panel>

        {/* ── Provenance (§5) ────────────────────────────────────────────── */}
        <Panel>
          <PanelHeader title={t('sensor.provenance')} subtitle={t('sensor.provenanceNote')} />
          <PanelBody className="space-y-3">
            {!provenance ? (
              <EmptyState title={t('sensor.provenance')} body={t('sensor.empty')} />
            ) : (
              <div data-testid="provenance-chain">
                <div className="grid grid-cols-2 gap-2">
                  <StatCard
                    label={t('sensor.confidence')}
                    value={n(provenance.chain.overallConfidence, { maximumFractionDigits: 1 })}
                  />
                  <StatCard
                    label={t('sensor.weakestLink')}
                    value={
                      provenance.chain.weakestStage
                        ? t(`sensor.stage.${provenance.chain.weakestStage}`)
                        : '—'
                    }
                    hint={n(provenance.chain.weakestConfidence, { maximumFractionDigits: 1 })}
                  />
                </div>

                <p className="pt-3 text-[11px] text-muted">
                  {t('sensor.whichSensors')}{' '}
                  <span className="font-mono text-fg">
                    {provenance.chain.sensors.join(', ') || '—'}
                  </span>
                </p>

                <ol className="space-y-1.5 pt-3">
                  {provenance.chain.nodes.map((node, index) => {
                  // The weakest link is a node, not a stage: five sensors feed this chain
                  // and marking all of them because one is the weak one would point at the
                  // wrong instrument. Ties are genuine and both are marked.
                  const isWeakest =
                    node.stage === provenance.chain.weakestStage &&
                    Math.abs(node.confidence - provenance.chain.weakestConfidence) < 0.01
                  return (
                    <li
                      key={`${node.stage}-${node.id}-${index}`}
                      data-stage={node.stage}
                      data-weakest={isWeakest}
                      className={cn(
                        'rounded-[--radius-panel] border p-2.5 text-[11px]',
                        isWeakest ? 'border-warning/40 bg-warning/8' : 'border-border bg-surface-2',
                      )}
                    >
                      <div className="flex items-center justify-between gap-2">
                        <span className="font-medium text-fg">
                          {t(`sensor.stage.${node.stage}`)} · {ar ? node.labelAr : node.label}
                        </span>
                        <span className={cn('font-mono tabular-nums', confidenceTone(node.confidence))}>
                          {n(node.confidence, { maximumFractionDigits: 1 })}
                        </span>
                      </div>
                      <div className="flex items-center justify-between gap-2 pt-1 text-muted">
                        <span>{ar ? node.detailAr : node.detail}</span>
                        <span className="font-mono text-[10px] uppercase">{node.kind}</span>
                      </div>
                      <Meter value={node.confidence} max={100} className="mt-1.5" />
                    </li>
                  )
                })}
                </ol>
              </div>
            )}
          </PanelBody>
        </Panel>
      </div>
    </div>
  )
}
