/**
 * Market Data Service
 * Facade for market data operations combining Binance API and Cache
 */

import { binanceService, Kline, Ticker24h, OrderBook, Interval, SymbolInfo } from './binance.service';
import { marketDataCache, MarketDataCache } from './cache.service';

// ============================================================================
// Types
// ============================================================================

export interface MarketSymbol {
  symbol: string;
  baseAsset: string;
  quoteAsset: string;
  status: string;
  pricePrecision: number;
  quantityPrecision: number;
  minPrice: string;
  maxPrice: string;
  tickSize: string;
  minQty: string;
  maxQty: string;
  stepSize: string;
  minNotional: string;
}

export interface MarketPrice {
  symbol: string;
  price: number;
  timestamp: number;
}

export interface MarketTicker {
  symbol: string;
  lastPrice: number;
  priceChange: number;
  priceChangePercent: number;
  high24h: number;
  low24h: number;
  volume24h: number;
  quoteVolume24h: number;
}

export interface CandlestickData {
  time: number;
  open: number;
  high: number;
  low: number;
  close: number;
  volume: number;
}

export interface WatchlistItem {
  symbol: string;
  price: number;
  change24h: number;
  changePercent24h: number;
  volume24h: number;
  high24h: number;
  low24h: number;
}

// ============================================================================
// Market Service
// ============================================================================

class MarketService {
  private symbolsInfo: Map<string, MarketSymbol> = new Map();
  private initialized: boolean = false;

  constructor() {
    // Set up WebSocket event handlers
    this.setupWebSocketHandlers();
  }

  /**
   * Initialize the service by loading exchange info
   */
  async initialize(): Promise<void> {
    if (this.initialized) return;

    try {
      await this.loadExchangeInfo();
      this.initialized = true;
      console.log('[MarketService] Initialized successfully');
    } catch (error) {
      console.error('[MarketService] Failed to initialize:', error);
      throw error;
    }
  }

  /**
   * Load and cache exchange info
   */
  async loadExchangeInfo(): Promise<void> {
    const cacheKey = MarketDataCache.exchangeInfoKey();

    const exchangeInfo = await marketDataCache.getOrSet(
      cacheKey,
      () => binanceService.getExchangeInfo(),
      3600 // Cache for 1 hour
    );

    // Process and store symbol info
    for (const symbol of exchangeInfo.symbols) {
      if (symbol.status === 'TRADING') {
        const marketSymbol = this.parseSymbolInfo(symbol);
        this.symbolsInfo.set(symbol.symbol, marketSymbol);
      }
    }

    console.log(`[MarketService] Loaded ${this.symbolsInfo.size} trading pairs`);
  }

  /**
   * Get candlestick/kline data
   */
  async getKlines(
    symbol: string,
    interval: Interval,
    options: { startTime?: number; endTime?: number; limit?: number } = {}
  ): Promise<CandlestickData[]> {
    const cacheKey = MarketDataCache.klineKey(symbol, interval);

    // For real-time data, use short cache
    const klines = await marketDataCache.getOrSet(
      cacheKey,
      () => binanceService.getKlines(symbol, interval, options),
      5 // 5 seconds cache
    );

    return klines.map(this.transformKline);
  }

  /**
   * Get current price for a symbol
   */
  async getPrice(symbol: string): Promise<MarketPrice> {
    const cacheKey = MarketDataCache.priceKey(symbol);

    const priceData = await marketDataCache.getOrSet(
      cacheKey,
      async () => {
        const result = await binanceService.getPrice(symbol);
        return result as { symbol: string; price: string };
      },
      2 // 2 seconds cache
    );

    return {
      symbol: priceData.symbol,
      price: parseFloat(priceData.price),
      timestamp: Date.now(),
    };
  }

  /**
   * Get prices for multiple symbols
   */
  async getPrices(symbols?: string[]): Promise<MarketPrice[]> {
    if (symbols && symbols.length === 1) {
      return [await this.getPrice(symbols[0])];
    }

    const allPrices = (await binanceService.getPrice()) as { symbol: string; price: string }[];

    let filtered = allPrices;
    if (symbols && symbols.length > 0) {
      const symbolSet = new Set(symbols.map((s) => s.toUpperCase()));
      filtered = allPrices.filter((p) => symbolSet.has(p.symbol));
    }

    return filtered.map((p) => ({
      symbol: p.symbol,
      price: parseFloat(p.price),
      timestamp: Date.now(),
    }));
  }

  /**
   * Get 24h ticker for a symbol
   */
  async getTicker(symbol: string): Promise<MarketTicker> {
    const cacheKey = MarketDataCache.tickerKey(symbol);

    const ticker = await marketDataCache.getOrSet(
      cacheKey,
      async () => {
        const result = await binanceService.get24hrTicker(symbol);
        return result as Ticker24h;
      },
      5 // 5 seconds cache
    );

    return this.transformTicker(ticker);
  }

  /**
   * Get 24h tickers for multiple symbols
   */
  async getTickers(symbols?: string[]): Promise<MarketTicker[]> {
    if (symbols && symbols.length === 1) {
      return [await this.getTicker(symbols[0])];
    }

    const allTickers = (await binanceService.get24hrTicker()) as Ticker24h[];

    let filtered = allTickers;
    if (symbols && symbols.length > 0) {
      const symbolSet = new Set(symbols.map((s) => s.toUpperCase()));
      filtered = allTickers.filter((t) => symbolSet.has(t.symbol));
    }

    return filtered.map(this.transformTicker);
  }

  /**
   * Get order book for a symbol
   */
  async getOrderBook(symbol: string, limit: number = 20): Promise<OrderBook> {
    const cacheKey = MarketDataCache.orderBookKey(symbol);

    return marketDataCache.getOrSet(cacheKey, () => binanceService.getOrderBook(symbol, limit), 1);
  }

  /**
   * Get watchlist data (optimized for multiple symbols)
   */
  async getWatchlist(symbols: string[]): Promise<WatchlistItem[]> {
    const tickers = await this.getTickers(symbols);

    return tickers.map((ticker) => ({
      symbol: ticker.symbol,
      price: ticker.lastPrice,
      change24h: ticker.priceChange,
      changePercent24h: ticker.priceChangePercent,
      volume24h: ticker.volume24h,
      high24h: ticker.high24h,
      low24h: ticker.low24h,
    }));
  }

  /**
   * Get symbol info
   */
  getSymbolInfo(symbol: string): MarketSymbol | undefined {
    return this.symbolsInfo.get(symbol.toUpperCase());
  }

  /**
   * Get all available symbols
   */
  getAvailableSymbols(): string[] {
    return Array.from(this.symbolsInfo.keys());
  }

  /**
   * Search symbols by query
   */
  searchSymbols(query: string, limit: number = 20): MarketSymbol[] {
    const upperQuery = query.toUpperCase();
    const results: MarketSymbol[] = [];

    for (const [symbol, info] of this.symbolsInfo) {
      if (
        symbol.includes(upperQuery) ||
        info.baseAsset.includes(upperQuery) ||
        info.quoteAsset.includes(upperQuery)
      ) {
        results.push(info);
        if (results.length >= limit) break;
      }
    }

    return results;
  }

  /**
   * Get popular symbols (USDT pairs, major cryptos)
   */
  getPopularSymbols(): string[] {
    const popular = [
      'BTCUSDT',
      'ETHUSDT',
      'BNBUSDT',
      'XRPUSDT',
      'SOLUSDT',
      'ADAUSDT',
      'DOGEUSDT',
      'DOTUSDT',
      'MATICUSDT',
      'LINKUSDT',
      'AVAXUSDT',
      'ATOMUSDT',
      'LTCUSDT',
      'UNIUSDT',
      'APTUSDT',
    ];

    return popular.filter((s) => this.symbolsInfo.has(s));
  }

  // ==========================================================================
  // WebSocket Subscriptions
  // ==========================================================================

  /**
   * Subscribe to real-time kline updates
   */
  subscribeKlines(symbol: string, interval: Interval): void {
    binanceService.subscribeKlines(symbol, interval);
  }

  /**
   * Subscribe to real-time ticker updates
   */
  subscribeTicker(symbol: string): void {
    binanceService.subscribeTicker(symbol);
  }

  /**
   * Subscribe to real-time trade updates
   */
  subscribeTrades(symbol: string): void {
    binanceService.subscribeTrades(symbol);
  }

  /**
   * Subscribe to order book depth updates
   */
  subscribeDepth(symbol: string, levels: 5 | 10 | 20 = 10): void {
    binanceService.subscribeDepth(symbol, levels);
  }

  /**
   * Unsubscribe from a stream
   */
  unsubscribe(streamName: string): void {
    binanceService.unsubscribe(streamName);
  }

  /**
   * Unsubscribe from all streams
   */
  unsubscribeAll(): void {
    binanceService.unsubscribeAll();
  }

  /**
   * Get active WebSocket streams
   */
  getActiveStreams(): string[] {
    return binanceService.getActiveStreams();
  }

  // ==========================================================================
  // Event Handlers
  // ==========================================================================

  /**
   * Register kline event handler
   */
  onKline(handler: (data: { symbol: string; interval: string; kline: CandlestickData; isFinal: boolean }) => void): void {
    binanceService.on('kline', (data) => {
      handler({
        symbol: data.symbol,
        interval: data.interval,
        kline: this.transformKline(data.kline),
        isFinal: data.isFinal,
      });
    });
  }

  /**
   * Register ticker event handler
   */
  onTicker(handler: (data: MarketTicker) => void): void {
    binanceService.on('ticker', (data) => {
      handler(this.transformTicker(data));
    });
  }

  /**
   * Register trade event handler
   */
  onTrade(
    handler: (data: {
      symbol: string;
      tradeId: number;
      price: number;
      quantity: number;
      time: number;
      isBuyerMaker: boolean;
    }) => void
  ): void {
    binanceService.on('trade', (data) => {
      handler({
        symbol: data.symbol,
        tradeId: data.tradeId,
        price: parseFloat(data.price),
        quantity: parseFloat(data.quantity),
        time: data.time,
        isBuyerMaker: data.isBuyerMaker,
      });
    });
  }

  // ==========================================================================
  // Private Methods
  // ==========================================================================

  private setupWebSocketHandlers(): void {
    // Log WebSocket connection events
    binanceService.on('wsConnected', (streamName) => {
      console.log(`[MarketService] WebSocket connected: ${streamName}`);
    });

    binanceService.on('wsDisconnected', (streamName) => {
      console.log(`[MarketService] WebSocket disconnected: ${streamName}`);
    });

    binanceService.on('wsError', ({ streamName, error }) => {
      console.error(`[MarketService] WebSocket error on ${streamName}:`, error.message);
    });
  }

  private parseSymbolInfo(symbol: SymbolInfo): MarketSymbol {
    const filters = symbol.filters;

    const priceFilter = filters.find((f) => f.filterType === 'PRICE_FILTER');
    const lotSizeFilter = filters.find((f) => f.filterType === 'LOT_SIZE');
    const notionalFilter = filters.find((f) => f.filterType === 'NOTIONAL');

    return {
      symbol: symbol.symbol,
      baseAsset: symbol.baseAsset,
      quoteAsset: symbol.quoteAsset,
      status: symbol.status,
      pricePrecision: symbol.quotePrecision,
      quantityPrecision: symbol.baseAssetPrecision,
      minPrice: priceFilter?.minPrice || '0',
      maxPrice: priceFilter?.maxPrice || '0',
      tickSize: priceFilter?.tickSize || '0',
      minQty: lotSizeFilter?.minQty || '0',
      maxQty: lotSizeFilter?.maxQty || '0',
      stepSize: lotSizeFilter?.stepSize || '0',
      minNotional: notionalFilter?.minNotional || '0',
    };
  }

  private transformKline(kline: Kline): CandlestickData {
    return {
      time: kline.openTime,
      open: parseFloat(kline.open),
      high: parseFloat(kline.high),
      low: parseFloat(kline.low),
      close: parseFloat(kline.close),
      volume: parseFloat(kline.volume),
    };
  }

  private transformTicker(ticker: Ticker24h): MarketTicker {
    return {
      symbol: ticker.symbol,
      lastPrice: parseFloat(ticker.lastPrice),
      priceChange: parseFloat(ticker.priceChange),
      priceChangePercent: parseFloat(ticker.priceChangePercent),
      high24h: parseFloat(ticker.highPrice),
      low24h: parseFloat(ticker.lowPrice),
      volume24h: parseFloat(ticker.volume),
      quoteVolume24h: parseFloat(ticker.quoteVolume),
    };
  }
}

// Export singleton instance
export const marketService = new MarketService();
