'use client';

// lib/hooks/useLeadRealtime.ts
//
// Live delivery of lead notifications.
//
// Two transports, in priority order:
//   1. socket.io — if your NotificationsModule already runs a gateway (the
//      one that makes audit-request notifications arrive live), this hook
//      joins the same connection and listens for the LEAD_* types.
//   2. polling — a fallback that hits your notifications endpoint every
//      `pollMs`. Slower, but it means the page is never silently stale if
//      the socket is unavailable in an environment.
//
// If you already have a global notification provider mounted in your
// layout, delete transport 1 here and just call `onLeadEvent` from that
// provider instead — two sockets to the same server is a waste.

import { useCallback, useEffect, useRef, useState } from 'react';
import type { LeadNotification } from '@/lib/api/types/leads.types';

const SOCKET_URL =
  process.env.NEXT_PUBLIC_SOCKET_URL ||
  process.env.NEXT_PUBLIC_API_URL?.replace(/\/api\/?$/, '') ||
  'http://localhost:3007';

const LEAD_EVENT_TYPES = new Set([
  'LEAD_ASSIGNED',
  'LEAD_UNASSIGNED',
  'LEAD_BULK_ASSIGNED',
  'LEAD_HANDOVER_REQUESTED',
]);

interface Options {
  /** Current user id — the server only pushes rows addressed to them. */
  userId: number | null;
  /** Fired for every lead notification that arrives. */
  onLeadEvent: (n: LeadNotification) => void;
  /** Polling interval when the socket isn't available. 0 disables polling. */
  pollMs?: number;
  enabled?: boolean;
}

export function useLeadRealtime({
  userId,
  onLeadEvent,
  pollMs = 30_000,
  enabled = true,
}: Options) {
  const [connected, setConnected] = useState(false);
  const [transport, setTransport] = useState<'socket' | 'poll' | 'off'>('off');

  // Keep the callback in a ref so re-renders don't tear down the socket.
  const handlerRef = useRef(onLeadEvent);
  useEffect(() => {
    handlerRef.current = onLeadEvent;
  }, [onLeadEvent]);

  const seenRef = useRef<Set<string>>(new Set());

  const emit = useCallback((n: LeadNotification) => {
    if (!LEAD_EVENT_TYPES.has(n.type)) return;

    // The socket and the poller can both surface the same notification.
    // De-dupe on type + lead id + title so the user sees one toast.
    const key = `${n.type}:${n.metadata?.lead_id ?? ''}:${n.title}`;
    if (seenRef.current.has(key)) return;
    seenRef.current.add(key);
    if (seenRef.current.size > 200) {
      seenRef.current = new Set([...seenRef.current].slice(-100));
    }

    handlerRef.current(n);
  }, []);

  // ── Transport 1: socket.io ────────────────────────────────────────────
  useEffect(() => {
    if (!enabled || !userId) return;

    let socket: any = null;
    let cancelled = false;

    (async () => {
      let io: any;
      try {
        // Dynamic import: if socket.io-client isn't installed, this throws
        // and we fall through to polling instead of crashing the page.
        ({ io } = await import('socket.io-client'));
      } catch {
        setTransport('poll');
        return;
      }
      if (cancelled) return;

      const token =
        typeof window !== 'undefined'
          ? localStorage.getItem('token') ?? localStorage.getItem('access_token')
          : null;

      socket = io(SOCKET_URL, {
        transports: ['websocket'],
        auth: token ? { token } : undefined,
        query: { userId: String(userId) },
      });

      socket.on('connect', () => {
        setConnected(true);
        setTransport('socket');
        // Adjust the room name to whatever your gateway expects.
        socket.emit('join', { room: `user:${userId}` });
      });

      socket.on('disconnect', () => {
        setConnected(false);
        setTransport('poll');
      });

      socket.on('connect_error', () => {
        setConnected(false);
        setTransport('poll');
      });

      // Listen broadly: different gateways name this differently, and
      // listening to all three costs nothing.
      ['notification', 'notification:new', 'lead:notification'].forEach((evt) =>
        socket.on(evt, (payload: LeadNotification) => {
          if (!payload?.type) return;
          if (payload.user_id && payload.user_id !== userId) return;
          emit(payload);
        }),
      );
    })();

    return () => {
      cancelled = true;
      if (socket) {
        socket.removeAllListeners?.();
        socket.disconnect?.();
      }
      setConnected(false);
    };
  }, [userId, enabled, emit]);

  // ── Transport 2: polling fallback ─────────────────────────────────────
  useEffect(() => {
    if (!enabled || !userId || !pollMs) return;
    if (transport === 'socket') return;

    const base = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3007/api';
    let stopped = false;

    const tick = async () => {
      try {
        const token =
          typeof window !== 'undefined'
            ? localStorage.getItem('token') ?? localStorage.getItem('access_token')
            : null;

        // 👉 Point this at your real unread-notifications endpoint.
        const res = await fetch(`${base}/notifications?unread=true&limit=20`, {
          headers: token ? { Authorization: `Bearer ${token}` } : undefined,
        });
        if (!res.ok || stopped) return;

        const json = await res.json();
        const rows: LeadNotification[] = Array.isArray(json)
          ? json
          : json?.data ?? [];
        rows.forEach(emit);
      } catch {
        // Silent: a failed poll is not something to interrupt the user with.
      }
    };

    tick();
    const timer = setInterval(tick, pollMs);
    return () => {
      stopped = true;
      clearInterval(timer);
    };
  }, [userId, enabled, pollMs, transport, emit]);

  return { connected, transport };
}

/** Reads the signed-in user id the same way MyAuditsPage does. */
export function useCurrentUserId(): number | null {
  const [id, setId] = useState<number | null>(null);

  useEffect(() => {
    if (typeof window === 'undefined') return;
    try {
      const raw = localStorage.getItem('user');
      if (raw) {
        const parsed = JSON.parse(raw);
        const found = parsed?.id ?? parsed?.userId ?? parsed?.user_id;
        if (found) {
          setId(Number(found));
          return;
        }
      }
    } catch {
      /* fall through */
    }
    const direct = localStorage.getItem('userId');
    if (direct) setId(Number(direct));
  }, []);

  return id;
}
