import axios from 'axios'
import { RefreshCw } from 'lucide-react'
import { memo, useCallback, useEffect, useState } from 'react'

import { getCommandJournal, getDeyeRegisters, type DeyeRegistersLive, type ModbusJournalCommandDto } from '../api/backend'

const BATT_VOLTAGE_V = 51.2
const POLL_REGISTERS_MS = 30_000
const POLL_JOURNAL_MS = 60_000

const TZ = 'Europe/Prague'

function fmtTime(iso: string): string {
  return new Date(iso).toLocaleString('cs-CZ', {
    timeZone: TZ,
    day: '2-digit',
    month: '2-digit',
    hour: '2-digit',
    minute: '2-digit',
    second: '2-digit',
  })
}

function ampsToKw(a: number | null | undefined): string {
  if (a == null || Number.isNaN(a)) return '—'
  return `${((a * BATT_VOLTAGE_V) / 1000).toFixed(2)} kW`
}

function fmtW(w: number | null | undefined): string {
  if (w == null || Number.isNaN(w)) return '—'
  return `${w} W`
}

function journalSignature(cmds: ModbusJournalCommandDto[]): string {
  return cmds
    .map(
      (c) =>
        `${c.id}:${c.status}:${c.attempt_count}:${c.value_written ?? ''}:${c.value_verified ?? ''}`,
    )
    .join('|')
}

function statusBadgeClass(status: string): string {
  const u = status.toLowerCase()
  if (u === 'verified') return 'bg-emerald-600/25 text-emerald-200 ring-1 ring-emerald-500/40'
  if (u === 'written') return 'bg-sky-600/25 text-sky-200 ring-1 ring-sky-500/40'
  if (u === 'pending' || u === 'retrying') return 'bg-slate-600/30 text-slate-300 ring-1 ring-slate-500/35'
  if (u === 'failed' || u === 'mismatch')
    return u === 'mismatch'
      ? 'bg-red-600/30 text-red-100 font-bold ring-1 ring-red-500/50'
      : 'bg-red-600/25 text-red-200 ring-1 ring-red-500/40'
  return 'bg-slate-600/30 text-slate-300 ring-1 ring-slate-500/35'
}

type LiveSectionProps = {
  live: DeyeRegistersLive | null
  liveLoading: boolean
  onRefresh: () => void
}

const LiveRegistersSection = memo(
  function LiveRegistersSection({ live, liveLoading, onRefresh }: LiveSectionProps) {
    return (
      <div className="rounded-xl border border-slate-800 bg-slate-900/50 p-4">
        <div className="mb-3 flex flex-wrap items-center justify-between gap-2">
          <h3 className="text-sm font-semibold text-slate-200">Živé registry</h3>
          <button
            type="button"
            onClick={() => onRefresh()}
            disabled={liveLoading}
            className="inline-flex items-center gap-1.5 rounded-lg border border-slate-600 bg-slate-800/80 px-3 py-1.5 text-xs font-medium text-slate-100 hover:bg-slate-700 disabled:opacity-50"
          >
            <RefreshCw className={`h-3.5 w-3.5 ${liveLoading ? 'animate-spin' : ''}`} aria-hidden />
            Refresh
          </button>
        </div>
        <div className="mt-2 grid grid-cols-1 gap-3 sm:grid-cols-2">
          <Metric label="Max nabíjení" reg={108} unitA={live?.reg108_charge_a} kwHint />
          <Metric label="Max vybíjení" reg={109} unitA={live?.reg109_discharge_a} kwHint />
          <Metric
            label="Limit control"
            reg={142}
            sub="0 = selling first, 1 = zero export"
            valueText={live?.reg142_limit_control != null ? String(live.reg142_limit_control) : undefined}
          />
          <Metric label="Energy mode" reg={141} valueText={live?.reg141_energy_mode != null ? String(live.reg141_energy_mode) : undefined} />
          <Metric
            label="Export cap"
            reg={143}
            sub="Hard limit lokality / invertoru; neforecastuje se"
            valueText={fmtW(live?.reg143_export_limit_w)}
          />
          <Metric
            label="Peak shaving switch"
            reg={178}
            sub="Bit4–5: 10 = disable při exportu, 11 = enable při IDLE/CHARGE"
            valueText={live?.reg178_peak_shaving_switch != null ? String(live.reg178_peak_shaving_switch) : undefined}
          />
          <Metric
            label="MI export cutoff (GEN)"
            reg={178}
            sub="Bits0–1: 2 = disable (cutoff OFF), 3 = enable (cutoff ON)"
            valueText={
              live
                ? `${live.reg178_mi_export_cutoff_is_on ? 'ON (enable=3)' : 'OFF (disable=2)'} · bits0–1=${live.reg178_mi_export_cutoff_bits} · reg178=${live.reg178_control_board_special_1}`
                : undefined
            }
          />
          <Metric
            label="Grid peak shaving W"
            reg={191}
            sub="EMS nezapisuje – nastavit v SolarmanApp (výkon peak shavingu v W)"
            valueText={fmtW(live?.reg191_peak_shaving_w)}
          />
        </div>
        {live?.read_at ? (
          <p className="mt-3 text-[10px] text-slate-500">Načteno: {fmtTime(live.read_at)}</p>
        ) : null}
      </div>
    )
  },
  (a, b) =>
    a.liveLoading === b.liveLoading &&
    a.live?.read_at === b.live?.read_at &&
    a.live?.reg108_charge_a === b.live?.reg108_charge_a &&
    a.live?.reg109_discharge_a === b.live?.reg109_discharge_a &&
    a.live?.reg141_energy_mode === b.live?.reg141_energy_mode &&
    a.live?.reg142_limit_control === b.live?.reg142_limit_control &&
    a.live?.reg143_export_limit_w === b.live?.reg143_export_limit_w &&
    a.live?.reg178_peak_shaving_switch === b.live?.reg178_peak_shaving_switch &&
    a.live?.reg178_control_board_special_1 === b.live?.reg178_control_board_special_1 &&
    a.live?.reg178_mi_export_cutoff_bits === b.live?.reg178_mi_export_cutoff_bits &&
    a.live?.reg178_mi_export_cutoff_is_on === b.live?.reg178_mi_export_cutoff_is_on &&
    a.live?.reg191_peak_shaving_w === b.live?.reg191_peak_shaving_w,
)

type MetricProps = {
  label: string
  reg: number
  unitA?: number | null
  kwHint?: boolean
  valueText?: string
  sub?: string
}

function Metric({ label, reg, unitA, kwHint, valueText, sub }: MetricProps) {
  const main =
    valueText ??
    (unitA != null && !Number.isNaN(unitA) ? `${unitA} A` : '—')
  const extra = kwHint ? ampsToKw(unitA ?? null) : null
  return (
    <div className="rounded-lg border border-slate-800/80 bg-slate-950/40 px-3 py-2">
      <p className="text-[10px] font-semibold uppercase tracking-wide text-slate-500">{label}</p>
      <p className="mt-0.5 font-mono text-sm text-slate-100">
        reg {reg}: {main}
        {extra && extra !== '—' ? <span className="text-slate-400"> · {extra}</span> : null}
      </p>
      {sub ? <p className="mt-0.5 text-[10px] text-slate-500">{sub}</p> : null}
    </div>
  )
}

type JournalSectionProps = {
  commands: ModbusJournalCommandDto[]
}

const JournalSection = memo(
  function JournalSection({ commands }: JournalSectionProps) {
    return (
      <div className="rounded-xl border border-slate-800 bg-slate-900/50 p-4">
        <h3 className="mb-3 text-sm font-semibold text-slate-200">Posledních 50 zápisů</h3>
        <div
          className="max-h-[50vh] overflow-x-auto overflow-y-auto md:max-h-[400px]"
          style={{
            borderRadius: 'var(--border-radius-md)',
            border: '0.5px solid var(--color-border-tertiary)',
          }}
        >
          <table className="w-full border-collapse text-left text-xs">
            <thead>
              <tr className="text-slate-500">
                <th className="py-2 pr-2 font-medium">Čas</th>
                <th className="py-2 pr-2 font-medium">Reg</th>
                <th className="py-2 pr-2 font-medium">Popis</th>
                <th className="py-2 pr-2 font-medium">Hodnota</th>
                <th className="py-2 pr-2 font-medium">Pokus</th>
                <th className="py-2 font-medium">Status</th>
              </tr>
            </thead>
            <tbody>
              {commands.length === 0 ? (
                <tr>
                  <td colSpan={6} className="py-4 text-slate-500">
                    Žádné záznamy v journalu.
                  </td>
                </tr>
              ) : (
                commands.map((c) => (
                  <tr key={c.id} className="border-t border-slate-800/80">
                    <td className="whitespace-nowrap py-1.5 pr-2 font-mono text-slate-400">
                      {fmtTime(c.created_at)}
                    </td>
                    <td className="pr-2 font-mono text-slate-300">{c.register}</td>
                    <td className="max-w-[140px] truncate pr-2 text-slate-400" title={c.register_name ?? ''}>
                      {c.register_name ?? '—'}
                    </td>
                    <td className="pr-2 font-mono tabular-nums text-slate-200">
                      {c.value_to_write}
                      {c.value_verified != null ? (
                        <span className="text-slate-500"> → {c.value_verified}</span>
                      ) : null}
                    </td>
                    <td className="pr-2 font-mono tabular-nums text-slate-300">{c.attempt_count}</td>
                    <td className="py-1.5">
                      <span
                        className={`inline-block rounded-md px-2 py-0.5 text-[10px] font-semibold uppercase ${statusBadgeClass(c.status)}`}
                      >
                        {c.status}
                      </span>
                    </td>
                  </tr>
                ))
              )}
            </tbody>
          </table>
        </div>
      </div>
    )
  },
  (a, b) => journalSignature(a.commands) === journalSignature(b.commands),
)

function ControlPanelImpl({ siteId }: { siteId: number }) {
  const [live, setLive] = useState<DeyeRegistersLive | null>(null)
  const [liveError, setLiveError] = useState<string | null>(null)
  const [liveLoading, setLiveLoading] = useState(false)

  const [commands, setCommands] = useState<ModbusJournalCommandDto[]>([])
  const [journalError, setJournalError] = useState<string | null>(null)

  const fetchRegisters = useCallback(async () => {
    setLiveLoading(true)
    setLiveError(null)
    try {
      const data = await getDeyeRegisters(siteId)
      setLive(data)
    } catch (e: unknown) {
      let msg = 'Chyba čtení registrů'
      if (axios.isAxiosError(e)) {
        const d = e.response?.data as { detail?: string } | undefined
        if (typeof d?.detail === 'string') msg = d.detail
      } else if (e instanceof Error) {
        msg = e.message
      }
      setLiveError(msg)
      setLive(null)
    } finally {
      setLiveLoading(false)
    }
  }, [siteId])

  const fetchJournal = useCallback(async () => {
    setJournalError(null)
    try {
      const res = await getCommandJournal(siteId, 50)
      setCommands(res.commands)
    } catch (e: unknown) {
      const msg = e instanceof Error ? e.message : 'Chyba načtení journalu'
      setJournalError(msg)
      setCommands([])
    }
  }, [siteId])

  useEffect(() => {
    void fetchRegisters()
  }, [fetchRegisters])

  useEffect(() => {
    void fetchJournal()
  }, [fetchJournal])

  useEffect(() => {
    const t = window.setInterval(() => void fetchRegisters(), POLL_REGISTERS_MS)
    return () => window.clearInterval(t)
  }, [fetchRegisters])

  useEffect(() => {
    const t = window.setInterval(() => void fetchJournal(), POLL_JOURNAL_MS)
    return () => window.clearInterval(t)
  }, [fetchJournal])

  const apiError = liveError ?? journalError

  return (
    <div className="space-y-4">
      {apiError ? (
        <div
          role="alert"
          className="rounded-lg border border-red-500/45 bg-red-950/50 px-4 py-3 text-sm text-red-100"
        >
          <p className="font-semibold text-red-50">Chyba API řízení / Modbus</p>
          {liveError ? (
            <p className="mt-1.5">
              <span className="text-red-200/90">GET …/control/registers: </span>
              {liveError}
            </p>
          ) : null}
          {journalError ? (
            <p className={liveError ? 'mt-2' : 'mt-1.5'}>
              <span className="text-red-200/90">Journal: </span>
              {journalError}
            </p>
          ) : null}
        </div>
      ) : null}
      <div className="grid grid-cols-1 gap-4 lg:grid-cols-2 lg:gap-6">
        <LiveRegistersSection live={live} liveLoading={liveLoading} onRefresh={fetchRegisters} />
        <JournalSection commands={commands} />
      </div>
    </div>
  )
}

export const ControlPanel = memo(ControlPanelImpl)
