/**
 * EN: WebSocket client for real-time messaging with automatic reconnection
 * VI: WebSocket client cho real-time messaging với tự động kết nối lại
 */

/**
 * EN: WebSocket connection state
 * VI: Trạng thái kết nối WebSocket
 */
export enum WebSocketState {
  /** EN: WebSocket is connecting / VI: WebSocket đang kết nối */
  CONNECTING = 'CONNECTING',
  /** EN: WebSocket is connected and ready / VI: WebSocket đã kết nối và sẵn sàng */
  CONNECTED = 'CONNECTED',
  /** EN: WebSocket connection is closed / VI: Kết nối WebSocket đã đóng */
  CLOSED = 'CLOSED',
  /** EN: WebSocket connection error / VI: Lỗi kết nối WebSocket */
  ERROR = 'ERROR',
  /** EN: WebSocket is reconnecting / VI: WebSocket đang kết nối lại */
  RECONNECTING = 'RECONNECTING',
}

/**
 * EN: WebSocket message event types
 * VI: Các loại sự kiện message WebSocket
 */
export enum WebSocketMessageType {
  /** EN: Text message / VI: Tin nhắn văn bản */
  MESSAGE = 'message',
  /** EN: Typing indicator / VI: Chỉ báo đang gõ */
  TYPING = 'typing',
  /** EN: Message read receipt / VI: Xác nhận đã đọc */
  READ = 'read',
  /** EN: User joined conversation / VI: Người dùng đã tham gia cuộc trò chuyện */
  USER_JOINED = 'user_joined',
  /** EN: User left conversation / VI: Người dùng đã rời cuộc trò chuyện */
  USER_LEFT = 'user_left',
  /** EN: Error message / VI: Tin nhắn lỗi */
  ERROR = 'error',
  /** EN: Connection acknowledgment / VI: Xác nhận kết nối */
  ACK = 'ack',
}

/**
 * EN: WebSocket message payload interface
 * VI: Interface payload tin nhắn WebSocket
 */
export interface WebSocketMessage {
  /** EN: Message type / VI: Loại tin nhắn */
  type: WebSocketMessageType;
  /** EN: Message payload / VI: Payload tin nhắn */
  data?: Record<string, unknown>;
  /** EN: Conversation ID / VI: ID cuộc trò chuyện */
  conversationId?: string;
  /** EN: Message ID / VI: ID tin nhắn */
  messageId?: string;
  /** EN: Timestamp / VI: Timestamp */
  timestamp?: string;
}

/**
 * EN: WebSocket event callbacks
 * VI: Callbacks sự kiện WebSocket
 */
export interface WebSocketCallbacks {
  /** EN: Called when WebSocket connection opens / VI: Được gọi khi kết nối WebSocket mở */
  onOpen?: () => void;
  /** EN: Called when WebSocket connection closes / VI: Được gọi khi kết nối WebSocket đóng */
  onClose?: () => void;
  /** EN: Called when WebSocket error occurs / VI: Được gọi khi xảy ra lỗi WebSocket */
  onError?: (error: Event) => void;
  /** EN: Called when message is received / VI: Được gọi khi nhận được tin nhắn */
  onMessage?: (message: WebSocketMessage) => void;
  /** EN: Called when connection state changes / VI: Được gọi khi trạng thái kết nối thay đổi */
  onStateChange?: (state: WebSocketState) => void;
}

/**
 * EN: WebSocket client configuration options
 * VI: Tùy chọn cấu hình WebSocket client
 */
export interface WebSocketClientConfig {
  /** EN: WebSocket server URL / VI: URL server WebSocket */
  url: string;
  /** EN: Enable automatic reconnection / VI: Bật tự động kết nối lại */
  autoReconnect?: boolean;
  /** EN: Maximum reconnection attempts (0 = unlimited) / VI: Số lần thử kết nối lại tối đa (0 = không giới hạn) */
  maxReconnectAttempts?: number;
  /** EN: Initial reconnection delay in milliseconds / VI: Độ trễ kết nối lại ban đầu tính bằng milliseconds */
  reconnectDelay?: number;
  /** EN: Maximum reconnection delay in milliseconds / VI: Độ trễ kết nối lại tối đa tính bằng milliseconds */
  maxReconnectDelay?: number;
  /** EN: Reconnection delay multiplier (exponential backoff) / VI: Hệ số nhân độ trễ kết nối lại (exponential backoff) */
  reconnectDelayMultiplier?: number;
  /** EN: Event callbacks / VI: Callbacks sự kiện */
  callbacks?: WebSocketCallbacks;
}

/**
 * EN: WebSocket client for real-time messaging
 * VI: WebSocket client cho real-time messaging
 *
 * Features:
 * - Automatic reconnection with exponential backoff
 * - Authentication via JWT token
 * - Event-based message handling
 * - Connection state management
 * - Heartbeat/ping-pong support (optional)
 */
export class WebSocketClient {
  /** EN: WebSocket instance / VI: Instance WebSocket */
  private ws: WebSocket | null = null;
  /** EN: Current connection state / VI: Trạng thái kết nối hiện tại */
  private state: WebSocketState = WebSocketState.CLOSED;
  /** EN: Configuration options / VI: Tùy chọn cấu hình */
  private config: Required<Omit<WebSocketClientConfig, 'callbacks'>> & { callbacks?: WebSocketCallbacks };
  /** EN: Reconnection attempt counter / VI: Bộ đếm số lần thử kết nối lại */
  private reconnectAttempts = 0;
  /** EN: Current reconnection delay / VI: Độ trễ kết nối lại hiện tại */
  private reconnectDelay = 0;
  /** EN: Reconnection timeout ID / VI: ID timeout kết nối lại */
  private reconnectTimeoutId: NodeJS.Timeout | null = null;
  /** EN: Ping interval ID for heartbeat / VI: ID interval ping cho heartbeat */
  private pingIntervalId: NodeJS.Timeout | null = null;

  /**
   * EN: Initialize WebSocket client with configuration
   * VI: Khởi tạo WebSocket client với cấu hình
   *
   * @param config - WebSocket client configuration / Cấu hình WebSocket client
   */
  constructor(config: WebSocketClientConfig) {
    this.config = {
      url: config.url,
      autoReconnect: config.autoReconnect ?? true,
      maxReconnectAttempts: config.maxReconnectAttempts ?? 10,
      reconnectDelay: config.reconnectDelay ?? 1000,
      maxReconnectDelay: config.maxReconnectDelay ?? 30000,
      reconnectDelayMultiplier: config.reconnectDelayMultiplier ?? 1.5,
      callbacks: config.callbacks,
    };
    this.reconnectDelay = this.config.reconnectDelay;
  }

  /**
   * EN: Get current connection state
   * VI: Lấy trạng thái kết nối hiện tại
   *
   * @returns Current WebSocket state / Trạng thái WebSocket hiện tại
   */
  getState(): WebSocketState {
    return this.state;
  }

  /**
   * EN: Check if WebSocket is connected
   * VI: Kiểm tra WebSocket đã kết nối chưa
   *
   * @returns True if connected / True nếu đã kết nối
   */
  isConnected(): boolean {
    return this.state === WebSocketState.CONNECTED && this.ws?.readyState === WebSocket.OPEN;
  }

  /**
   * EN: Connect to WebSocket server
   * VI: Kết nối tới server WebSocket
   *
   * @throws Error if connection fails / Ném lỗi nếu kết nối thất bại
   */
  connect(): void {
    // EN: Return if already connected or connecting
    // VI: Trả về nếu đã kết nối hoặc đang kết nối
    if (this.ws?.readyState === WebSocket.OPEN || this.ws?.readyState === WebSocket.CONNECTING) {
      return;
    }

    // EN: Get authentication token from localStorage
    // VI: Lấy authentication token từ localStorage
    const token = this.getAuthToken();
    if (!token) {
      throw new Error('No authentication token found. Please login first. / Không tìm thấy token xác thực. Vui lòng đăng nhập trước.');
    }

    // EN: Build WebSocket URL with authentication token
    // VI: Tạo URL WebSocket với authentication token
    const url = this.buildWebSocketUrl(this.config.url, token);

    try {
      this.setState(WebSocketState.CONNECTING);
      this.ws = new WebSocket(url);

      this.ws.onopen = this.handleOpen.bind(this);
      this.ws.onclose = this.handleClose.bind(this);
      this.ws.onerror = this.handleError.bind(this);
      this.ws.onmessage = this.handleMessage.bind(this);
    } catch (error) {
      this.setState(WebSocketState.ERROR);
      this.config.callbacks?.onError?.(error as Event);
      throw error;
    }
  }

  /**
   * EN: Disconnect from WebSocket server
   * VI: Ngắt kết nối khỏi server WebSocket
   */
  disconnect(): void {
    // EN: Disable auto-reconnect when manually disconnecting
    // VI: Tắt tự động kết nối lại khi ngắt kết nối thủ công
    this.config.autoReconnect = false;

    // EN: Clear reconnection timeout
    // VI: Xóa timeout kết nối lại
    if (this.reconnectTimeoutId) {
      clearTimeout(this.reconnectTimeoutId);
      this.reconnectTimeoutId = null;
    }

    // EN: Clear ping interval
    // VI: Xóa ping interval
    if (this.pingIntervalId) {
      clearInterval(this.pingIntervalId);
      this.pingIntervalId = null;
    }

    // EN: Close WebSocket connection
    // VI: Đóng kết nối WebSocket
    if (this.ws) {
      this.ws.close(1000, 'Client disconnect / Ngắt kết nối từ client');
      this.ws = null;
    }

    this.setState(WebSocketState.CLOSED);
    this.reconnectAttempts = 0;
    this.reconnectDelay = this.config.reconnectDelay;
  }

  /**
   * EN: Send message through WebSocket
   * VI: Gửi tin nhắn qua WebSocket
   *
   * @param message - Message to send / Tin nhắn cần gửi
   * @throws Error if not connected / Ném lỗi nếu chưa kết nối
   */
  send(message: WebSocketMessage): void {
    if (!this.isConnected() || !this.ws) {
      throw new Error('WebSocket is not connected / WebSocket chưa kết nối');
    }

    try {
      this.ws.send(JSON.stringify(message));
    } catch (error) {
      this.config.callbacks?.onError?.(error as Event);
      throw error;
    }
  }

  /**
   * EN: Update event callbacks
   * VI: Cập nhật event callbacks
   *
   * @param callbacks - New callbacks / Callbacks mới
   */
  updateCallbacks(callbacks: WebSocketCallbacks): void {
    this.config.callbacks = { ...this.config.callbacks, ...callbacks };
  }

  /**
   * EN: Handle WebSocket open event
   * VI: Xử lý sự kiện WebSocket mở
   */
  private handleOpen(): void {
    this.setState(WebSocketState.CONNECTED);
    this.reconnectAttempts = 0;
    this.reconnectDelay = this.config.reconnectDelay;
    this.config.callbacks?.onOpen?.();

    // EN: Start heartbeat/ping (optional, uncomment if server supports)
    // VI: Bắt đầu heartbeat/ping (tùy chọn, bỏ comment nếu server hỗ trợ)
    // this._startPing(); // EN: Unused for now / VI: Chưa sử dụng
  }

  /**
   * EN: Handle WebSocket close event
   * VI: Xử lý sự kiện WebSocket đóng
   */
  private handleClose(event: CloseEvent): void {
    this.setState(WebSocketState.CLOSED);
    this.config.callbacks?.onClose?.();

    // EN: Clear ping interval
    // VI: Xóa ping interval
    if (this.pingIntervalId) {
      clearInterval(this.pingIntervalId);
      this.pingIntervalId = null;
    }

    // EN: Attempt reconnection if not a normal closure and auto-reconnect is enabled
    // VI: Thử kết nối lại nếu không phải đóng bình thường và auto-reconnect được bật
    if (event.code !== 1000 && this.config.autoReconnect) {
      this.attemptReconnect();
    }
  }

  /**
   * EN: Handle WebSocket error event
   * VI: Xử lý sự kiện lỗi WebSocket
   */
  private handleError(error: Event): void {
    this.setState(WebSocketState.ERROR);
    this.config.callbacks?.onError?.(error);
  }

  /**
   * EN: Handle WebSocket message event
   * VI: Xử lý sự kiện tin nhắn WebSocket
   */
  private handleMessage(event: MessageEvent): void {
    try {
      const message: WebSocketMessage = JSON.parse(event.data);
      this.config.callbacks?.onMessage?.(message);
    } catch (error) {
      console.error('Failed to parse WebSocket message / Không thể parse tin nhắn WebSocket:', error);
      this.config.callbacks?.onError?.(error as Event);
    }
  }

  /**
   * EN: Attempt to reconnect to WebSocket server with exponential backoff
   * VI: Thử kết nối lại tới server WebSocket với exponential backoff
   */
  private attemptReconnect(): void {
    // EN: Check if max reconnection attempts reached
    // VI: Kiểm tra nếu đã đạt số lần thử kết nối lại tối đa
    if (this.config.maxReconnectAttempts > 0 && this.reconnectAttempts >= this.config.maxReconnectAttempts) {
      console.error(
        `Max reconnection attempts reached (${this.config.maxReconnectAttempts}) / Đã đạt số lần thử kết nối lại tối đa (${this.config.maxReconnectAttempts})`
      );
      return;
    }

    this.reconnectAttempts++;
    this.setState(WebSocketState.RECONNECTING);

    // EN: Calculate exponential backoff delay
    // VI: Tính toán độ trễ exponential backoff
    const delay = Math.min(this.reconnectDelay, this.config.maxReconnectDelay);

    console.log(
      `Attempting to reconnect (${this.reconnectAttempts}/${this.config.maxReconnectAttempts || '∞'}) in ${delay}ms / Đang thử kết nối lại (${this.reconnectAttempts}/${this.config.maxReconnectAttempts || '∞'}) sau ${delay}ms`
    );

    // EN: Schedule reconnection attempt
    // VI: Lên lịch thử kết nối lại
    this.reconnectTimeoutId = setTimeout(() => {
      this.reconnectDelay *= this.config.reconnectDelayMultiplier;
      this.connect();
    }, delay);
  }

  /**
   * EN: Update connection state and trigger callback
   * VI: Cập nhật trạng thái kết nối và trigger callback
   */
  private setState(state: WebSocketState): void {
    if (this.state !== state) {
      this.state = state;
      this.config.callbacks?.onStateChange?.(state);
    }
  }

  /**
   * EN: Get authentication token from localStorage
   * VI: Lấy authentication token từ localStorage
   *
   * @returns Access token or null / Access token hoặc null
   */
  private getAuthToken(): string | null {
    if (typeof window !== 'undefined') {
      return localStorage.getItem('accessToken');
    }
    return null;
  }

  /**
   * EN: Build WebSocket URL with authentication token
   * VI: Tạo URL WebSocket với authentication token
   *
   * @param baseUrl - Base WebSocket URL / URL WebSocket cơ sở
   * @param token - Authentication token / Token xác thực
   * @returns WebSocket URL with token / URL WebSocket với token
   */
  private buildWebSocketUrl(baseUrl: string, token: string): string {
    // EN: Convert HTTP/HTTPS URL to WS/WSS URL
    // VI: Chuyển đổi URL HTTP/HTTPS sang WS/WSS URL
    const wsUrl = baseUrl.replace(/^http/, 'ws');
    const url = new URL(wsUrl);

    // EN: Add token as query parameter (common pattern)
    // VI: Thêm token như query parameter (pattern phổ biến)
    url.searchParams.set('token', token);

    return url.toString();
  }

  /**
   * EN: Start ping interval for heartbeat (optional)
   * VI: Bắt đầu ping interval cho heartbeat (tùy chọn)
   * @internal - Currently unused, available for future use
   */
  private _startPing(): void {
    // EN: Send ping every 30 seconds to keep connection alive
    // VI: Gửi ping mỗi 30 giây để giữ kết nối sống
    this.pingIntervalId = setInterval(() => {
      if (this.isConnected() && this.ws) {
        this.ws.send(JSON.stringify({ type: WebSocketMessageType.ACK, data: { type: 'ping' } }));
      }
    }, 30000);
  }
}

/**
 * EN: Create WebSocket client instance
 * VI: Tạo instance WebSocket client
 *
 * @param config - WebSocket client configuration / Cấu hình WebSocket client
 * @returns WebSocket client instance / Instance WebSocket client
 */
export const createWebSocketClient = (config: WebSocketClientConfig): WebSocketClient => {
  return new WebSocketClient(config);
};
