import { useEffect, useRef, useCallback } from 'react';
import { useStore } from '@/store/useStore';
import { WS_URL } from '@/lib/api';
import type { QDScoreUpdate, VaultOpenedEvent } from '@/types';

const SENTINEL_WS_ROOT = `${WS_URL}/api/sentinel/ws`;

type WsEventType =
  | 'WS_ASSET_OPENED'
  | 'QD_UPDATED'
  | 'LEAD_TAGGED'
  | 'system'
  | 'ack';

interface WsMessage {
  type: WsEventType;
  data?: Record<string, unknown>;
  timestamp?: string;
}

interface UseVelocitySocketOptions {
  channel?: 'notifications' | 'perception';
  onConnect?: () => void;
  onDisconnect?: () => void;
  onMessage?: (msg: WsMessage) => void;
}

export function useVelocitySocket(options: UseVelocitySocketOptions = {}) {
  const { channel = 'notifications', onConnect, onDisconnect, onMessage } = options;
  const { addNotification } = useStore();

  const wsRef = useRef<WebSocket | null>(null);
  const retryCountRef = useRef(0);
  const retryTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
  const pendingBufferRef = useRef<string[]>([]);
  const isMountedRef = useRef(true);

  const handleMessage = useCallback(
    (event: MessageEvent) => {
      let msg: WsMessage;
      try {
        msg = JSON.parse(event.data as string) as WsMessage;
      } catch {
        return;
      }

      onMessage?.(msg);

      switch (msg.type) {
        case 'WS_ASSET_OPENED': {
          const d = msg.data as Partial<VaultOpenedEvent>;
          addNotification({
            type: 'velocity_link_opened',
            title: 'Velocity Link Opened',
            body: `${d.lead_name ?? 'A prospect'} just opened ${d.asset_name ?? 'your asset'}.`,
            leadId: d.lead_id,
          });
          break;
        }
        case 'QD_UPDATED': {
          const d = msg.data as Partial<QDScoreUpdate>;
          if ((d.qd_score ?? 0) >= 75) {
            addNotification({
              type: 'qd_spike',
              title: 'QD Score Spike',
              body: `QD Score jumped to ${d.qd_score}. ${d.reasoning ?? ''}`.trim(),
              leadId: d.lead_id,
              qdScore: d.qd_score,
            });
          }
          break;
        }
        case 'LEAD_TAGGED': {
          const d = msg.data as { lead_id?: string; lead_name?: string; tags?: string[] };
          if (d.tags?.length) {
            addNotification({
              type: 'lead_tagged',
              title: 'Lead Tagged',
              body: `${d.lead_name ?? 'Lead'} tagged as ${d.tags.join(', ')}.`,
              leadId: d.lead_id,
              tags: d.tags,
            });
          }
          break;
        }
        default:
          break;
      }
    },
    [addNotification, onMessage],
  );

  const connect = useCallback(() => {
    if (!isMountedRef.current) return;

    const url = `${SENTINEL_WS_ROOT}/${channel}`;
    const ws = new WebSocket(url);
    wsRef.current = ws;

    ws.onopen = () => {
      retryCountRef.current = 0;
      pendingBufferRef.current.forEach((msg) => ws.send(msg));
      pendingBufferRef.current = [];
      onConnect?.();
    };

    ws.onmessage = handleMessage;

    ws.onclose = () => {
      onDisconnect?.();
      if (!isMountedRef.current) return;
      if (retryCountRef.current >= 5) return;
      const delay = Math.min(1000 * 2 ** retryCountRef.current, 30_000);
      retryCountRef.current += 1;
      retryTimerRef.current = setTimeout(connect, delay);
    };

    ws.onerror = () => ws.close();
  }, [channel, handleMessage, onConnect, onDisconnect]);

  useEffect(() => {
    isMountedRef.current = true;
    connect();

    return () => {
      isMountedRef.current = false;
      if (retryTimerRef.current) clearTimeout(retryTimerRef.current);
      wsRef.current?.close();
    };
  }, [connect]);

  const sendPacket = useCallback((payload: unknown) => {
    const str = JSON.stringify(payload);
    if (wsRef.current?.readyState === WebSocket.OPEN) {
      wsRef.current.send(str);
    } else {
      pendingBufferRef.current.push(str);
      if (pendingBufferRef.current.length > 100) {
        pendingBufferRef.current.shift();
      }
    }
  }, []);

  return { sendPacket };
}
