import { addIDBData } from './database';
import { isEmpty } from 'lodash-es';

interface WebSocketMessage {
  module: string;
  data: any;
  [key: string]: any;
}

enum ConnectionState {
  CONNECTING = 0,
  OPEN = 1,
  CLOSING = 2,
  CLOSED = 3,
}

type Callback = (message: WebSocketMessage) => void;

/**
 * websocket 接收器
 */
export class WebsocketReceiver {
  public static readonly instances: Map<string, WebsocketReceiver> = new Map();
  /**
   * 获取单例
   * @param url 服务地址
   * @param protocols 服务协议
   * @returns
   */
  static getInstance(url: string, protocols?: string | string[]) {
    const key = `${url}-${JSON.stringify(protocols)}`;
    if (!this.instances.has(key)) {
      this.instances.set(key, new WebsocketReceiver(url, protocols));
    }
    return this.instances.get(key)!;
  }

  private ws: WebSocket;
  private timer: NodeJS.Timeout | null = null;
  private readonly url: string;
  private readonly protocols?: string | string[];
  private readonly HEARTBEAT_INTERVAL = 30000;
  private readonly RECONNECT_DELAY = 10000;
  private isAlive = false;
  private readonly messageQueue: string[] = [];
  private isProcessing = false;
  private readonly onMessageCallbacks: Callback[] = [];
  private readonly onErrorCallbacks: Array<(error: Event) => void> = [];
  constructor(url: string, protocols?: string | string[]) {
    this.url = url;
    this.protocols = protocols;
    this.ws = this.connect();
  }

  /**
   * 监听消息
   * @param callback 监听消息的回调
   */
  onMessage(callback: (message: WebSocketMessage) => void) {
    this.onMessageCallbacks.push(callback);
  }

  /**
   * 监听错误
   * @param callback 监听错误的回调
   */
  onError(callback: (error: Event) => void) {
    this.onErrorCallbacks.push(callback);
  }

  /**
   * 发送消息
   * @param message
   */
  send(message: string) {
    if (this.ws.readyState === WebSocket.OPEN) {
      this.ws.send(message);
    } else {
      this.messageQueue.push(message);
      this.processQueue();
    }
  }

  /**
   * 关闭连接
   */
  close() {
    this.ws.close();
  }

  /**
   * 获取连接状态
   */
  getConnectionState(): ConnectionState {
    return this.ws.readyState;
  }

  /**
   * 连接WebSocket
   */
  private connect(): WebSocket {
    const ws = new WebSocket(this.url, this.protocols);

    ws.onopen = () => {
      this.isAlive = true;
      this.startHeartbeat();
      this.processQueue();
    };

    ws.onmessage = async (event) => {
      try {
        const message: WebSocketMessage = JSON.parse(event.data);
        if (!isEmpty(message.data)) {
          await addIDBData(message.module, message.data);
        }
        this.onMessageCallbacks.forEach((callback) => callback(message));
      } catch (error) {
        this.onErrorCallbacks.forEach((callback) => callback(error as Event));
      }
      if (event.data === 'pong') {
        this.isAlive = true;
      }
    };

    ws.onerror = (error) => {
      this.onErrorCallbacks.forEach((callback) => callback(error));
      this.handleDisconnect();
      this.isAlive = false;
    };

    ws.onclose = () => {
      this.isAlive = false;
    };

    return ws;
  }

  private startHeartbeat() {
    this.timer && clearInterval(this.timer);
    this.timer = setInterval(() => {
      if (!this.isAlive) {
        this.ws.close();
        return;
      }
      this.isAlive = false;
      this.ws.send('ping');
    }, this.HEARTBEAT_INTERVAL);
  }

  private handleDisconnect() {
    this.timer && clearInterval(this.timer);
    this.timer = null;
    this.isAlive = false;
    setTimeout(() => {
      this.ws = this.connect();
    }, this.RECONNECT_DELAY);
  }

  private processQueue() {
    if (this.isProcessing || this.ws.readyState !== WebSocket.OPEN) return;

    this.isProcessing = true;
    while (this.messageQueue.length > 0) {
      const message = this.messageQueue.shift();
      if (message) {
        this.ws.send(message);
      }
    }
    this.isProcessing = false;
  }
}
