import { createContext, useContext, useCallback, useEffect, useRef } from 'react'
import { useQueryClient } from '@tanstack/react-query'
import useAuthStore from '../store/authStore'
import useConnectionStore from '../store/connectionStore'
import { useWebSocket } from '../hooks/useWebSocket'
import db from '../db/posdb'
import client from '../api/client'
import { flushAllOfflineOps } from '../services/offlineOrders'
import useChatStore from '../store/chatStore'
import useKdsReadyStore from '../store/kdsReadyStore'

const SSEContext = createContext(null)

export function useSSEContext() {
  return useContext(SSEContext)
}

export function SSEProvider({ children }) {
  const { token } = useAuthStore()
  const { setLost, setOnline, setSseAlive } = useConnectionStore()
  const queryClient = useQueryClient()
  const wsAlive = useRef(false)
  const syncingRef = useRef(false)   // prevent concurrent syncs

  const setLostRef = useRef(setLost)
  const setOnlineRef = useRef(setOnline)
  useEffect(() => { setLostRef.current = setLost }, [setLost])
  useEffect(() => { setOnlineRef.current = setOnline }, [setOnline])

  // ── Snapshot helpers ──────────────────────────────────────────────────────────

  const snapshotTables = useCallback(async () => {
    try {
      const res = await client.get('/api/tables/')
      await db.pos_tables.bulkPut(res.data)
    } catch { /* offline — snapshot stays as-is */ }
  }, [])

  const snapshotOrders = useCallback(async () => {
    try {
      const res = await client.get('/api/orders/active')
      const fullOrders = await Promise.all(
        res.data.map(o =>
          client.get(`/api/orders/${o.id}`)
            .then(r => ({
              ...r.data,
              waiter_ids: r.data.waiters?.map(w => w.waiter_id) ?? o.waiter_ids ?? [],
            }))
            .catch(() => o)
        )
      )
      await db.pos_orders.bulkPut(fullOrders)
      // Purge any IDB orders that are no longer active on the server
      const activeIds = new Set(fullOrders.map(o => o.id))
      const allIdbIds = await db.pos_orders.toCollection().primaryKeys()
      const staleIds = allIdbIds.filter(id => !activeIds.has(id) && typeof id === 'number')
      if (staleIds.length > 0) await db.pos_orders.bulkDelete(staleIds)
    } catch { /* offline — snapshot stays as-is */ }
    useKdsReadyStore.getState().refresh()
  }, [])

  const snapshotProducts = useCallback(async () => {
    try {
      const [prodRes, catRes] = await Promise.all([
        client.get('/api/products/'),
        client.get('/api/products/categories'),
      ])
      await db.products.bulkPut(prodRes.data)
      await db.categories.bulkPut(catRes.data)
      // Also update React Query cache so AddItemsPage gets it immediately
      queryClient.setQueryData(['products'], { products: prodRes.data, categories: catRes.data })
    } catch { /* offline — snapshot stays as-is */ }
  }, [queryClient])

  const snapshotCatalogue = useCallback(async () => {
    try {
      const [groupsRes, flagDefsRes, flagAssignRes] = await Promise.all([
        client.get('/api/tables/groups'),
        client.get('/api/flags/defs'),
        client.get('/api/flags/assignments'),
      ])
      if (db.table_groups) await db.table_groups.bulkPut(groupsRes.data)
      if (db.flag_defs) await db.flag_defs.bulkPut(flagDefsRes.data)
      if (db.flag_assignments) {
        await db.flag_assignments.clear()
        if (flagAssignRes.data.length > 0) await db.flag_assignments.bulkAdd(flagAssignRes.data)
      }
    } catch { /* offline — snapshot stays as-is */ }
  }, [])

  const fullRefresh = useCallback(async () => {
    await Promise.all([snapshotTables(), snapshotOrders(), snapshotCatalogue(), snapshotProducts()])
  }, [snapshotTables, snapshotOrders, snapshotCatalogue, snapshotProducts])

  // ── Flush all offline queues in strict sequence order ────────────────────────

  const flushOfflineQueue = useCallback(async () => {
    if (syncingRef.current) return
    syncingRef.current = true
    try {
      await flushAllOfflineOps()
    } finally {
      syncingRef.current = false
    }
  }, [])

  // ── Called whenever we confirm the server is reachable again ─────────────────

  const handleCameOnline = useCallback(async () => {
    const wasOffline = useConnectionStore.getState().status !== 'online'
    setOnlineRef.current()
    window.dispatchEvent(new Event('sse-reconnected'))
    if (wasOffline) {
      await flushOfflineQueue()
    }
    await fullRefresh()
  }, [fullRefresh, flushOfflineQueue])

  // ── SSE event handler ─────────────────────────────────────────────────────────

  const handleEvent = useCallback(async (type, data) => {
    window.dispatchEvent(new CustomEvent('sse-event', { detail: { type, data } }))

    switch (type) {
      case 'order_updated': {
        try {
          const full = await client.get(`/api/orders/${data.order_id}`)
          const o = full.data
          await db.pos_orders.put({
            ...o,
            waiter_ids: o.waiters?.map(w => w.waiter_id) ?? [],
          })
        } catch {
          const existing = await db.pos_orders.get(data.order_id)
          await db.pos_orders.put({
            ...(existing || {}),
            id: data.order_id,
            table_id: data.table_id,
            status: data.status,
            waiter_ids: existing?.waiter_ids || [],
          })
        }
        useKdsReadyStore.getState().refresh()
        break
      }
      case 'order_paid': {
        // Do NOT write to IDB — when auto-close is on, order_closed fires immediately
        // after order_paid. Writing here races with the delete in order_closed and
        // can resurrect the order in IDB after it's been deleted.
        useKdsReadyStore.getState().refresh()
        break
      }
      case 'order_closed': {
        await db.pos_orders.delete(data.order_id)
        useKdsReadyStore.getState().refresh()
        break
      }
      case 'kds_item_updated':
      case 'kds_order_updated': {
        try {
          const full = await client.get(`/api/orders/${data.order_id}`)
          const o = full.data
          await db.pos_orders.put({
            ...o,
            waiter_ids: o.waiters?.map(w => w.waiter_id) ?? [],
          })
        } catch {}
        useKdsReadyStore.getState().refresh()
        break
      }
      case 'table_list_changed': {
        await snapshotTables()
        break
      }
      case 'table_flags_changed': {
        await snapshotCatalogue()
        break
      }
      case 'products_changed': {
        await snapshotProducts()
        break
      }
      case 'chat_message': {
        const { conversation_id, message_id, sender_id, sender_name, body, sent_at } = data
        const { conversations, upsertConversation, incrementUnread } = useChatStore.getState()
        const existingConv = conversations.find(c => c.id === conversation_id)
        const newMsg = { id: message_id, conversation_id, sender_id, sender_name, body, sent_at, is_deleted: false }
        if (existingConv) {
          upsertConversation({ ...existingConv, last_message: newMsg })
          if (sender_id !== useAuthStore.getState().user?.id) {
            incrementUnread(conversation_id)
          }
        } else {
          client.get('/api/chat/conversations')
            .then(r => useChatStore.getState().setConversations(r.data || []))
            .catch(() => {})
        }
        break
      }
      case 'chat_read': {
        useChatStore.getState().updateLastRead(data.conversation_id, data.user_id, data.read_at)
        break
      }
      default:
        break
    }
  }, [snapshotTables, snapshotCatalogue, snapshotProducts, queryClient])

  // ── SSE lifecycle ─────────────────────────────────────────────────────────────

  const handleConnect = useCallback(async () => {
    wsAlive.current = true
    setSseAlive(true)
    await handleCameOnline()
  }, [handleCameOnline, setSseAlive])

  const handleDisconnect = useCallback(() => {
    wsAlive.current = false
    setSseAlive(false)
    // The WS hook will auto-reconnect with exponential backoff.
    // Don't immediately declare offline — wait for the axios interceptor
    // or visibility handler to confirm the server is truly unreachable.
  }, [setSseAlive])

  const { reconnect } = useWebSocket({
    token,
    enabled: !!token,
    onEvent: handleEvent,
    onConnect: handleConnect,
    onDisconnect: handleDisconnect,
  })

  // ── Auto-offline on any failed API request ────────────────────────────────────
  // The axios interceptor fires 'backend-offline' on network errors.
  // We act on it immediately — no grace, no modals.

  useEffect(() => {
    function onBackendOffline() {
      setLostRef.current()
    }
    window.addEventListener('backend-offline', onBackendOffline)
    return () => window.removeEventListener('backend-offline', onBackendOffline)
  }, [])

  // ── Foreground / visibility handler ──────────────────────────────────────────
  // When the phone screen turns back on or the user switches back to the tab,
  // immediately probe the server and pull a full refresh.

  useEffect(() => {
    if (!token) return

    async function onVisible() {
      if (document.visibilityState !== 'visible') return
      try {
        await client.get('/api/system/health')
        const currentStatus = useConnectionStore.getState().status
        if (currentStatus !== 'online') {
          reconnect()
          await handleCameOnline()
        } else if (!wsAlive.current) {
          // WS dropped silently while sleeping — re-establish and refresh
          setSseAlive(false)
          reconnect()
          await fullRefresh()
        } else {
          // Was online the whole time — still do a data refresh to catch anything missed
          await fullRefresh()
        }
      } catch {
        setLostRef.current()
      }
    }

    document.addEventListener('visibilitychange', onVisible)
    return () => document.removeEventListener('visibilitychange', onVisible)
  }, [token, reconnect, handleCameOnline, fullRefresh])

  // ── Initial snapshot on login ─────────────────────────────────────────────────

  useEffect(() => {
    if (token) fullRefresh()
  }, [token, fullRefresh])

  return (
    <SSEContext.Provider value={{ reconnect, fullRefresh }}>
      {children}
    </SSEContext.Provider>
  )
}
