/**
 * WebSocket Service
 * Real-time data streaming for trading signals and market data
 */

type MessageHandler = (data: unknown) => void;
type ConnectionHandler = () => void;

interface WebSocketConfig {
  url: string;
  reconnectInterval?: number;
  maxReconnectAttempts?: number;
}

export class WebSocketService {
  private ws: WebSocket | null = null;
  private url: string;
  private reconnectInterval: number;
  private maxReconnectAttempts: number;
  private reconnectAttempts = 0;
  private messageHandlers: Map<string, Set<MessageHandler>> = new Map();
  private onConnectHandlers: Set<ConnectionHandler> = new Set();
  private onDisconnectHandlers: Set<ConnectionHandler> = new Set();
  private isManualClose = false;

  constructor(config: WebSocketConfig) {
    this.url = config.url;
    this.reconnectInterval = config.reconnectInterval || 5000;
    this.maxReconnectAttempts = config.maxReconnectAttempts || 10;
  }

  connect(): void {
    if (this.ws?.readyState === WebSocket.OPEN) {
      console.log('WebSocket already connected');
      return;
    }

    this.isManualClose = false;
    this.ws = new WebSocket(this.url);

    this.ws.onopen = () => {
      console.log('WebSocket connected');
      this.reconnectAttempts = 0;
      this.onConnectHandlers.forEach((handler) => handler());
    };

    this.ws.onmessage = (event) => {
      try {
        const message = JSON.parse(event.data);
        const { type, data } = message;

        if (type && this.messageHandlers.has(type)) {
          this.messageHandlers.get(type)?.forEach((handler) => handler(data));
        }

        // Also emit to 'all' handlers
        if (this.messageHandlers.has('all')) {
          this.messageHandlers.get('all')?.forEach((handler) => handler(message));
        }
      } catch (error) {
        console.error('Failed to parse WebSocket message:', error);
      }
    };

    this.ws.onclose = () => {
      console.log('WebSocket disconnected');
      this.onDisconnectHandlers.forEach((handler) => handler());

      if (!this.isManualClose && this.reconnectAttempts < this.maxReconnectAttempts) {
        this.reconnectAttempts++;
        console.log(`Reconnecting... (attempt ${this.reconnectAttempts})`);
        setTimeout(() => this.connect(), this.reconnectInterval);
      }
    };

    this.ws.onerror = (error) => {
      console.error('WebSocket error:', error);
    };
  }

  disconnect(): void {
    this.isManualClose = true;
    this.ws?.close();
    this.ws = null;
  }

  send(type: string, data: unknown): void {
    if (this.ws?.readyState === WebSocket.OPEN) {
      this.ws.send(JSON.stringify({ type, data }));
    } else {
      console.warn('WebSocket not connected, cannot send message');
    }
  }

  subscribe(type: string, handler: MessageHandler): () => void {
    if (!this.messageHandlers.has(type)) {
      this.messageHandlers.set(type, new Set());
    }
    this.messageHandlers.get(type)?.add(handler);

    // Return unsubscribe function
    return () => {
      this.messageHandlers.get(type)?.delete(handler);
    };
  }

  onConnect(handler: ConnectionHandler): () => void {
    this.onConnectHandlers.add(handler);
    return () => {
      this.onConnectHandlers.delete(handler);
    };
  }

  onDisconnect(handler: ConnectionHandler): () => void {
    this.onDisconnectHandlers.add(handler);
    return () => {
      this.onDisconnectHandlers.delete(handler);
    };
  }

  isConnected(): boolean {
    return this.ws?.readyState === WebSocket.OPEN;
  }
}

// ============================================================================
// Pre-configured WebSocket instances
// ============================================================================

// Backend WebSocket (Express.js) - port 3080
const WS_BASE_URL = import.meta.env.VITE_WS_URL || 'ws://localhost:3080';
// ML Engine WebSocket (FastAPI) - port 3083
const ML_WS_URL = import.meta.env.VITE_ML_WS_URL || 'ws://localhost:3083';

// Trading WebSocket - for market data and order updates
// Backend uses single /ws path with message types for differentiation
export const tradingWS = new WebSocketService({
  url: `${WS_BASE_URL}/ws`,
  reconnectInterval: 3000,
  maxReconnectAttempts: 20,
});

// ML Signals WebSocket - for real-time ML predictions
// ML Engine (FastAPI) has dedicated /ws/signals endpoint
export const mlSignalsWS = new WebSocketService({
  url: `${ML_WS_URL}/ws/signals`,
  reconnectInterval: 5000,
  maxReconnectAttempts: 10,
});

// Portfolio WebSocket - for real-time portfolio updates
// Uses same backend WebSocket with portfolio: prefixed message types
export const portfolioWS = new WebSocketService({
  url: `${WS_BASE_URL}/ws`,
  reconnectInterval: 5000,
  maxReconnectAttempts: 15,
});

// ============================================================================
// Signal Types
// ============================================================================

export interface MLSignalUpdate {
  symbol: string;
  type: 'ict' | 'ensemble' | 'amd' | 'quick';
  action: 'BUY' | 'SELL' | 'HOLD';
  confidence: number;
  score: number;
  entry?: number;
  stop_loss?: number;
  take_profit?: number;
  timestamp: string;
}

export interface PriceUpdate {
  symbol: string;
  bid: number;
  ask: number;
  timestamp: string;
}

export interface PositionUpdate {
  ticket: number;
  symbol: string;
  type: 'buy' | 'sell';
  profit: number;
  current_price: number;
}

export interface TradeNotification {
  type: 'opened' | 'closed' | 'modified' | 'error';
  ticket?: number;
  symbol: string;
  message: string;
  profit?: number;
  timestamp: string;
}

export interface PortfolioUpdate {
  portfolioId: string;
  totalValue: number;
  unrealizedPnl: number;
  unrealizedPnlPercent: number;
  allocations: {
    asset: string;
    value: number;
    currentPercent: number;
    pnl: number;
    pnlPercent: number;
  }[];
  timestamp: string;
}

// ============================================================================
// React Hooks for WebSocket
// ============================================================================

import { useState, useEffect, useCallback } from 'react';

/**
 * Hook for subscribing to ML signals
 */
export function useMLSignals(symbols: string[] = []) {
  const [signals, setSignals] = useState<Map<string, MLSignalUpdate>>(new Map());
  const [connected, setConnected] = useState(false);

  useEffect(() => {
    mlSignalsWS.connect();

    const unsubConnect = mlSignalsWS.onConnect(() => {
      setConnected(true);
      // Subscribe to symbols
      if (symbols.length > 0) {
        mlSignalsWS.send('subscribe', { symbols });
      }
    });

    const unsubDisconnect = mlSignalsWS.onDisconnect(() => {
      setConnected(false);
    });

    const unsubSignal = mlSignalsWS.subscribe('signal', (data) => {
      const signal = data as MLSignalUpdate;
      setSignals((prev) => {
        const next = new Map(prev);
        next.set(signal.symbol, signal);
        return next;
      });
    });

    return () => {
      unsubConnect();
      unsubDisconnect();
      unsubSignal();
    };
  }, [symbols.join(',')]);

  const subscribeSymbol = useCallback((symbol: string) => {
    mlSignalsWS.send('subscribe', { symbols: [symbol] });
  }, []);

  const unsubscribeSymbol = useCallback((symbol: string) => {
    mlSignalsWS.send('unsubscribe', { symbols: [symbol] });
    setSignals((prev) => {
      const next = new Map(prev);
      next.delete(symbol);
      return next;
    });
  }, []);

  return { signals, connected, subscribeSymbol, unsubscribeSymbol };
}

/**
 * Hook for subscribing to price updates
 */
export function usePriceUpdates(symbols: string[] = []) {
  const [prices, setPrices] = useState<Map<string, PriceUpdate>>(new Map());
  const [connected, setConnected] = useState(false);

  useEffect(() => {
    tradingWS.connect();

    const unsubConnect = tradingWS.onConnect(() => {
      setConnected(true);
      if (symbols.length > 0) {
        tradingWS.send('subscribe_prices', { symbols });
      }
    });

    const unsubDisconnect = tradingWS.onDisconnect(() => {
      setConnected(false);
    });

    const unsubPrice = tradingWS.subscribe('price', (data) => {
      const price = data as PriceUpdate;
      setPrices((prev) => {
        const next = new Map(prev);
        next.set(price.symbol, price);
        return next;
      });
    });

    return () => {
      unsubConnect();
      unsubDisconnect();
      unsubPrice();
    };
  }, [symbols.join(',')]);

  return { prices, connected };
}

/**
 * Hook for trade notifications
 */
export function useTradeNotifications() {
  const [notifications, setNotifications] = useState<TradeNotification[]>([]);

  useEffect(() => {
    tradingWS.connect();

    const unsubNotification = tradingWS.subscribe('trade_notification', (data) => {
      const notification = data as TradeNotification;
      setNotifications((prev) => [notification, ...prev].slice(0, 50)); // Keep last 50
    });

    return () => {
      unsubNotification();
    };
  }, []);

  const clearNotifications = useCallback(() => {
    setNotifications([]);
  }, []);

  return { notifications, clearNotifications };
}

/**
 * Hook for position updates
 */
export function usePositionUpdates() {
  const [positions, setPositions] = useState<Map<number, PositionUpdate>>(new Map());

  useEffect(() => {
    tradingWS.connect();

    const unsubPosition = tradingWS.subscribe('position', (data) => {
      const position = data as PositionUpdate;
      setPositions((prev) => {
        const next = new Map(prev);
        next.set(position.ticket, position);
        return next;
      });
    });

    const unsubClosed = tradingWS.subscribe('position_closed', (data) => {
      const { ticket } = data as { ticket: number };
      setPositions((prev) => {
        const next = new Map(prev);
        next.delete(ticket);
        return next;
      });
    });

    return () => {
      unsubPosition();
      unsubClosed();
    };
  }, []);

  return { positions };
}

/**
 * Hook for real-time portfolio updates
 */
export function usePortfolioUpdates(portfolioId: string | null) {
  const [portfolioData, setPortfolioData] = useState<PortfolioUpdate | null>(null);
  const [connected, setConnected] = useState(false);

  useEffect(() => {
    if (!portfolioId) return;

    portfolioWS.connect();

    const unsubConnect = portfolioWS.onConnect(() => {
      setConnected(true);
      // Subscribe to portfolio updates
      portfolioWS.send('portfolio:subscribe', { portfolioId });
    });

    const unsubDisconnect = portfolioWS.onDisconnect(() => {
      setConnected(false);
    });

    const unsubUpdate = portfolioWS.subscribe('portfolio:update', (data) => {
      const update = data as PortfolioUpdate;
      if (update.portfolioId === portfolioId) {
        setPortfolioData(update);
      }
    });

    return () => {
      // Unsubscribe when unmounting
      portfolioWS.send('portfolio:unsubscribe', { portfolioId });
      unsubConnect();
      unsubDisconnect();
      unsubUpdate();
    };
  }, [portfolioId]);

  const refresh = useCallback(() => {
    if (portfolioId) {
      portfolioWS.send('portfolio:refresh', { portfolioId });
    }
  }, [portfolioId]);

  return { portfolioData, connected, refresh };
}

export default {
  tradingWS,
  mlSignalsWS,
  portfolioWS,
  WebSocketService,
};
