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

/**
 * WebSocket Client for Real-Time Family Sync
 *
 * Features:
 * - Automatic connection/reconnection with exponential backoff
 * - Family room management
 * - Presence indicators
 * - Activity updates (create/update/delete)
 * - Connection status monitoring
 */

export interface WebSocketConfig {
  url: string;
  token: string;
}

export interface PresenceUpdate {
  onlineUsers: string[];
  count: number;
}

export type WebSocketEventCallback<T = any> = (data: T) => void;

class WebSocketService {
  private socket: Socket | null = null;
  private config: WebSocketConfig | null = null;
  private reconnectAttempts = 0;
  private maxReconnectAttempts = 10;
  private reconnectDelay = 1000; // Start with 1 second
  private maxReconnectDelay = 30000; // Max 30 seconds
  private currentFamilyId: string | null = null;
  private eventListeners: Map<string, Set<WebSocketEventCallback>> = new Map();
  private connectionStatusListeners: Set<(connected: boolean) => void> = new Set();

  constructor() {}

  /**
   * Connect to WebSocket server
   */
  connect(config: WebSocketConfig): void {
    if (this.socket?.connected) {
      console.log('[WebSocket] Already connected');
      return;
    }

    console.log('[WebSocket] Connecting to:', config.url);
    console.log('[WebSocket] Token length:', config.token?.length || 0);
    this.config = config;

    this.socket = io(config.url, {
      auth: {
        token: config.token,
      },
      transports: ['websocket', 'polling'],
      reconnection: true,
      reconnectionAttempts: this.maxReconnectAttempts,
      reconnectionDelay: this.reconnectDelay,
      reconnectionDelayMax: this.maxReconnectDelay,
      secure: config.url.startsWith('https'),
      rejectUnauthorized: false, // For development with self-signed certs
      path: '/socket.io/', // Explicit path for Socket.IO
      withCredentials: true,
      extraHeaders: {
        'Authorization': `Bearer ${config.token}`,
      },
    });

    this.setupEventHandlers();
  }

  /**
   * Disconnect from WebSocket server
   */
  disconnect(): void {
    if (this.socket) {
      console.log('[WebSocket] Disconnecting...');
      this.socket.disconnect();
      this.socket = null;
      this.currentFamilyId = null;
      this.notifyConnectionStatus(false);
    }
  }

  /**
   * Join a family room to receive real-time updates
   */
  joinFamily(familyId: string): void {
    if (!this.socket?.connected) {
      console.warn('[WebSocket] Not connected, cannot join family');
      return;
    }

    console.log('[WebSocket] Joining family room:', familyId);
    this.currentFamilyId = familyId;
    this.socket.emit('joinFamily', { familyId });
  }

  /**
   * Leave current family room
   */
  leaveFamily(): void {
    if (!this.socket?.connected || !this.currentFamilyId) {
      return;
    }

    console.log('[WebSocket] Leaving family room:', this.currentFamilyId);
    this.socket.emit('leaveFamily');
    this.currentFamilyId = null;
  }

  /**
   * Add event listener
   */
  on<T = any>(event: string, callback: WebSocketEventCallback<T>): () => void {
    if (!this.eventListeners.has(event)) {
      this.eventListeners.set(event, new Set());
    }
    this.eventListeners.get(event)!.add(callback);

    // Return unsubscribe function
    return () => {
      this.off(event, callback);
    };
  }

  /**
   * Remove event listener
   */
  off(event: string, callback: WebSocketEventCallback): void {
    const listeners = this.eventListeners.get(event);
    if (listeners) {
      listeners.delete(callback);
    }
  }

  /**
   * Add connection status listener
   */
  onConnectionStatusChange(callback: (connected: boolean) => void): () => void {
    this.connectionStatusListeners.add(callback);
    // Immediately call with current status
    callback(this.isConnected());

    // Return unsubscribe function
    return () => {
      this.connectionStatusListeners.delete(callback);
    };
  }

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

  /**
   * Get current family ID
   */
  getCurrentFamilyId(): string | null {
    return this.currentFamilyId;
  }

  /**
   * Setup socket event handlers
   */
  private setupEventHandlers(): void {
    if (!this.socket) return;

    // Connection events
    this.socket.on('connect', () => {
      console.log('[WebSocket] Connected');
      this.reconnectAttempts = 0;
      this.reconnectDelay = 1000;
      this.notifyConnectionStatus(true);

      // Rejoin family if we were in one
      if (this.currentFamilyId) {
        console.log('[WebSocket] Rejoining family after reconnect:', this.currentFamilyId);
        this.socket!.emit('joinFamily', { familyId: this.currentFamilyId });
      }
    });

    this.socket.on('disconnect', (reason) => {
      console.log('[WebSocket] Disconnected:', reason);
      this.notifyConnectionStatus(false);

      // Attempt reconnection with exponential backoff
      if (reason === 'io server disconnect') {
        // Server initiated disconnect, try to reconnect
        this.attemptReconnect();
      }
    });

    this.socket.on('connect_error', (error) => {
      console.error('[WebSocket] Connection error:', error.message);
      console.error('[WebSocket] Error details:', error);
      console.error('[WebSocket] Connection URL:', this.config?.url);
      this.attemptReconnect();
    });

    this.socket.on('error', (error) => {
      console.error('[WebSocket] Error:', error);
      this.emitToListeners('error', error);
    });

    // Custom events
    this.socket.on('connected', (data) => {
      console.log('[WebSocket] Server acknowledged connection:', data);
    });

    this.socket.on('familyJoined', (data) => {
      console.log('[WebSocket] Family joined:', data);
      this.emitToListeners('familyJoined', data);
    });

    this.socket.on('familyLeft', (data) => {
      console.log('[WebSocket] Family left:', data);
      this.emitToListeners('familyLeft', data);
    });

    // Presence updates
    this.socket.on('presenceUpdate', (data: PresenceUpdate) => {
      console.log('[WebSocket] Presence update:', data);
      this.emitToListeners('presenceUpdate', data);
    });

    // Activity events
    this.socket.on('activityCreated', (activity) => {
      console.log('[WebSocket] Activity created:', activity);
      this.emitToListeners('activityCreated', activity);
    });

    this.socket.on('activityUpdated', (activity) => {
      console.log('[WebSocket] Activity updated:', activity);
      this.emitToListeners('activityUpdated', activity);
    });

    this.socket.on('activityDeleted', (data) => {
      console.log('[WebSocket] Activity deleted:', data);
      this.emitToListeners('activityDeleted', data);
    });

    // Child events
    this.socket.on('childAdded', (child) => {
      console.log('[WebSocket] Child added:', child);
      this.emitToListeners('childAdded', child);
    });

    this.socket.on('childUpdated', (child) => {
      console.log('[WebSocket] Child updated:', child);
      this.emitToListeners('childUpdated', child);
    });

    this.socket.on('childDeleted', (data) => {
      console.log('[WebSocket] Child deleted:', data);
      this.emitToListeners('childDeleted', data);
    });

    // Member events
    this.socket.on('memberAdded', (member) => {
      console.log('[WebSocket] Member added:', member);
      this.emitToListeners('memberAdded', member);
    });

    this.socket.on('memberUpdated', (member) => {
      console.log('[WebSocket] Member updated:', member);
      this.emitToListeners('memberUpdated', member);
    });

    this.socket.on('memberRemoved', (data) => {
      console.log('[WebSocket] Member removed:', data);
      this.emitToListeners('memberRemoved', data);
    });
  }

  /**
   * Attempt reconnection with exponential backoff
   */
  private attemptReconnect(): void {
    if (this.reconnectAttempts >= this.maxReconnectAttempts) {
      console.error('[WebSocket] Max reconnection attempts reached');
      return;
    }

    this.reconnectAttempts++;
    const delay = Math.min(
      this.reconnectDelay * Math.pow(2, this.reconnectAttempts - 1),
      this.maxReconnectDelay
    );

    console.log(
      `[WebSocket] Reconnecting in ${delay}ms (attempt ${this.reconnectAttempts}/${this.maxReconnectAttempts})`
    );

    setTimeout(() => {
      if (this.config && !this.socket?.connected) {
        this.connect(this.config);
      }
    }, delay);
  }

  /**
   * Emit event to all registered listeners
   */
  private emitToListeners<T = any>(event: string, data: T): void {
    const listeners = this.eventListeners.get(event);
    if (listeners) {
      listeners.forEach((callback) => {
        try {
          callback(data);
        } catch (error) {
          console.error(`[WebSocket] Error in event listener for '${event}':`, error);
        }
      });
    }
  }

  /**
   * Notify connection status listeners
   */
  private notifyConnectionStatus(connected: boolean): void {
    this.connectionStatusListeners.forEach((callback) => {
      try {
        callback(connected);
      } catch (error) {
        console.error('[WebSocket] Error in connection status listener:', error);
      }
    });
  }
}

// Export singleton instance
export const websocketService = new WebSocketService();
