import { useCallback, useEffect, useRef, useState } from 'react'
import { Link } from 'react-router-dom'

interface HeartbeatResponse {
  server_time?: string
  server_timestamp?: number
  status?: string
}

// BroadcastChannel name – only one tab sends heartbeats
const HEARTBEAT_CHANNEL = 'kiosk-heartbeat'
const HEARTBEAT_INTERVAL_MS = 30_000
const CLIENT_VERSION = '1.0.0'

// NFC keyboard-wedge readers "type" the card UID very fast, ending with Enter.
// A human never types 10 digits in under this window, so a burst of digit
// keys arriving within NFC_BURST_MS is treated as a card tap, not typing.
const NFC_BURST_MS = 300
const NFC_MIN_LENGTH = 4

type Step = 'idle' | 'pin' | 'action' | 'done'

interface StampStatus {
  stamped_in: boolean
  on_break: boolean
}

function formatTime(date: Date): string {
  return date.toLocaleTimeString('de-DE', { hour: '2-digit', minute: '2-digit', second: '2-digit' })
}

function formatDate(date: Date): string {
  return date.toLocaleDateString('de-DE', {
    weekday: 'long',
    year: 'numeric',
    month: 'long',
    day: 'numeric',
  })
}

async function sendMessageToSW(
  reg: ServiceWorkerRegistration,
  message: object
): Promise<Record<string, unknown>> {
  return new Promise((resolve, reject) => {
    const channel = new MessageChannel()
    channel.port1.onmessage = (e) => resolve(e.data as Record<string, unknown>)
    channel.port1.onmessageerror = () => reject(new Error('MessageChannel-Fehler'))
    const sw = reg.active
    if (!sw) { reject(new Error('ServiceWorker nicht aktiv')); return }
    sw.postMessage({ ...message, port: channel.port2 }, [channel.port2])
  })
}

async function kioskFetch<T>(path: string, body: object): Promise<T> {
  const res = await fetch(path, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(body),
  })
  const data = await res.json().catch(() => ({}))
  if (!res.ok) {
    throw new Error(typeof data.detail === 'string' ? data.detail : `Fehler ${res.status}`)
  }
  return data as T
}

export function KioskStampPage() {
  const [displayTime, setDisplayTime]       = useState(new Date())
  const [serverTimeOffset, setServerTimeOffset] = useState(0) // ms offset from server
  const [isOnline, setIsOnline]             = useState(navigator.onLine)
  const [heartbeatStatus, setHeartbeatStatus] = useState<'connected' | 'error' | 'pending'>('pending')
  const [heartbeatError, setHeartbeatError] = useState<string | null>(null)
  const [hasCredentials, setHasCredentials] = useState<boolean | null>(null)
  const [deviceId, setDeviceId]             = useState<string | null>(null)
  const [isLeaderTab, setIsLeaderTab]       = useState(false)

  // ── Login/Stamp-Flow ────────────────────────────────────────────────────
  const [step, setStep]               = useState<Step>('idle')
  const [nfcUid, setNfcUid]           = useState<string | null>(null)
  const [pin, setPin]                 = useState('')
  const [sessionToken, setSessionToken] = useState<string | null>(null)
  const [userName, setUserName]       = useState<string | null>(null)
  const [stampStatus, setStampStatus] = useState<StampStatus | null>(null)
  const [flowError, setFlowError]     = useState<string | null>(null)
  const [busy, setBusy]               = useState(false)
  const [doneMessage, setDoneMessage] = useState<string | null>(null)

  const heartbeatIntervalRef = useRef<ReturnType<typeof setInterval> | null>(null)
  const clockIntervalRef     = useRef<ReturnType<typeof setInterval> | null>(null)
  const broadcastRef         = useRef<BroadcastChannel | null>(null)
  const swRegRef             = useRef<ServiceWorkerRegistration | null>(null)
  const startTimeRef         = useRef<number>(Date.now())
  const resetTimeoutRef      = useRef<ReturnType<typeof setTimeout> | null>(null)

  // NFC keyboard-wedge capture buffer
  const nfcBufferRef    = useRef('')
  const nfcLastKeyAtRef = useRef(0)

  const resetFlow = useCallback(() => {
    setStep('idle')
    setNfcUid(null)
    setPin('')
    setSessionToken(null)
    setUserName(null)
    setStampStatus(null)
    setFlowError(null)
    setDoneMessage(null)
    nfcBufferRef.current = ''
  }, [])

  function scheduleAutoReset(ms = 4000) {
    if (resetTimeoutRef.current) clearTimeout(resetTimeoutRef.current)
    resetTimeoutRef.current = setTimeout(resetFlow, ms)
  }

  // Live clock – uses server time offset
  useEffect(() => {
    clockIntervalRef.current = setInterval(() => {
      const localNow = Date.now()
      setDisplayTime(new Date(localNow + serverTimeOffset))
    }, 1000)
    return () => {
      if (clockIntervalRef.current) clearInterval(clockIntervalRef.current)
    }
  }, [serverTimeOffset])

  // Online/Offline events
  useEffect(() => {
    const onOnline  = () => setIsOnline(true)
    const onOffline = () => setIsOnline(false)
    window.addEventListener('online',  onOnline)
    window.addEventListener('offline', onOffline)
    return () => {
      window.removeEventListener('online',  onOnline)
      window.removeEventListener('offline', onOffline)
    }
  }, [])

  // ServiceWorker + BroadcastChannel setup
  useEffect(() => {
    let cancelled = false

    async function init() {
      // Register / retrieve SW
      if ('serviceWorker' in navigator) {
        try {
          await navigator.serviceWorker.register('/kiosk-sw.js', { scope: '/' })
          const ready = await navigator.serviceWorker.ready
          swRegRef.current = ready

          if (!cancelled) {
            const result = await sendMessageToSW(ready, { type: 'CHECK_CREDENTIALS' })
            setHasCredentials(!!result.hasCredentials)
            setDeviceId(result.deviceId as string | null)
          }
        } catch {
          if (!cancelled) {
            setHasCredentials(false)
          }
        }
      } else {
        setHasCredentials(false)
      }

      if (cancelled) return

      // Leader-election via BroadcastChannel:
      // We announce ourselves; if we receive an announcement from another tab
      // we yield. Simple "last writer wins for 1s window" approach.
      const channel = new BroadcastChannel(HEARTBEAT_CHANNEL)
      broadcastRef.current = channel

      let isLeader = true

      channel.onmessage = (e) => {
        if (e.data?.type === 'HEARTBEAT_LEADER_ANNOUNCE') {
          // Another tab claims leadership – we yield
          isLeader = false
          setIsLeaderTab(false)
          if (heartbeatIntervalRef.current) {
            clearInterval(heartbeatIntervalRef.current)
            heartbeatIntervalRef.current = null
          }
        }
        if (e.data?.type === 'HEARTBEAT_LEADER_YIELD' && !isLeader) {
          // Previous leader stepped down – we take over
          isLeader = true
          setIsLeaderTab(true)
          startHeartbeatLoop()
        }
      }

      // Announce ourselves as leader after a short random delay
      // to avoid simultaneous announcements on page reload
      const delay = Math.random() * 500
      await new Promise(r => setTimeout(r, delay))
      if (!cancelled) {
        channel.postMessage({ type: 'HEARTBEAT_LEADER_ANNOUNCE' })
        setIsLeaderTab(isLeader)
        if (isLeader) startHeartbeatLoop()
      }
    }

    init()

    return () => {
      cancelled = true
      if (heartbeatIntervalRef.current) clearInterval(heartbeatIntervalRef.current)
      if (broadcastRef.current) {
        broadcastRef.current.postMessage({ type: 'HEARTBEAT_LEADER_YIELD' })
        broadcastRef.current.close()
      }
    }
  // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [])

  // NFC keyboard-wedge listener: only active on the idle screen. Cheap USB-HID
  // RFID readers "type" the UID as fast keystrokes ending with Enter - a human
  // typing on a real keyboard can't hit 4+ digits within NFC_BURST_MS.
  useEffect(() => {
    if (step !== 'idle' || hasCredentials !== true) return

    function onKeyDown(e: KeyboardEvent) {
      const now = Date.now()
      if (now - nfcLastKeyAtRef.current > NFC_BURST_MS) {
        nfcBufferRef.current = ''
      }
      nfcLastKeyAtRef.current = now

      if (e.key === 'Enter') {
        const uid = nfcBufferRef.current
        nfcBufferRef.current = ''
        if (uid.length >= NFC_MIN_LENGTH) {
          setNfcUid(uid)
          setStep('pin')
          setPin('')
          setFlowError(null)
        }
        return
      }
      if (/^[0-9A-Za-z]$/.test(e.key)) {
        nfcBufferRef.current += e.key
      }
    }

    window.addEventListener('keydown', onKeyDown)
    return () => window.removeEventListener('keydown', onKeyDown)
  }, [step, hasCredentials])

  function startHeartbeatLoop() {
    // Send immediately, then on interval
    sendHeartbeat()
    if (heartbeatIntervalRef.current) clearInterval(heartbeatIntervalRef.current)
    heartbeatIntervalRef.current = setInterval(sendHeartbeat, HEARTBEAT_INTERVAL_MS)
  }

  async function sendHeartbeat() {
    if (!navigator.onLine) {
      setHeartbeatStatus('error')
      setHeartbeatError('Kein Netzwerk')
      return
    }

    const uptimeSeconds = Math.floor((Date.now() - startTimeRef.current) / 1000)
    const body = JSON.stringify({
      uptime_seconds:         uptimeSeconds,
      client_version:         CLIENT_VERSION,
      queued_offline_entries: 0,
      current_user_id:        null,
    })

    try {
      const res = await fetch('/api/v1/kiosk/heartbeat', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body,
      })

      if (!res.ok) {
        const errData = await res.json().catch(() => ({ detail: res.statusText }))
        throw new Error(
          typeof errData.detail === 'string' ? errData.detail : res.statusText
        )
      }

      const data: HeartbeatResponse = await res.json().catch(() => ({}))

      // Sync server time if provided
      if (data.server_timestamp) {
        const localNow = Date.now()
        const serverMs = data.server_timestamp * 1000
        setServerTimeOffset(serverMs - localNow)
      } else if (data.server_time) {
        const localNow = Date.now()
        const serverMs = new Date(data.server_time).getTime()
        if (!isNaN(serverMs)) {
          setServerTimeOffset(serverMs - localNow)
        }
      }

      setHeartbeatStatus('connected')
      setHeartbeatError(null)
    } catch (e: unknown) {
      setHeartbeatStatus('error')
      setHeartbeatError(e instanceof Error ? e.message : 'Verbindungsfehler')
    }
  }

  async function submitPin() {
    if (!nfcUid || pin.length < 4) return
    setBusy(true)
    setFlowError(null)
    try {
      const login = await kioskFetch<{ session_token: string; user_name: string }>(
        '/api/v1/kiosk/auth/nfc', { nfc_uid: nfcUid, pin }
      )
      setSessionToken(login.session_token)
      setUserName(login.user_name)
      const status = await kioskFetch<StampStatus>(
        '/api/v1/kiosk/stamp/status', { session_token: login.session_token }
      )
      setStampStatus(status)
      setStep('action')
    } catch (e: unknown) {
      setFlowError(e instanceof Error ? e.message : 'Anmeldung fehlgeschlagen')
      setPin('')
    } finally {
      setBusy(false)
    }
  }

  async function performAction(action: 'in' | 'out' | 'break-start' | 'break-end') {
    if (!sessionToken) return
    setBusy(true)
    setFlowError(null)
    try {
      const result = await kioskFetch<{ status: string; warnings?: string[] }>(
        `/api/v1/kiosk/stamp/${action}`, { session_token: sessionToken }
      )
      const labels: Record<string, string> = {
        stamped_in: 'Eingestempelt ✓',
        stamped_out: 'Ausgestempelt ✓',
        break_started: 'Pause gestartet ✓',
        break_ended: 'Pause beendet ✓',
      }
      setDoneMessage(labels[result.status] ?? 'Erledigt ✓')
      if (result.warnings?.length) {
        setFlowError(result.warnings.join(' · '))
      }
      setStep('done')
      scheduleAutoReset()
    } catch (e: unknown) {
      setFlowError(e instanceof Error ? e.message : 'Aktion fehlgeschlagen')
    } finally {
      setBusy(false)
    }
  }

  return (
    <div className='min-h-screen bg-gray-950 text-white flex flex-col'>

      {/* Status bar */}
      <div className='flex items-center justify-between px-6 py-3 bg-gray-900 border-b border-gray-800'>
        <div className='flex items-center gap-3'>
          <span className='text-sm font-semibold text-gray-300'>TimeMaster Kiosk</span>
          {deviceId && (
            <span className='text-xs text-gray-600 font-mono'>
              {deviceId.slice(0, 8)}…
            </span>
          )}
        </div>

        <div className='flex items-center gap-4'>
          {/* Heartbeat / connection status */}
          {isLeaderTab && (
            <div className='flex items-center gap-1.5 text-xs'>
              {heartbeatStatus === 'connected' && (
                <>
                  <span className='w-2 h-2 rounded-full bg-green-500 animate-pulse' />
                  <span className='text-green-400'>Verbunden</span>
                </>
              )}
              {heartbeatStatus === 'error' && (
                <>
                  <span className='w-2 h-2 rounded-full bg-red-500' />
                  <span className='text-red-400'>{heartbeatError ?? 'Verbindungsfehler'}</span>
                </>
              )}
              {heartbeatStatus === 'pending' && (
                <>
                  <span className='w-2 h-2 rounded-full bg-yellow-500 animate-pulse' />
                  <span className='text-yellow-400'>Verbinde…</span>
                </>
              )}
            </div>
          )}
          {!isLeaderTab && (
            <span className='text-xs text-gray-600'>Heartbeat: anderer Tab aktiv</span>
          )}

          {/* Online/Offline indicator */}
          <div className='flex items-center gap-1.5 text-xs'>
            {isOnline
              ? <span className='text-gray-500'>Online</span>
              : <span className='text-orange-400 font-semibold'>Offline</span>
            }
          </div>
        </div>
      </div>

      {/* Main content */}
      <div className='flex-1 flex flex-col items-center justify-center gap-8 px-6'>

        {/* Clock */}
        <div className='text-center'>
          <div className='text-8xl font-bold font-mono tracking-tight tabular-nums'>
            {formatTime(displayTime)}
          </div>
          <div className='text-xl text-gray-400 mt-2'>
            {formatDate(displayTime)}
          </div>
        </div>

        {/* Credentials missing warning */}
        {hasCredentials === false && (
          <div className='max-w-md w-full bg-yellow-900/50 border border-yellow-700 rounded-2xl p-5 text-center'>
            <div className='text-3xl mb-2'>⚠️</div>
            <p className='text-yellow-300 font-semibold mb-1'>Kiosk nicht eingerichtet</p>
            <p className='text-yellow-500 text-sm mb-4'>
              Dieses Geraet hat noch kein Ed25519-Schluesselpaar.
              Bitte zuerst den Setup-Assistenten durchlaufen.
            </p>
            <Link
              to='/kiosk/setup'
              className='inline-block px-5 py-2.5 bg-yellow-600 hover:bg-yellow-500 rounded-xl
                         font-semibold text-sm transition-colors'
            >
              Kiosk einrichten →
            </Link>
          </div>
        )}

        {/* ── Login/Stempel-Flow ─────────────────────────────────────────── */}
        {hasCredentials === true && step === 'idle' && (
          <div className='max-w-md w-full bg-gray-800 rounded-2xl p-8 text-center space-y-3'>
            <div className='text-5xl'>💳</div>
            <p className='text-gray-200 font-semibold text-lg'>Bitte Karte auflegen</p>
            <p className='text-gray-500 text-sm'>
              Kein Kartenleser-Fokus nötig – Eingabe wird automatisch erkannt.
            </p>
            {heartbeatStatus === 'error' && heartbeatError && (
              <div className='bg-red-900/40 border border-red-700 rounded-lg px-3 py-2 text-sm text-red-300'>
                Verbindungsfehler: {heartbeatError}
              </div>
            )}
          </div>
        )}

        {hasCredentials === true && step === 'pin' && (
          <div className='max-w-xs w-full bg-gray-800 rounded-2xl p-6 text-center space-y-4'>
            <div className='text-4xl'>🔑</div>
            <p className='text-gray-200 font-semibold'>PIN eingeben</p>
            <div className='text-2xl font-mono tracking-[0.5em] h-8'>
              {'•'.repeat(pin.length)}
            </div>
            {flowError && (
              <div className='bg-red-900/40 border border-red-700 rounded-lg px-3 py-2 text-sm text-red-300'>
                {flowError}
              </div>
            )}
            <div className='grid grid-cols-3 gap-2'>
              {['1','2','3','4','5','6','7','8','9','','0','⌫'].map((k, i) => (
                k === '' ? <div key={i} /> : (
                  <button
                    key={i}
                    disabled={busy}
                    onClick={() => {
                      if (k === '⌫') { setPin(p => p.slice(0, -1)); return }
                      setPin(p => (p.length < 6 ? p + k : p))
                    }}
                    className='py-3 rounded-xl bg-gray-700 hover:bg-gray-600 text-lg font-semibold
                               disabled:opacity-50 transition-colors'
                  >
                    {k}
                  </button>
                )
              ))}
            </div>
            <div className='flex gap-2 pt-2'>
              <button
                onClick={resetFlow}
                className='flex-1 py-2 rounded-xl bg-gray-700 hover:bg-gray-600 text-sm'
              >
                Abbrechen
              </button>
              <button
                onClick={submitPin}
                disabled={pin.length < 4 || busy}
                className='flex-1 py-2 rounded-xl bg-blue-600 hover:bg-blue-500 text-sm font-semibold
                           disabled:opacity-50'
              >
                {busy ? '…' : 'Bestätigen'}
              </button>
            </div>
          </div>
        )}

        {hasCredentials === true && step === 'action' && stampStatus && (
          <div className='max-w-sm w-full bg-gray-800 rounded-2xl p-6 text-center space-y-4'>
            <p className='text-gray-400 text-sm'>Angemeldet als</p>
            <p className='text-gray-100 font-semibold text-xl'>{userName}</p>
            {flowError && (
              <div className='bg-red-900/40 border border-red-700 rounded-lg px-3 py-2 text-sm text-red-300'>
                {flowError}
              </div>
            )}
            <div className='space-y-2 pt-2'>
              {!stampStatus.stamped_in && (
                <button
                  onClick={() => performAction('in')}
                  disabled={busy}
                  className='w-full py-4 rounded-xl bg-green-600 hover:bg-green-500 font-semibold text-lg disabled:opacity-50'
                >
                  Einstempeln
                </button>
              )}
              {stampStatus.stamped_in && !stampStatus.on_break && (
                <>
                  <button
                    onClick={() => performAction('out')}
                    disabled={busy}
                    className='w-full py-4 rounded-xl bg-red-600 hover:bg-red-500 font-semibold text-lg disabled:opacity-50'
                  >
                    Ausstempeln
                  </button>
                  <button
                    onClick={() => performAction('break-start')}
                    disabled={busy}
                    className='w-full py-3 rounded-xl bg-gray-700 hover:bg-gray-600 font-medium disabled:opacity-50'
                  >
                    Pause starten
                  </button>
                </>
              )}
              {stampStatus.stamped_in && stampStatus.on_break && (
                <button
                  onClick={() => performAction('break-end')}
                  disabled={busy}
                  className='w-full py-4 rounded-xl bg-blue-600 hover:bg-blue-500 font-semibold text-lg disabled:opacity-50'
                >
                  Pause beenden
                </button>
              )}
            </div>
            <button
              onClick={resetFlow}
              className='text-xs text-gray-500 hover:text-gray-300 underline pt-2'
            >
              Abbrechen
            </button>
          </div>
        )}

        {hasCredentials === true && step === 'done' && (
          <div className='max-w-sm w-full bg-gray-800 rounded-2xl p-8 text-center space-y-3'>
            <div className='text-5xl'>✅</div>
            <p className='text-green-400 font-semibold text-xl'>{doneMessage}</p>
            {flowError && (
              <p className='text-yellow-400 text-sm'>{flowError}</p>
            )}
            <p className='text-gray-600 text-xs'>Zurück zur Startseite in wenigen Sekunden…</p>
          </div>
        )}

        {/* Loading state */}
        {hasCredentials === null && (
          <div className='text-gray-600 text-sm animate-pulse'>Initialisiere…</div>
        )}
      </div>

      {/* Footer */}
      <div className='px-6 py-4 text-center'>
        <Link
          to='/kiosk/setup'
          className='text-xs text-gray-700 hover:text-gray-500 transition-colors underline'
        >
          Kiosk-Einrichtung
        </Link>
      </div>
    </div>
  )
}
