import { io, Socket } from "socket.io-client";

/**
 * Socket event types for type safety
 */
export interface SocketEvents {
  // Incoming events (from server)
  new_message: (message: any) => void;
  message_read: (data: {
    messageId: string;
    readAt: string;
    readBy: string;
  }) => void;
  user_typing: (data: {
    userId: string;
    firstName: string;
    isTyping: boolean;
  }) => void;
  conversation_joined: (data: {
    conversationRoom: string;
    otherUserId: string;
  }) => void;

  // Connection events
  connect: () => void;
  disconnect: (reason: string) => void;
  connect_error: (error: Error) => void;
  error: (error: Error) => void;
}

/**
 * Socket service for managing WebSocket connection
 * Implements singleton pattern to ensure only one socket instance
 */
class SocketService {
  private socket: Socket | null = null;
  private reconnectAttempts = 0;
  private maxReconnectAttempts = 5;
  private connectionListeners: Array<(connected: boolean) => void> = [];

  /**
   * Get the socket instance URL based on environment
   */
  private getSocketUrl(): string {
    // Use environment variable or default to localhost:5001 (matches backend)
    return process.env.REACT_APP_BASE_URL || "http://localhost:5001";
  }

  /**
   * Initialize and connect to the socket server
   * Authentication happens via cookies (sent automatically)
   */
  connect(): Socket {
    if (this.socket?.connected) {
      return this.socket;
    }

    this.socket = io(this.getSocketUrl(), {
      withCredentials: true, // Send cookies for authentication
      transports: ["websocket", "polling"], // Try WebSocket first, fallback to polling
      path: "/socket.io", // Explicit Socket.io path
      reconnection: true,
      reconnectionAttempts: this.maxReconnectAttempts,
      reconnectionDelay: 1000,
      reconnectionDelayMax: 5000,
      timeout: 20000,
    });

    // Connection event handlers
    this.socket.on("connect", () => {
      this.reconnectAttempts = 0;
      this.notifyConnectionListeners(true);
    });

    this.socket.on("disconnect", (reason) => {
      this.notifyConnectionListeners(false);
    });

    this.socket.on("connect_error", (error) => {
      this.reconnectAttempts++;

      if (this.reconnectAttempts >= this.maxReconnectAttempts) {
        console.error("[Socket] Max reconnection attempts reached");
        this.disconnect();
      }
    });

    return this.socket;
  }

  /**
   * Disconnect from the socket server
   */
  disconnect(): void {
    if (this.socket) {
      this.socket.disconnect();
      this.socket = null;
      this.notifyConnectionListeners(false);
    }
  }

  /**
   * Get the current socket instance
   */
  getSocket(): Socket | null {
    return this.socket;
  }

  /**
   * Check if socket is connected
   */
  isConnected(): boolean {
    return this.socket?.connected ?? false;
  }

  /**
   * Join a conversation room
   */
  joinConversation(otherUserId: string): void {
    if (!this.socket?.connected) {
      console.warn("[Socket] Not connected, cannot join conversation");
      return;
    }

    this.socket.emit("join_conversation", { otherUserId });
  }

  /**
   * Leave a conversation room
   */
  leaveConversation(otherUserId: string): void {
    if (!this.socket?.connected) {
      return;
    }

    this.socket.emit("leave_conversation", { otherUserId });
  }

  /**
   * Emit typing start event
   */
  emitTypingStart(receiverId: string): void {
    if (!this.socket?.connected) {
      return;
    }

    this.socket.emit("typing_start", { receiverId });
  }

  /**
   * Emit typing stop event
   */
  emitTypingStop(receiverId: string): void {
    if (!this.socket?.connected) {
      return;
    }

    this.socket.emit("typing_stop", { receiverId });
  }

  /**
   * Emit mark message as read event
   */
  emitMarkMessageRead(messageId: string, senderId: string): void {
    if (!this.socket?.connected) {
      return;
    }

    this.socket.emit("mark_message_read", { messageId, senderId });
  }

  /**
   * Listen for new messages
   */
  onNewMessage(callback: (message: any) => void): () => void {
    if (!this.socket) {
      console.warn("[Socket] Socket not initialized");
      return () => {};
    }

    this.socket.on("new_message", callback);

    // Return cleanup function
    return () => {
      this.socket?.off("new_message", callback);
    };
  }

  /**
   * Listen for message read events
   */
  onMessageRead(
    callback: (data: {
      messageId: string;
      readAt: string;
      readBy: string;
    }) => void
  ): () => void {
    if (!this.socket) {
      console.warn("[Socket] Socket not initialized");
      return () => {};
    }

    this.socket.on("message_read", callback);

    // Return cleanup function
    return () => {
      this.socket?.off("message_read", callback);
    };
  }

  /**
   * Listen for typing indicators
   */
  onUserTyping(
    callback: (data: {
      userId: string;
      firstName: string;
      isTyping: boolean;
    }) => void
  ): () => void {
    if (!this.socket) {
      console.warn("[Socket] Socket not initialized");
      return () => {};
    }

    this.socket.on("user_typing", callback);

    // Return cleanup function
    return () => {
      this.socket?.off("user_typing", callback);
    };
  }

  /**
   * Listen for conversation joined event
   */
  onConversationJoined(
    callback: (data: { conversationRoom: string; otherUserId: string }) => void
  ): () => void {
    if (!this.socket) {
      console.warn("[Socket] Socket not initialized");
      return () => {};
    }

    this.socket.on("conversation_joined", callback);

    // Return cleanup function
    return () => {
      this.socket?.off("conversation_joined", callback);
    };
  }

  /**
   * Add connection status listener
   */
  addConnectionListener(callback: (connected: boolean) => void): () => void {
    this.connectionListeners.push(callback);

    // Immediately notify of current status
    callback(this.isConnected());

    // Return cleanup function
    return () => {
      this.connectionListeners = this.connectionListeners.filter(
        (cb) => cb !== callback
      );
    };
  }

  /**
   * Notify all connection listeners of status change
   */
  private notifyConnectionListeners(connected: boolean): void {
    this.connectionListeners.forEach((callback) => {
      try {
        callback(connected);
      } catch (error) {
        console.error("[Socket] Error in connection listener", error);
      }
    });
  }

  /**
   * Remove all event listeners
   */
  removeAllListeners(): void {
    if (this.socket) {
      this.socket.removeAllListeners();
    }
  }
}

// Export singleton instance
export const socketService = new SocketService();
export default socketService;
