import { WebSocketClient } from './connection';
import {
  AllMids,
  WsTrade,
  WsBook,
  WsBbo,
  WsOrder,
  WsUserEvent,
  Notification,
  WebData2,
  Candle,
  WsUserFills,
  WsUserFundings,
  WsUserNonFundingLedgerUpdates,
  WsUserActiveAssetData,
  WsActiveSpotAssetCtx,
  WsActiveAssetCtx,
  WsTwapHistoryResponse,
  WsTwapSliceFill,
} from '../types/index';
import { SymbolConversion } from '../utils/symbolConversion';

export class WebSocketSubscriptions {
  private ws: WebSocketClient;
  private symbolConversion: SymbolConversion;
  private activeSubscriptions: Map<string, Set<Function>> = new Map();
  private subscriptionDetails: Map<string, { type: string; params: any }> = new Map();

  constructor(ws: WebSocketClient, symbolConversion: SymbolConversion) {
    this.ws = ws;
    this.symbolConversion = symbolConversion;

    // Listen for reconnect events to resubscribe
    this.ws.on('reconnect', () => {
      this.resubscribeAll();
    });
  }

  private getSubscriptionKey(type: string, params: any = {}): string {
    return `${type}:${JSON.stringify(params)}`;
  }

  private addSubscriptionCallback(key: string, callback: Function): void {
    if (!this.activeSubscriptions.has(key)) {
      this.activeSubscriptions.set(key, new Set());
    }
    this.activeSubscriptions.get(key)?.add(callback);
  }

  private removeSubscriptionCallback(key: string, callback: Function): void {
    const callbacks = this.activeSubscriptions.get(key);
    if (callbacks) {
      callbacks.delete(callback);
      if (callbacks.size === 0) {
        this.activeSubscriptions.delete(key);
      }
    }
  }

  private async subscribe(subscription: { type: string; [key: string]: any }): Promise<void> {
    // Check if we can add another subscription
    if (!this.ws.incrementSubscriptionCount()) {
      throw new Error('Maximum subscription limit reached (1000 subscriptions per IP)');
    }

    try {
      await this.ws.sendMessage({ method: 'subscribe', subscription: subscription });

      // Store subscription details for resubscription
      const subscriptionKey = this.getSubscriptionKey(subscription.type, subscription);
      this.subscriptionDetails.set(subscriptionKey, {
        type: subscription.type,
        params: subscription,
      });
    } catch (error) {
      // If subscription fails, decrement the count
      this.ws.decrementSubscriptionCount();
      throw error;
    }
  }

  private async unsubscribe(subscription: { type: string; [key: string]: any }): Promise<void> {
    const convertedSubscription = await this.symbolConversion.convertSymbolsInObject(subscription);
    await this.ws.sendMessage({ method: 'unsubscribe', subscription: convertedSubscription });
    // Decrement subscription count when unsubscribing
    this.ws.decrementSubscriptionCount();

    // Remove subscription details
    const subscriptionKey = this.getSubscriptionKey(subscription.type, subscription);
    this.subscriptionDetails.delete(subscriptionKey);
  }

  private handleMessage(
    message: any,
    callback: (data: any) => void,
    channel: string,
    additionalChecks: (data: any) => boolean = () => true
  ) {
    if (typeof message !== 'object' || message === null) {
      console.warn('Received invalid message format:', message);
      return;
    }

    let data = message.data || message;
    if (data.channel === channel && additionalChecks(data)) {
      const convertedData = this.symbolConversion.convertSymbolsInObject(data);
      callback(convertedData);
    }
  }

  async subscribeToAllMids(callback: (data: AllMids) => void): Promise<void> {
    if (typeof callback !== 'function') {
      throw new Error('Callback must be a function');
    }

    const subscriptionKey = this.getSubscriptionKey('allMids');

    // Remove existing subscription if any
    if (this.activeSubscriptions.has(subscriptionKey)) {
      await this.unsubscribeFromAllMids();
    }

    this.addSubscriptionCallback(subscriptionKey, callback);

    const messageHandler = async (message: any) => {
      if (message.channel === 'allMids') {
        if (message.data.mids) {
          const convertedData: AllMids = {};
          for (const [key, value] of Object.entries(message.data.mids)) {
            const convertedKey = await this.symbolConversion.convertSymbol(key);
            const convertedValue = this.symbolConversion.convertToNumber(value);
            convertedData[convertedKey] = convertedValue;
          }
          callback(convertedData);
        }
      }
    };

    (callback as any).__messageHandler = messageHandler;
    this.ws.on('message', messageHandler);
    await this.subscribe({ type: 'allMids' });
  }

  async subscribeToNotification(
    user: string,
    callback: (data: Notification & { user: string }) => void
  ): Promise<void> {
    const subscriptionKey = this.getSubscriptionKey('notification', { user });

    if (this.activeSubscriptions.has(subscriptionKey)) {
      await this.unsubscribeFromNotification(user);
    }

    this.addSubscriptionCallback(subscriptionKey, callback);

    const messageHandler = async (message: any) => {
      if (message.channel === 'notification') {
        message = await this.symbolConversion.convertSymbolsInObject(message);
        callback(message.data);
      }
    };

    (callback as any).__messageHandler = messageHandler;
    this.ws.on('message', messageHandler);
    await this.subscribe({ type: 'notification', user: user });
  }

  async subscribeToWebData2(user: string, callback: (data: WebData2) => void): Promise<void> {
    const subscriptionKey = this.getSubscriptionKey('webData2', { user });

    if (this.activeSubscriptions.has(subscriptionKey)) {
      await this.unsubscribeFromWebData2(user);
    }

    this.addSubscriptionCallback(subscriptionKey, callback);

    const messageHandler = async (message: any) => {
      if (message.channel === 'webData2') {
        message = await this.symbolConversion.convertSymbolsInObject(message);
        callback(message.data);
      }
    };

    (callback as any).__messageHandler = messageHandler;
    this.ws.on('message', messageHandler);
    await this.subscribe({ type: 'webData2', user: user });
  }

  async subscribeToCandle(
    coin: string,
    interval: string,
    callback: (data: Candle) => void
  ): Promise<void> {
    const convertedCoin = await this.symbolConversion.convertSymbol(coin, 'reverse');
    const subscriptionKey = this.getSubscriptionKey('candle', { coin: convertedCoin, interval });

    if (this.activeSubscriptions.has(subscriptionKey)) {
      await this.unsubscribeFromCandle(coin, interval);
    }

    this.addSubscriptionCallback(subscriptionKey, callback);

    const messageHandler = async (message: any) => {
      if (
        message.channel === 'candle' &&
        message.data.s === convertedCoin &&
        message.data.i === interval
      ) {
        message = await this.symbolConversion.convertSymbolsInObject(message, ['s']);
        callback(message.data);
      }
    };

    (callback as any).__messageHandler = messageHandler;
    this.ws.on('message', messageHandler);
    await this.subscribe({ type: 'candle', coin: convertedCoin, interval: interval });
  }

  async subscribeToL2Book(
    coin: string,
    callback: (data: WsBook & { coin: string }) => void
  ): Promise<void> {
    const convertedCoin = await this.symbolConversion.convertSymbol(coin, 'reverse');
    const subscriptionKey = this.getSubscriptionKey('l2Book', { coin: convertedCoin });

    if (this.activeSubscriptions.has(subscriptionKey)) {
      await this.unsubscribeFromL2Book(coin);
    }

    this.addSubscriptionCallback(subscriptionKey, callback);

    const messageHandler = async (message: any) => {
      if (message.channel === 'l2Book' && message.data.coin === convertedCoin) {
        message = await this.symbolConversion.convertSymbolsInObject(message, ['coin']);
        callback(message.data);
      }
    };

    (callback as any).__messageHandler = messageHandler;
    this.ws.on('message', messageHandler);
    await this.subscribe({ type: 'l2Book', coin: convertedCoin });
  }

  async subscribeToTrades(coin: string, callback: (data: any) => void): Promise<void> {
    const convertedCoin = await this.symbolConversion.convertSymbol(coin, 'reverse');
    const subscriptionKey = this.getSubscriptionKey('trades', { coin: convertedCoin });

    if (this.activeSubscriptions.has(subscriptionKey)) {
      await this.unsubscribeFromTrades(coin);
    }

    this.addSubscriptionCallback(subscriptionKey, callback);

    const messageHandler = async (message: any) => {
      if (message.channel === 'trades' && message.data[0].coin === convertedCoin) {
        message = await this.symbolConversion.convertSymbolsInObject(message, ['coin']);
        callback(message.data);
      }
    };

    (callback as any).__messageHandler = messageHandler;
    this.ws.on('message', messageHandler);
    await this.subscribe({ type: 'trades', coin: convertedCoin });
  }

  async subscribeToOrderUpdates(
    user: string,
    callback: (data: WsOrder[] & { user: string }) => void
  ): Promise<void> {
    const subscriptionKey = this.getSubscriptionKey('orderUpdates', { user });

    if (this.activeSubscriptions.has(subscriptionKey)) {
      await this.unsubscribeFromOrderUpdates(user);
    }

    this.addSubscriptionCallback(subscriptionKey, callback);

    const messageHandler = async (message: any) => {
      if (message.channel === 'orderUpdates') {
        message = await this.symbolConversion.convertSymbolsInObject(message);
        callback(message.data);
      }
    };

    (callback as any).__messageHandler = messageHandler;
    this.ws.on('message', messageHandler);
    await this.subscribe({ type: 'orderUpdates', user: user });
  }

  async subscribeToUserEvents(
    user: string,
    callback: (data: WsUserEvent & { user: string }) => void
  ): Promise<void> {
    const subscriptionKey = this.getSubscriptionKey('userEvents', { user });

    if (this.activeSubscriptions.has(subscriptionKey)) {
      await this.unsubscribeFromUserEvents(user);
    }

    this.addSubscriptionCallback(subscriptionKey, callback);

    const messageHandler = async (message: any) => {
      if (message.channel === 'userEvents') {
        message = await this.symbolConversion.convertSymbolsInObject(message);
        callback(message.data);
      }
    };

    (callback as any).__messageHandler = messageHandler;
    this.ws.on('message', messageHandler);
    await this.subscribe({ type: 'userEvents', user: user });
  }

  async subscribeToUserFills(
    user: string,
    callback: (data: WsUserFills & { user: string }) => void
  ): Promise<void> {
    const subscriptionKey = this.getSubscriptionKey('userFills', { user });

    // Remove existing subscription if any
    if (this.activeSubscriptions.has(subscriptionKey)) {
      await this.unsubscribeFromUserFills(user);
    }

    this.addSubscriptionCallback(subscriptionKey, callback);

    const messageHandler = async (message: any) => {
      if (message.channel === 'userFills') {
        const convertedMessage = await this.symbolConversion.convertSymbolsInObject(message);
        callback(convertedMessage.data);
      }
    };

    // Store the message handler with the callback for cleanup
    (callback as any).__messageHandler = messageHandler;

    this.ws.on('message', messageHandler);
    await this.subscribe({ type: 'userFills', user });
  }

  async subscribeToUserFundings(
    user: string,
    callback: (data: WsUserFundings & { user: string }) => void
  ): Promise<void> {
    const subscriptionKey = this.getSubscriptionKey('userFundings', { user });

    if (this.activeSubscriptions.has(subscriptionKey)) {
      await this.unsubscribeFromUserFundings(user);
    }

    this.addSubscriptionCallback(subscriptionKey, callback);

    const messageHandler = async (message: any) => {
      if (message.channel === 'userFundings') {
        message = await this.symbolConversion.convertSymbolsInObject(message);
        callback(message.data);
      }
    };

    (callback as any).__messageHandler = messageHandler;
    this.ws.on('message', messageHandler);
    await this.subscribe({ type: 'userFundings', user: user });
  }

  async subscribeToUserNonFundingLedgerUpdates(
    user: string,
    callback: (data: WsUserNonFundingLedgerUpdates & { user: string }) => void
  ): Promise<void> {
    const subscriptionKey = this.getSubscriptionKey('userNonFundingLedgerUpdates', { user });

    if (this.activeSubscriptions.has(subscriptionKey)) {
      await this.unsubscribeFromUserNonFundingLedgerUpdates(user);
    }

    this.addSubscriptionCallback(subscriptionKey, callback);

    const messageHandler = async (message: any) => {
      if (message.channel === 'userNonFundingLedgerUpdates') {
        message = await this.symbolConversion.convertSymbolsInObject(message);
        callback(message.data);
      }
    };

    (callback as any).__messageHandler = messageHandler;
    this.ws.on('message', messageHandler);
    await this.subscribe({ type: 'userNonFundingLedgerUpdates', user: user });
  }

  async subscribeToUserActiveAssetData(
    user: string,
    coin: string,
    callback: (data: WsUserActiveAssetData & { user: string }) => void
  ): Promise<void> {
    const subscriptionKey = this.getSubscriptionKey('activeAssetData', { user, coin });

    if (this.activeSubscriptions.has(subscriptionKey)) {
      await this.unsubscribeFromUserActiveAssetData(user, coin);
    }

    this.addSubscriptionCallback(subscriptionKey, callback);

    const messageHandler = async (message: any) => {
      if (message.channel === 'activeAssetData') {
        message = await this.symbolConversion.convertSymbolsInObject(message);
        callback(message.data);
      }
    };

    (callback as any).__messageHandler = messageHandler;
    this.ws.on('message', messageHandler);
    await this.subscribe({ type: 'activeAssetData', user: user, coin: coin });
  }

  /**
   * Send a POST request via WebSocket
   * @param requestType - The type of request ('info' or 'action')
   * @param payload - The payload to send with the request
   * @param timeout - Optional timeout in milliseconds (default: 30000)
   * @returns A promise that resolves with the response data
   */
  async postRequest(
    requestType: 'info' | 'action',
    payload: any,
    timeout: number = 30000
  ): Promise<any> {
    // Ensure WebSocket is connected
    if (!this.ws.isConnected()) {
      throw new Error('WebSocket is not connected');
    }

    // Generate a unique request ID
    const id = Date.now() + Math.floor(Math.random() * 1000);

    console.log(`Preparing WebSocket POST request (ID: ${id}):`, JSON.stringify(payload));

    // For WebSocket POST requests, we need to use the exchange format (e.g., 'BTC' instead of 'BTC-PERP')
    // We need to ensure all coin references are in the exchange format
    let convertedPayload = { ...payload };

    // Helper function to convert a coin to exchange format
    const convertCoinToExchangeFormat = (coin: string): string => {
      if (coin && coin.includes('-')) {
        const parts = coin.split('-');
        return parts[0]; // Return just the base symbol (e.g., 'BTC' from 'BTC-PERP')
      }
      return coin;
    };

    // Process coin field if it exists
    if (convertedPayload.coin) {
      convertedPayload.coin = convertCoinToExchangeFormat(convertedPayload.coin);
    }

    // Process arrays of coins if they exist
    if (Array.isArray(convertedPayload.coins)) {
      convertedPayload.coins = convertedPayload.coins.map(convertCoinToExchangeFormat);
    }

    // For nested objects like in order requests
    if (convertedPayload.orders) {
      convertedPayload.orders = convertedPayload.orders.map((order: any) => {
        if (order.coin) {
          return { ...order, coin: convertCoinToExchangeFormat(order.coin) };
        }
        return order;
      });
    }

    // For cancels which might have coin field
    if (convertedPayload.cancels) {
      convertedPayload.cancels = convertedPayload.cancels.map((cancel: any) => {
        if (cancel.coin) {
          return { ...cancel, coin: convertCoinToExchangeFormat(cancel.coin) };
        }
        return cancel;
      });
    }

    // For action.cancels which might have coin field
    if (convertedPayload.action && convertedPayload.action.cancels) {
      convertedPayload.action.cancels = convertedPayload.action.cancels.map((cancel: any) => {
        if (cancel.coin) {
          return { ...cancel, coin: convertCoinToExchangeFormat(cancel.coin) };
        }
        return cancel;
      });
    }

    // Create the request object according to Hyperliquid API format
    const request = {
      method: 'post',
      id: id,
      request: {
        type: requestType,
        payload: convertedPayload,
      },
    };

    console.log(`Sending WebSocket POST request (ID: ${id}):`, JSON.stringify(request));

    // Send the request
    this.ws.sendMessage(request);

    // Wait for and process the response
    return new Promise((resolve, reject) => {
      let receivedMessages = 0;
      let timeoutId: NodeJS.Timeout;

      const responseHandler = (message: any) => {
        // Skip if not an object
        if (typeof message !== 'object' || message === null) {
          return;
        }

        receivedMessages++;

        // For debugging - log every 10th message to avoid flooding the console
        if (receivedMessages % 10 === 0) {
          console.log(
            `Received ${receivedMessages} WebSocket messages while waiting for response to request ${id}`
          );
        }

        // Check if this is a post response
        if (message.channel === 'post') {
          console.log(`Received post response:`, JSON.stringify(message));

          // Check if this is a response to our request
          if (message.data && message.data.id === id) {
            console.log(`Found matching response for request ID ${id}`);

            // Clean up the event listener and timeout
            this.ws.removeListener('message', responseHandler);
            if (timeoutId) {
              clearTimeout(timeoutId);
            }

            // Handle error responses
            if (message.data.response && message.data.response.type === 'error') {
              reject(new Error(message.data.response.payload));
              return;
            }

            try {
              // Extract and convert the response payload
              let responseData;

              if (message.data.response && message.data.response.payload) {
                responseData = message.data.response.payload;
              } else if (message.data.response) {
                responseData = message.data.response;
              } else {
                responseData = message.data;
              }

              // For the response, we want to convert exchange format back to our internal format
              // This means adding the '-PERP' suffix to coin symbols
              const processResponse = (data: any): any => {
                if (!data || typeof data !== 'object') {
                  return data;
                }

                if (Array.isArray(data)) {
                  return data.map(item => processResponse(item));
                }

                const result: any = {};

                for (const [key, value] of Object.entries(data)) {
                  if (key === 'coin' && typeof value === 'string') {
                    // Convert coin to internal format (add -PERP suffix if not present)
                    result[key] = value.includes('-') ? value : `${value}-PERP`;
                  } else if (typeof value === 'object' && value !== null) {
                    // Recursively process nested objects
                    result[key] = processResponse(value);
                  } else {
                    // Keep other values as is
                    result[key] = value;
                  }
                }

                return result;
              };

              const processedResponse = processResponse(responseData);
              resolve(processedResponse);
            } catch (error) {
              console.error('Error processing response:', error);
              reject(error);
            }
          }
        }
      };

      // Register the response handler
      this.ws.on('message', responseHandler);

      // Set a timeout to prevent hanging requests
      timeoutId = setTimeout(() => {
        this.ws.removeListener('message', responseHandler);
        console.log(
          `Request ${id} timed out after ${timeout}ms. Received ${receivedMessages} messages.`
        );
        reject(new Error(`WebSocket request timeout after ${timeout}ms`));
      }, timeout);
    });
  }

  async unsubscribeFromAllMids(): Promise<void> {
    const subscriptionKey = this.getSubscriptionKey('allMids');
    const callbacks = this.activeSubscriptions.get(subscriptionKey);

    if (callbacks) {
      for (const callback of callbacks) {
        const messageHandler = (callback as any).__messageHandler;
        if (messageHandler) {
          this.ws.removeListener('message', messageHandler);
          delete (callback as any).__messageHandler;
        }
      }
      this.activeSubscriptions.delete(subscriptionKey);
    }

    await this.unsubscribe({ type: 'allMids' });
  }

  async unsubscribeFromNotification(user: string): Promise<void> {
    const subscriptionKey = this.getSubscriptionKey('notification', { user });
    const callbacks = this.activeSubscriptions.get(subscriptionKey);

    if (callbacks) {
      for (const callback of callbacks) {
        const messageHandler = (callback as any).__messageHandler;
        if (messageHandler) {
          this.ws.removeListener('message', messageHandler);
          delete (callback as any).__messageHandler;
        }
      }
      this.activeSubscriptions.delete(subscriptionKey);
    }

    await this.unsubscribe({ type: 'notification', user: user });
  }

  async unsubscribeFromWebData2(user: string): Promise<void> {
    const subscriptionKey = this.getSubscriptionKey('webData2', { user });
    const callbacks = this.activeSubscriptions.get(subscriptionKey);

    if (callbacks) {
      for (const callback of callbacks) {
        const messageHandler = (callback as any).__messageHandler;
        if (messageHandler) {
          this.ws.removeListener('message', messageHandler);
          delete (callback as any).__messageHandler;
        }
      }
      this.activeSubscriptions.delete(subscriptionKey);
    }

    await this.unsubscribe({ type: 'webData2', user: user });
  }

  async unsubscribeFromCandle(coin: string, interval: string): Promise<void> {
    const convertedCoin = await this.symbolConversion.convertSymbol(coin, 'reverse');
    const subscriptionKey = this.getSubscriptionKey('candle', { coin: convertedCoin, interval });
    const callbacks = this.activeSubscriptions.get(subscriptionKey);

    if (callbacks) {
      for (const callback of callbacks) {
        const messageHandler = (callback as any).__messageHandler;
        if (messageHandler) {
          this.ws.removeListener('message', messageHandler);
          delete (callback as any).__messageHandler;
        }
      }
      this.activeSubscriptions.delete(subscriptionKey);
    }

    await this.unsubscribe({ type: 'candle', coin: convertedCoin, interval: interval });
  }

  async unsubscribeFromL2Book(coin: string): Promise<void> {
    const convertedCoin = await this.symbolConversion.convertSymbol(coin, 'reverse');
    const subscriptionKey = this.getSubscriptionKey('l2Book', { coin: convertedCoin });
    const callbacks = this.activeSubscriptions.get(subscriptionKey);

    if (callbacks) {
      for (const callback of callbacks) {
        const messageHandler = (callback as any).__messageHandler;
        if (messageHandler) {
          this.ws.removeListener('message', messageHandler);
          delete (callback as any).__messageHandler;
        }
      }
      this.activeSubscriptions.delete(subscriptionKey);
    }

    await this.unsubscribe({ type: 'l2Book', coin: convertedCoin });
  }

  async unsubscribeFromTrades(coin: string): Promise<void> {
    const convertedCoin = await this.symbolConversion.convertSymbol(coin, 'reverse');
    const subscriptionKey = this.getSubscriptionKey('trades', { coin: convertedCoin });
    const callbacks = this.activeSubscriptions.get(subscriptionKey);

    if (callbacks) {
      for (const callback of callbacks) {
        const messageHandler = (callback as any).__messageHandler;
        if (messageHandler) {
          this.ws.removeListener('message', messageHandler);
          delete (callback as any).__messageHandler;
        }
      }
      this.activeSubscriptions.delete(subscriptionKey);
    }

    await this.unsubscribe({ type: 'trades', coin: convertedCoin });
  }

  async unsubscribeFromOrderUpdates(user: string): Promise<void> {
    const subscriptionKey = this.getSubscriptionKey('orderUpdates', { user });
    const callbacks = this.activeSubscriptions.get(subscriptionKey);

    if (callbacks) {
      for (const callback of callbacks) {
        const messageHandler = (callback as any).__messageHandler;
        if (messageHandler) {
          this.ws.removeListener('message', messageHandler);
          delete (callback as any).__messageHandler;
        }
      }
      this.activeSubscriptions.delete(subscriptionKey);
    }

    await this.unsubscribe({ type: 'orderUpdates', user: user });
  }

  async unsubscribeFromUserEvents(user: string): Promise<void> {
    const subscriptionKey = this.getSubscriptionKey('userEvents', { user });
    const callbacks = this.activeSubscriptions.get(subscriptionKey);

    if (callbacks) {
      for (const callback of callbacks) {
        const messageHandler = (callback as any).__messageHandler;
        if (messageHandler) {
          this.ws.removeListener('message', messageHandler);
          delete (callback as any).__messageHandler;
        }
      }
      this.activeSubscriptions.delete(subscriptionKey);
    }

    await this.unsubscribe({ type: 'userEvents', user: user });
  }

  async unsubscribeFromUserFills(user: string): Promise<void> {
    const subscriptionKey = this.getSubscriptionKey('userFills', { user });
    const callbacks = this.activeSubscriptions.get(subscriptionKey);

    if (callbacks) {
      for (const callback of callbacks) {
        const messageHandler = (callback as any).__messageHandler;
        if (messageHandler) {
          this.ws.removeListener('message', messageHandler);
          delete (callback as any).__messageHandler;
        }
      }
      this.activeSubscriptions.delete(subscriptionKey);
    }

    await this.unsubscribe({ type: 'userFills', user });
  }

  async unsubscribeFromUserFundings(user: string): Promise<void> {
    const subscriptionKey = this.getSubscriptionKey('userFundings', { user });
    const callbacks = this.activeSubscriptions.get(subscriptionKey);

    if (callbacks) {
      for (const callback of callbacks) {
        const messageHandler = (callback as any).__messageHandler;
        if (messageHandler) {
          this.ws.removeListener('message', messageHandler);
          delete (callback as any).__messageHandler;
        }
      }
      this.activeSubscriptions.delete(subscriptionKey);
    }

    await this.unsubscribe({ type: 'userFundings', user: user });
  }

  async unsubscribeFromUserNonFundingLedgerUpdates(user: string): Promise<void> {
    const subscriptionKey = this.getSubscriptionKey('userNonFundingLedgerUpdates', { user });
    const callbacks = this.activeSubscriptions.get(subscriptionKey);

    if (callbacks) {
      for (const callback of callbacks) {
        const messageHandler = (callback as any).__messageHandler;
        if (messageHandler) {
          this.ws.removeListener('message', messageHandler);
          delete (callback as any).__messageHandler;
        }
      }
      this.activeSubscriptions.delete(subscriptionKey);
    }

    await this.unsubscribe({ type: 'userNonFundingLedgerUpdates', user: user });
  }

  async unsubscribeFromUserActiveAssetData(user: string, coin: string): Promise<void> {
    const subscriptionKey = this.getSubscriptionKey('activeAssetData', { user, coin });
    const callbacks = this.activeSubscriptions.get(subscriptionKey);

    if (callbacks) {
      for (const callback of callbacks) {
        const messageHandler = (callback as any).__messageHandler;
        if (messageHandler) {
          this.ws.removeListener('message', messageHandler);
          delete (callback as any).__messageHandler;
        }
      }
      this.activeSubscriptions.delete(subscriptionKey);
    }

    await this.unsubscribe({ type: 'activeAssetData', user: user, coin: coin });
  }

  async subscribeToActiveAssetCtx(
    coin: string,
    callback: (data: WsActiveAssetCtx) => void
  ): Promise<void> {
    const convertedCoin = await this.symbolConversion.convertSymbol(coin, 'reverse');
    const subscriptionKey = this.getSubscriptionKey('activeAssetCtx', { coin: convertedCoin });

    if (this.activeSubscriptions.has(subscriptionKey)) {
      await this.unsubscribeFromActiveAssetCtx(coin);
    }

    this.addSubscriptionCallback(subscriptionKey, callback);

    const messageHandler = async (message: any) => {
      if (message.channel === 'activeAssetCtx' && message.data.coin === convertedCoin) {
        const convertedMessage = await this.symbolConversion.convertSymbolsInObject(message);
        callback(convertedMessage.data);
      }
    };

    (callback as any).__messageHandler = messageHandler;
    this.ws.on('message', messageHandler);
    await this.subscribe({ type: 'activeAssetCtx', coin: convertedCoin });
  }

  async subscribeToActiveSpotAssetCtx(
    coin: string,
    callback: (data: WsActiveSpotAssetCtx) => void
  ): Promise<void> {
    const convertedCoin = await this.symbolConversion.convertSymbol(coin, 'reverse');
    const subscriptionKey = this.getSubscriptionKey('activeSpotAssetCtx', { coin: convertedCoin });

    if (this.activeSubscriptions.has(subscriptionKey)) {
      await this.unsubscribeFromActiveSpotAssetCtx(coin);
    }

    this.addSubscriptionCallback(subscriptionKey, callback);

    const messageHandler = async (message: any) => {
      if (message.channel === 'activeSpotAssetCtx' && message.data.coin === convertedCoin) {
        const convertedMessage = await this.symbolConversion.convertSymbolsInObject(message);
        callback(convertedMessage.data);
      }
    };

    (callback as any).__messageHandler = messageHandler;
    this.ws.on('message', messageHandler);
    await this.subscribe({ type: 'activeSpotAssetCtx', coin: convertedCoin });
  }

  async subscribeToUserTwapSliceFills(
    user: string,
    callback: (data: WsTwapSliceFill & { user: string }) => void
  ): Promise<void> {
    const subscriptionKey = this.getSubscriptionKey('userTwapSliceFills', { user });

    if (this.activeSubscriptions.has(subscriptionKey)) {
      await this.unsubscribeFromUserTwapSliceFills(user);
    }

    this.addSubscriptionCallback(subscriptionKey, callback);

    const messageHandler = async (message: any) => {
      if (message.channel === 'userTwapSliceFills') {
        const convertedMessage = await this.symbolConversion.convertSymbolsInObject(message);
        callback(convertedMessage.data);
      }
    };

    (callback as any).__messageHandler = messageHandler;
    this.ws.on('message', messageHandler);
    await this.subscribe({ type: 'userTwapSliceFills', user });
  }

  async subscribeToBbo(coin: string, callback: (data: WsBbo) => void): Promise<void> {
    const convertedCoin = await this.symbolConversion.convertSymbol(coin, 'reverse');
    const subscriptionKey = this.getSubscriptionKey('bbo', { coin: convertedCoin });

    if (this.activeSubscriptions.has(subscriptionKey)) {
      await this.unsubscribeFromBbo(coin);
    }

    this.addSubscriptionCallback(subscriptionKey, callback);

    const messageHandler = async (message: any) => {
      if (message.channel === 'bbo' && message.data.coin === convertedCoin) {
        const convertedMessage = await this.symbolConversion.convertSymbolsInObject(message);
        callback(convertedMessage.data);
      }
    };

    (callback as any).__messageHandler = messageHandler;
    this.ws.on('message', messageHandler);
    await this.subscribe({ type: 'bbo', coin: convertedCoin });
  }

  async subscribeToUserTwapHistory(
    user: string,
    callback: (data: WsTwapHistoryResponse) => void
  ): Promise<void> {
    const subscriptionKey = this.getSubscriptionKey('userTwapHistory', { user });

    if (this.activeSubscriptions.has(subscriptionKey)) {
      await this.unsubscribeFromUserTwapHistory(user);
    }

    this.addSubscriptionCallback(subscriptionKey, callback);

    const messageHandler = async (message: any) => {
      if (message.channel === 'userTwapHistory') {
        const convertedMessage = await this.symbolConversion.convertSymbolsInObject(message);
        callback(convertedMessage.data);
      }
    };

    (callback as any).__messageHandler = messageHandler;
    this.ws.on('message', messageHandler);
    await this.subscribe({ type: 'userTwapHistory', user });
  }

  async unsubscribeFromActiveAssetCtx(coin: string): Promise<void> {
    const convertedCoin = await this.symbolConversion.convertSymbol(coin, 'reverse');
    const subscriptionKey = this.getSubscriptionKey('activeAssetCtx', { coin: convertedCoin });
    const callbacks = this.activeSubscriptions.get(subscriptionKey);

    if (callbacks) {
      for (const callback of callbacks) {
        const messageHandler = (callback as any).__messageHandler;
        if (messageHandler) {
          this.ws.removeListener('message', messageHandler);
          delete (callback as any).__messageHandler;
        }
      }
      this.activeSubscriptions.delete(subscriptionKey);
    }

    await this.unsubscribe({ type: 'activeAssetCtx', coin: convertedCoin });
  }

  async unsubscribeFromActiveSpotAssetCtx(coin: string): Promise<void> {
    const convertedCoin = await this.symbolConversion.convertSymbol(coin, 'reverse');
    const subscriptionKey = this.getSubscriptionKey('activeSpotAssetCtx', { coin: convertedCoin });
    const callbacks = this.activeSubscriptions.get(subscriptionKey);

    if (callbacks) {
      for (const callback of callbacks) {
        const messageHandler = (callback as any).__messageHandler;
        if (messageHandler) {
          this.ws.removeListener('message', messageHandler);
          delete (callback as any).__messageHandler;
        }
      }
      this.activeSubscriptions.delete(subscriptionKey);
    }

    await this.unsubscribe({ type: 'activeSpotAssetCtx', coin: convertedCoin });
  }

  async unsubscribeFromUserTwapSliceFills(user: string): Promise<void> {
    const subscriptionKey = this.getSubscriptionKey('userTwapSliceFills', { user });
    const callbacks = this.activeSubscriptions.get(subscriptionKey);

    if (callbacks) {
      for (const callback of callbacks) {
        const messageHandler = (callback as any).__messageHandler;
        if (messageHandler) {
          this.ws.removeListener('message', messageHandler);
          delete (callback as any).__messageHandler;
        }
      }
      this.activeSubscriptions.delete(subscriptionKey);
    }

    await this.unsubscribe({ type: 'userTwapSliceFills', user });
  }

  async unsubscribeFromBbo(coin: string): Promise<void> {
    const convertedCoin = await this.symbolConversion.convertSymbol(coin, 'reverse');
    const subscriptionKey = this.getSubscriptionKey('bbo', { coin: convertedCoin });
    const callbacks = this.activeSubscriptions.get(subscriptionKey);

    if (callbacks) {
      for (const callback of callbacks) {
        const messageHandler = (callback as any).__messageHandler;
        if (messageHandler) {
          this.ws.removeListener('message', messageHandler);
          delete (callback as any).__messageHandler;
        }
      }
      this.activeSubscriptions.delete(subscriptionKey);
    }

    await this.unsubscribe({ type: 'bbo', coin: convertedCoin });
  }

  async unsubscribeFromUserTwapHistory(user: string): Promise<void> {
    const subscriptionKey = this.getSubscriptionKey('userTwapHistory', { user });
    const callbacks = this.activeSubscriptions.get(subscriptionKey);

    if (callbacks) {
      for (const callback of callbacks) {
        const messageHandler = (callback as any).__messageHandler;
        if (messageHandler) {
          this.ws.removeListener('message', messageHandler);
          delete (callback as any).__messageHandler;
        }
      }
      this.activeSubscriptions.delete(subscriptionKey);
    }

    await this.unsubscribe({ type: 'userTwapHistory', user });
  }

  /**
   * Resubscribes to all active subscriptions after a WebSocket reconnection
   */
  async resubscribeAll(): Promise<void> {
    console.log('Resubscribing to all active subscriptions after reconnection...');

    // Reset the subscription count since we're starting fresh after reconnection
    // The count will be incremented for each subscription as we resubscribe

    // Create a copy of the subscription details to avoid modification during iteration
    const subscriptionsToRestore = new Map(this.subscriptionDetails);

    // Clear the current subscription details as we'll rebuild it
    this.subscriptionDetails.clear();

    // Resubscribe to each subscription
    for (const [, details] of subscriptionsToRestore.entries()) {
      try {
        console.log(`Resubscribing to ${details.type}`);
        await this.subscribe(details.params);
      } catch (error) {
        console.error(`Failed to resubscribe to ${details.type}:`, error);
      }
    }

    console.log('Resubscription complete');
  }
}
