import { useState, useEffect, useCallback, useRef } from 'react';
import axios from 'axios';
import { io, Socket } from 'socket.io-client';
import { ChatToken, ChatMessage, UseChatWebSocketReturn } from '../types/chat';

interface WebSocketEvents {
  onMessage?: (message: ChatMessage) => void;
  onTyping?: (conversationId: string, userId: number, isTyping: boolean) => void;
  onOnlineStatus?: (userId: number, isOnline: boolean) => void;
  onConversationUpdate?: (conversationId: string, data: any) => void;
}

export const useChatWebSocket = (events: WebSocketEvents = {}): UseChatWebSocketReturn => {
  const [connected, setConnected] = useState(false);
  const [connecting, setConnecting] = useState(false);
  const [error, setError] = useState<string | null>(null);
  const [onlineUsers, setOnlineUsers] = useState<Set<number>>(new Set());

  const socketRef = useRef<Socket | null>(null);
  const tokenRef = useRef<ChatToken | null>(null);
  const reconnectAttemptsRef = useRef(0);
  const tokenRetryCountRef = useRef(0);
  const eventsRef = useRef(events);
  // Track joined conversations (like Flutter does)
  const joinedConversationsRef = useRef<Set<string>>(new Set());

  // Keep events ref updated
  eventsRef.current = events;

  const MAX_RECONNECT_ATTEMPTS = 5;
  const MAX_TOKEN_RETRIES = 3;

  // Auto-join all conversations on connect (like Flutter _joinAllConversationsOnConnect)
  const joinAllConversationsOnConnect = useCallback(async () => {
    if (!socketRef.current?.connected || !tokenRef.current) return;

    try {
      const { token, chatServerUrl } = tokenRef.current;
      const response = await axios.get(`${chatServerUrl}/api/conversations`, {
        headers: { Authorization: `Bearer ${token}` },
      });

      const conversations = response.data.conversations || [];
      console.log('[Chat Socket.IO] Auto-joining', conversations.length, 'conversations');

      for (const conv of conversations) {
        const convId = conv.id?.toString();
        if (convId && !joinedConversationsRef.current.has(convId)) {
          joinedConversationsRef.current.add(convId);
          socketRef.current?.emit('conversation:join', { conversationId: convId });
        }
      }
    } catch (err) {
      console.error('[Chat Socket.IO] Failed to auto-join conversations:', err);
    }
  }, []);

  // Rejoin all previously joined conversations (for reconnect)
  const rejoinAllConversations = useCallback(() => {
    if (!socketRef.current?.connected) return;

    if (joinedConversationsRef.current.size > 0) {
      console.log('[Chat Socket.IO] Rejoining', joinedConversationsRef.current.size, 'conversations');
      for (const convId of joinedConversationsRef.current) {
        socketRef.current.emit('conversation:join', { conversationId: convId });
      }
    }
  }, []);

  // Fetch JWT token from backend
  const fetchToken = useCallback(async (): Promise<ChatToken | null> => {
    try {
      const response = await axios.get<ChatToken>('/zeiterfassung/chat/token');
      tokenRef.current = response.data;
      tokenRetryCountRef.current = 0; // Reset on success
      return response.data;
    } catch (err) {
      console.error('[Chat Socket.IO] Failed to fetch token:', err);
      tokenRetryCountRef.current++;

      if (tokenRetryCountRef.current >= MAX_TOKEN_RETRIES) {
        setError('Authentication failed - max retries exceeded');
      } else {
        setError('Failed to authenticate with chat server');
      }
      return null;
    }
  }, []);

  // Handle incoming message event (message:new)
  const handleMessageEvent = useCallback((data: any) => {
    console.log('[Chat Socket.IO] message:new received:', data);
    const message: ChatMessage = {
      id: data.id || data._id || `msg_${Date.now()}`,
      conversationId: data.conversationId,
      senderId: parseInt(data.senderId || data.sender?.externalUserId, 10),
      content: data.content,
      timestamp: data.createdAt || data.timestamp || new Date().toISOString(),
      status: 'delivered',
      type: data.type || 'text',
    };
    eventsRef.current.onMessage?.(message);
  }, []);

  // Handle message sent confirmation (message:sent)
  const handleMessageSent = useCallback((data: any) => {
    console.log('[Chat Socket.IO] message:sent confirmation:', data);
    // Could be used to update message status from 'sending' to 'sent'
    if (data.message) {
      const message: ChatMessage = {
        id: data.message.id || data.message._id,
        conversationId: data.message.conversationId,
        senderId: parseInt(data.message.senderId || data.message.sender?.externalUserId, 10),
        content: data.message.content,
        timestamp: data.message.createdAt || new Date().toISOString(),
        status: 'sent',
        type: data.message.type || 'text',
      };
      eventsRef.current.onMessage?.(message);
    }
  }, []);

  // Handle typing event (message:typing)
  const handleTypingEvent = useCallback((data: any) => {
    console.log('[Chat Socket.IO] message:typing event:', data);
    eventsRef.current.onTyping?.(
      data.conversationId,
      parseInt(data.externalUserId || data.userId, 10),
      data.isTyping ?? true
    );
  }, []);

  // Handle presence update (presence:update)
  const handlePresenceEvent = useCallback((data: any) => {
    console.log('[Chat Socket.IO] presence:update event:', data);
    const userId = parseInt(data.externalUserId || data.userId, 10);
    const isOnline = data.status === 'online';

    if (isOnline) {
      setOnlineUsers(prev => new Set(prev).add(userId));
    } else {
      setOnlineUsers(prev => {
        const next = new Set(prev);
        next.delete(userId);
        return next;
      });
    }
    eventsRef.current.onOnlineStatus?.(userId, isOnline);
  }, []);

  // Handle read receipt (message:read)
  const handleReadEvent = useCallback((data: any) => {
    console.log('[Chat Socket.IO] message:read event:', data);
    eventsRef.current.onConversationUpdate?.(data.conversationId, {
      type: 'read',
      userId: parseInt(data.externalUserId || data.userId, 10),
      messageId: data.messageId,
      timestamp: data.timestamp,
    });
  }, []);

  // Connect to Socket.IO server
  const connect = useCallback(async () => {
    if (socketRef.current?.connected) {
      return; // Already connected
    }

    // Guard against exhausted token retries
    if (tokenRetryCountRef.current >= MAX_TOKEN_RETRIES) {
      setError('Cannot connect - authentication retries exhausted');
      setConnecting(false);
      return;
    }

    setConnecting(true);
    setError(null);

    // Get token if we don't have one
    let token = tokenRef.current;
    if (!token) {
      token = await fetchToken();
      if (!token) {
        setConnecting(false);
        return;
      }
    }

    try {
      console.log('[Chat Socket.IO] Connecting to:', token.chatServerUrl);

      // Create Socket.IO connection with auth token
      const socket = io(token.chatServerUrl, {
        auth: {
          token: token.token,
        },
        transports: ['websocket'],
        reconnection: true,
        reconnectionAttempts: MAX_RECONNECT_ATTEMPTS,
        reconnectionDelay: 1000,
        reconnectionDelayMax: 5000,
      });

      // Connection events
      socket.on('connect', async () => {
        console.log('[Chat Socket.IO] Connected! Socket ID:', socket.id);
        setConnected(true);
        setConnecting(false);
        setError(null);
        reconnectAttemptsRef.current = 0;

        // Rejoin all previously joined conversations (for reconnect scenario)
        rejoinAllConversations();
        // Then auto-join all conversations from server
        await joinAllConversationsOnConnect();
      });

      socket.on('disconnect', (reason) => {
        console.log('[Chat Socket.IO] Disconnected:', reason);
        setConnected(false);

        if (reason === 'io server disconnect') {
          tokenRef.current = null; // Force token refresh
        }
      });

      socket.on('connect_error', (err) => {
        console.error('[Chat Socket.IO] Connection error:', err.message);
        setError(`Connection error: ${err.message}`);
        setConnecting(false);

        if (err.message.includes('auth') || err.message.includes('token')) {
          tokenRef.current = null;
        }
      });

      // Server -> Client Events (from documentation)
      socket.on('message:new', handleMessageEvent);
      socket.on('message:sent', handleMessageSent);
      socket.on('message:typing', handleTypingEvent);
      socket.on('message:read', handleReadEvent);
      socket.on('presence:update', handlePresenceEvent);
      socket.on('conversation:new', (data) => {
        console.log('[Chat Socket.IO] conversation:new:', data);
        eventsRef.current.onConversationUpdate?.(data.id, { type: 'new', ...data });
      });

      socketRef.current = socket;
    } catch (err) {
      console.error('[Chat Socket.IO] Failed to connect:', err);
      setError('Failed to connect to chat server');
      setConnecting(false);
    }
  }, [fetchToken, handleMessageEvent, handleMessageSent, handleTypingEvent, handleReadEvent, handlePresenceEvent, rejoinAllConversations, joinAllConversationsOnConnect]);

  // Join a conversation room (manual join like Flutter joinConversation)
  const joinConversation = useCallback((conversationId: string) => {
    if (!socketRef.current?.connected) {
      return;
    }
    // Track in joined set (like Flutter does)
    joinedConversationsRef.current.add(conversationId);
    console.log('[Chat Socket.IO] Joining conversation:', conversationId);
    socketRef.current.emit('conversation:join', { conversationId });
  }, []);

  // Leave a conversation room
  const leaveConversation = useCallback((conversationId: string) => {
    if (!socketRef.current?.connected) {
      return;
    }
    socketRef.current.emit('conversation:leave', { conversationId });
  }, []);

  // Send a chat message (message:send)
  const sendMessage = useCallback((conversationId: string, content: string) => {
    if (!socketRef.current?.connected) {
      console.error('[Chat Socket.IO] Cannot send message: not connected');
      return;
    }

    const clientMessageId = `client_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
    console.log('[Chat Socket.IO] Sending message:send to:', conversationId);

    socketRef.current.emit('message:send', {
      conversationId,
      content,
      type: 'TEXT',
      clientMessageId,
    });
  }, []);

  // Create direct conversation and send first message (conversation:createAndSend)
  // Returns a Promise with the server response containing conversation and message
  const createAndSendMessage = useCallback((
    recipientUserId: number,
    content: string
  ): Promise<{ success: boolean; conversation?: any; message?: any; error?: string }> => {
    return new Promise((resolve) => {
      if (!socketRef.current?.connected) {
        console.error('[Chat Socket.IO] Cannot create conversation: not connected');
        resolve({ success: false, error: 'Not connected' });
        return;
      }

      const clientMessageId = `client_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
      console.log('[Chat Socket.IO] Creating conversation and sending to user:', recipientUserId);

      socketRef.current.emit(
        'conversation:createAndSend',
        {
          recipientExternalId: String(recipientUserId),
          content,
          type: 'TEXT',
          clientMessageId,
        },
        (response: { success: boolean; conversation?: any; message?: any; error?: string }) => {
          // This callback receives the server's return value
          console.log('[Chat Socket.IO] conversation:createAndSend response:', response);

          // Auto-join the new conversation
          if (response.success && response.conversation?.id) {
            const convId = response.conversation.id.toString();
            if (!joinedConversationsRef.current.has(convId)) {
              joinedConversationsRef.current.add(convId);
              socketRef.current?.emit('conversation:join', { conversationId: convId });
            }
          }

          resolve(response);
        }
      );
    });
  }, []);

  // Send typing indicator (message:typing)
  const sendTyping = useCallback((conversationId: string, isTyping: boolean) => {
    if (!socketRef.current?.connected) {
      return;
    }

    socketRef.current.emit('message:typing', {
      conversationId,
      isTyping,
    });
  }, []);

  // Mark messages as read (message:read)
  const markAsRead = useCallback((conversationId: string, messageId?: string) => {
    if (!socketRef.current?.connected) {
      return;
    }

    socketRef.current.emit('message:read', {
      conversationId,
      messageId,
    });
  }, []);

  // Get presence for specific users (presence:get)
  const getPresence = useCallback((userIds: number[]) => {
    if (!socketRef.current?.connected) {
      return;
    }

    socketRef.current.emit('presence:get', { userIds });
  }, []);

  // Disconnect Socket.IO
  const disconnect = useCallback(() => {
    if (socketRef.current) {
      socketRef.current.disconnect();
      socketRef.current = null;
    }

    setConnected(false);
    setConnecting(false);
  }, []);

  // Auto-connect on mount
  useEffect(() => {
    connect();

    return () => {
      disconnect();
    };
  }, [connect, disconnect]);

  return {
    connected,
    connecting,
    error,
    sendMessage,
    createAndSendMessage,
    sendTyping,
    markAsRead,
    onlineUsers,
    disconnect,
    joinConversation,
    leaveConversation,
    getPresence,
    chatToken: tokenRef.current?.token ?? null,
    chatServerUrl: tokenRef.current?.chatServerUrl ?? null,
    socket: socketRef.current,
  } as UseChatWebSocketReturn;
};

export default useChatWebSocket;
