/**
 * Socket.io Client for LocalGreenChain
 *
 * Provides a client-side wrapper for Socket.io connections.
 */

import { io, Socket } from 'socket.io-client';
import type {
  ClientToServerEvents,
  ServerToClientEvents,
  ConnectionStatus,
  RoomType,
  TransparencyEventType,
  TransparencyEvent,
  ConnectionMetrics,
} from './types';

type TypedSocket = Socket<ServerToClientEvents, ClientToServerEvents>;

/**
 * Client configuration options
 */
export interface SocketClientConfig {
  url?: string;
  path?: string;
  autoConnect?: boolean;
  auth?: {
    userId?: string;
    token?: string;
    sessionId?: string;
  };
  reconnection?: boolean;
  reconnectionAttempts?: number;
  reconnectionDelay?: number;
}

/**
 * Event listener types
 */
export type EventListener = (event: TransparencyEvent) => void;
export type StatusListener = (status: ConnectionStatus) => void;
export type ErrorListener = (error: { code: string; message: string }) => void;

/**
 * Socket.io client wrapper
 */
class RealtimeSocketClient {
  private socket: TypedSocket | null = null;
  private config: SocketClientConfig;
  private status: ConnectionStatus = 'disconnected';
  private eventListeners: Set<EventListener> = new Set();
  private statusListeners: Set<StatusListener> = new Set();
  private errorListeners: Set<ErrorListener> = new Set();
  private metrics: ConnectionMetrics;
  private pingInterval: NodeJS.Timeout | null = null;

  constructor(config: SocketClientConfig = {}) {
    this.config = {
      url: typeof window !== 'undefined' ? window.location.origin : '',
      path: '/api/socket',
      autoConnect: true,
      reconnection: true,
      reconnectionAttempts: 10,
      reconnectionDelay: 1000,
      ...config,
    };

    this.metrics = {
      status: 'disconnected',
      eventsReceived: 0,
      reconnectAttempts: 0,
      rooms: [],
    };
  }

  /**
   * Connect to the server
   */
  connect(): void {
    if (this.socket?.connected) {
      return;
    }

    this.updateStatus('connecting');

    this.socket = io(this.config.url!, {
      path: this.config.path,
      autoConnect: this.config.autoConnect,
      auth: this.config.auth,
      reconnection: this.config.reconnection,
      reconnectionAttempts: this.config.reconnectionAttempts,
      reconnectionDelay: this.config.reconnectionDelay,
      transports: ['websocket', 'polling'],
    });

    this.setupEventHandlers();
  }

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

    // Connection events
    this.socket.on('connect', () => {
      this.updateStatus('connected');
      this.metrics.connectedAt = Date.now();
      this.metrics.reconnectAttempts = 0;
      this.startPingInterval();
    });

    this.socket.on('disconnect', () => {
      this.updateStatus('disconnected');
      this.stopPingInterval();
    });

    this.socket.on('connect_error', () => {
      this.updateStatus('error');
      this.metrics.reconnectAttempts++;
    });

    // Server events
    this.socket.on('connection:established', (data) => {
      console.log('[SocketClient] Connected:', data.socketId);
    });

    this.socket.on('connection:error', (error) => {
      this.errorListeners.forEach((listener) => listener(error));
    });

    // Real-time events
    this.socket.on('event', (event) => {
      this.metrics.eventsReceived++;
      this.metrics.lastEventAt = Date.now();
      this.eventListeners.forEach((listener) => listener(event));
    });

    this.socket.on('event:batch', (events) => {
      this.metrics.eventsReceived += events.length;
      this.metrics.lastEventAt = Date.now();
      events.forEach((event) => {
        this.eventListeners.forEach((listener) => listener(event));
      });
    });

    // Room events
    this.socket.on('room:joined', (room) => {
      if (!this.metrics.rooms.includes(room)) {
        this.metrics.rooms.push(room);
      }
    });

    this.socket.on('room:left', (room) => {
      this.metrics.rooms = this.metrics.rooms.filter((r) => r !== room);
    });

    // System events
    this.socket.on('system:message', (message) => {
      console.log(`[SocketClient] System ${message.type}: ${message.text}`);
    });

    this.socket.on('system:heartbeat', () => {
      // Heartbeat received - connection is alive
    });

    // Reconnection events
    this.socket.io.on('reconnect_attempt', () => {
      this.updateStatus('reconnecting');
      this.metrics.reconnectAttempts++;
    });

    this.socket.io.on('reconnect', () => {
      this.updateStatus('connected');
    });
  }

  /**
   * Start ping interval for latency measurement
   */
  private startPingInterval(): void {
    this.stopPingInterval();

    this.pingInterval = setInterval(() => {
      if (this.socket?.connected) {
        const start = Date.now();
        this.socket.emit('ping', (serverTime) => {
          this.metrics.latency = Date.now() - start;
        });
      }
    }, 10000); // Every 10 seconds
  }

  /**
   * Stop ping interval
   */
  private stopPingInterval(): void {
    if (this.pingInterval) {
      clearInterval(this.pingInterval);
      this.pingInterval = null;
    }
  }

  /**
   * Update connection status and notify listeners
   */
  private updateStatus(status: ConnectionStatus): void {
    this.status = status;
    this.metrics.status = status;
    this.statusListeners.forEach((listener) => listener(status));
  }

  /**
   * Disconnect from the server
   */
  disconnect(): void {
    this.stopPingInterval();
    if (this.socket) {
      this.socket.disconnect();
      this.socket = null;
    }
    this.updateStatus('disconnected');
  }

  /**
   * Join a room
   */
  joinRoom(room: RoomType): Promise<boolean> {
    return new Promise((resolve) => {
      if (!this.socket?.connected) {
        resolve(false);
        return;
      }

      this.socket.emit('room:join', room, (success) => {
        resolve(success);
      });
    });
  }

  /**
   * Leave a room
   */
  leaveRoom(room: RoomType): Promise<boolean> {
    return new Promise((resolve) => {
      if (!this.socket?.connected) {
        resolve(false);
        return;
      }

      this.socket.emit('room:leave', room, (success) => {
        resolve(success);
      });
    });
  }

  /**
   * Subscribe to specific event types
   */
  subscribeToTypes(types: TransparencyEventType[]): Promise<boolean> {
    return new Promise((resolve) => {
      if (!this.socket?.connected) {
        resolve(false);
        return;
      }

      this.socket.emit('subscribe:types', types, (success) => {
        resolve(success);
      });
    });
  }

  /**
   * Unsubscribe from specific event types
   */
  unsubscribeFromTypes(types: TransparencyEventType[]): Promise<boolean> {
    return new Promise((resolve) => {
      if (!this.socket?.connected) {
        resolve(false);
        return;
      }

      this.socket.emit('unsubscribe:types', types, (success) => {
        resolve(success);
      });
    });
  }

  /**
   * Get recent events
   */
  getRecentEvents(limit: number = 50): Promise<TransparencyEvent[]> {
    return new Promise((resolve) => {
      if (!this.socket?.connected) {
        resolve([]);
        return;
      }

      this.socket.emit('events:recent', limit, (events) => {
        resolve(events);
      });
    });
  }

  /**
   * Add an event listener
   */
  onEvent(listener: EventListener): () => void {
    this.eventListeners.add(listener);
    return () => this.eventListeners.delete(listener);
  }

  /**
   * Add a status listener
   */
  onStatusChange(listener: StatusListener): () => void {
    this.statusListeners.add(listener);
    return () => this.statusListeners.delete(listener);
  }

  /**
   * Add an error listener
   */
  onError(listener: ErrorListener): () => void {
    this.errorListeners.add(listener);
    return () => this.errorListeners.delete(listener);
  }

  /**
   * Get current connection status
   */
  getStatus(): ConnectionStatus {
    return this.status;
  }

  /**
   * Get connection metrics
   */
  getMetrics(): ConnectionMetrics {
    return { ...this.metrics };
  }

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

  /**
   * Get socket ID
   */
  getSocketId(): string | undefined {
    return this.socket?.id;
  }
}

// Singleton instance for client-side use
let clientInstance: RealtimeSocketClient | null = null;

/**
 * Get the singleton socket client instance
 */
export function getSocketClient(config?: SocketClientConfig): RealtimeSocketClient {
  if (!clientInstance) {
    clientInstance = new RealtimeSocketClient(config);
  }
  return clientInstance;
}

/**
 * Create a new socket client instance
 */
export function createSocketClient(config?: SocketClientConfig): RealtimeSocketClient {
  return new RealtimeSocketClient(config);
}

export { RealtimeSocketClient };
export default RealtimeSocketClient;
