import { io, Socket } from 'socket.io-client';
import type {
  Message,
  TypingIndicator,
  UserStatusChange,
  MessagesReadEvent,
  CreateMessageDto,
} from '@/types/messaging';

type MessageHandler = (message: Message) => void;
type TypingHandler = (data: TypingIndicator) => void;
type StatusHandler = (data: UserStatusChange) => void;
type ReadHandler = (data: MessagesReadEvent) => void;

class SocketService {
  private socket: Socket | null = null;
  private reconnectAttempts = 0;
  private maxReconnectAttempts = 5;
  private messageHandlers: Set<MessageHandler> = new Set();
  private typingStartHandlers: Set<TypingHandler> = new Set();
  private typingStopHandlers: Set<TypingHandler> = new Set();
  private statusHandlers: Set<StatusHandler> = new Set();
  private readHandlers: Set<ReadHandler> = new Set();
  private connectionHandlers: Set<(connected: boolean) => void> = new Set();
  private authErrorHandlers: Set<() => void> = new Set();
  private isConnecting = false;
  private currentToken: string | null = null;

  connect(token: string): Promise<void> {
    return new Promise((resolve, reject) => {
      this.currentToken = token;

      // Already connected with same token
      if (this.socket?.connected) {
        resolve();
        return;
      }

      // Already connecting
      if (this.isConnecting) {
        // Wait for existing connection attempt
        const checkConnection = setInterval(() => {
          if (this.socket?.connected) {
            clearInterval(checkConnection);
            resolve();
          }
        }, 100);
        setTimeout(() => {
          clearInterval(checkConnection);
          if (!this.socket?.connected) {
            reject(new Error('Connection timeout'));
          }
        }, 10000);
        return;
      }

      this.isConnecting = true;

      // Clean up existing socket if any (but not if it's connecting)
      if (this.socket && !this.socket.connected) {
        this.socket.removeAllListeners();
        this.socket.disconnect();
        this.socket = null;
      }

      // Extract base URL without /api/v1 path for Socket.io connection
      const apiUrl = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:4000';
      const baseUrl = apiUrl.replace(/\/api\/v1\/?$/, '');

      this.socket = io(baseUrl, {
        auth: { token },
        transports: ['websocket', 'polling'],
        reconnection: true,
        reconnectionAttempts: this.maxReconnectAttempts,
        reconnectionDelay: 1000,
        reconnectionDelayMax: 5000,
      });

      this.socket.on('connect', () => {
        console.log('Socket connected:', this.socket?.id);
        this.isConnecting = false;
        this.reconnectAttempts = 0;
        this.notifyConnectionHandlers(true);
        resolve();
      });

      this.socket.on('disconnect', (reason) => {
        console.log('Socket disconnected:', reason);
        this.isConnecting = false;
        this.notifyConnectionHandlers(false);

        // "io server disconnect" means the server rejected us (likely auth failure)
        if (reason === 'io server disconnect') {
          console.warn('Socket: Server disconnected us (likely token expired). Requesting token refresh...');
          this.authErrorHandlers.forEach((handler) => handler());
        }
      });

      this.socket.on('connect_error', (error) => {
        console.error('Socket connection error:', error.message);
        this.isConnecting = false;
        this.reconnectAttempts++;
        if (this.reconnectAttempts >= this.maxReconnectAttempts) {
          reject(new Error('Max reconnection attempts reached'));
        }
      });

      // Set up event listeners
      this.socket.on('new_message', (message: Message) => {
        this.messageHandlers.forEach((handler) => handler(message));
      });

      this.socket.on('typing_start', (data: TypingIndicator) => {
        this.typingStartHandlers.forEach((handler) => handler(data));
      });

      this.socket.on('typing_stop', (data: TypingIndicator) => {
        this.typingStopHandlers.forEach((handler) => handler(data));
      });

      this.socket.on('user_status_change', (data: UserStatusChange) => {
        this.statusHandlers.forEach((handler) => handler(data));
      });

      this.socket.on('messages_read', (data: MessagesReadEvent) => {
        this.readHandlers.forEach((handler) => handler(data));
      });
    });
  }

  // Reconnect with a fresh token (used when the previous token expired)
  reconnectWithToken(token: string): void {
    this.currentToken = token;
    if (this.socket) {
      // Update the auth token for reconnection
      this.socket.auth = { token };
      this.socket.connect();
    } else {
      this.connect(token).catch((err) => {
        console.error('Socket reconnect with new token failed:', err);
      });
    }
  }

  disconnect(): void {
    if (this.socket) {
      this.socket.disconnect();
      this.socket = null;
    }
    this.currentToken = null;
    this.isConnecting = false;
  }

  isConnected(): boolean {
    return this.socket?.connected || false;
  }

  // Room management
  joinConversation(conversationId: string): Promise<{ success: boolean; error?: string }> {
    return new Promise((resolve) => {
      if (!this.socket) {
        resolve({ success: false, error: 'Not connected' });
        return;
      }

      const timeout = setTimeout(() => {
        resolve({ success: false, error: 'Socket timeout' });
      }, 10000);

      this.socket.emit('join_conversation', { conversationId }, (response: { success: boolean; error?: string }) => {
        clearTimeout(timeout);
        resolve(response);
      });
    });
  }

  leaveConversation(conversationId: string): void {
    if (this.socket) {
      this.socket.emit('leave_conversation', { conversationId });
    }
  }

  // Messaging
  sendMessage(
    conversationId: string,
    message: CreateMessageDto
  ): Promise<{ success: boolean; message?: Message; error?: string }> {
    return new Promise((resolve) => {
      if (!this.socket) {
        resolve({ success: false, error: 'Not connected' });
        return;
      }

      // Timeout fallback in case acknowledgement never arrives
      const timeout = setTimeout(() => {
        resolve({ success: false, error: 'Socket timeout' });
      }, 10000);

      this.socket.emit(
        'send_message',
        { conversationId, message },
        (response: { success: boolean; message?: Message; error?: string }) => {
          clearTimeout(timeout);
          resolve(response);
        }
      );
    });
  }

  // Typing indicators
  startTyping(conversationId: string): void {
    if (this.socket) {
      this.socket.emit('typing_start', { conversationId });
    }
  }

  stopTyping(conversationId: string): void {
    if (this.socket) {
      this.socket.emit('typing_stop', { conversationId });
    }
  }

  // Mark messages as read
  markAsRead(conversationId: string): Promise<{ success: boolean; error?: string }> {
    return new Promise((resolve) => {
      if (!this.socket) {
        resolve({ success: false, error: 'Not connected' });
        return;
      }

      const timeout = setTimeout(() => {
        resolve({ success: false, error: 'Socket timeout' });
      }, 10000);

      this.socket.emit('mark_read', { conversationId }, (response: { success: boolean; error?: string }) => {
        clearTimeout(timeout);
        resolve(response);
      });
    });
  }

  // Event handlers registration
  onNewMessage(handler: MessageHandler): () => void {
    this.messageHandlers.add(handler);
    return () => this.messageHandlers.delete(handler);
  }

  onTypingStart(handler: TypingHandler): () => void {
    this.typingStartHandlers.add(handler);
    return () => this.typingStartHandlers.delete(handler);
  }

  onTypingStop(handler: TypingHandler): () => void {
    this.typingStopHandlers.add(handler);
    return () => this.typingStopHandlers.delete(handler);
  }

  onUserStatusChange(handler: StatusHandler): () => void {
    this.statusHandlers.add(handler);
    return () => this.statusHandlers.delete(handler);
  }

  onMessagesRead(handler: ReadHandler): () => void {
    this.readHandlers.add(handler);
    return () => this.readHandlers.delete(handler);
  }

  onConnectionChange(handler: (connected: boolean) => void): () => void {
    this.connectionHandlers.add(handler);
    return () => this.connectionHandlers.delete(handler);
  }

  onAuthError(handler: () => void): () => void {
    this.authErrorHandlers.add(handler);
    return () => this.authErrorHandlers.delete(handler);
  }

  private notifyConnectionHandlers(connected: boolean): void {
    this.connectionHandlers.forEach((handler) => handler(connected));
  }
}

// Singleton instance
export const socketService = new SocketService();
