/**
 * Binance Client Service
 *
 * CCXT wrapper for Binance operations.
 * Provides a unified interface for both Spot and Futures trading.
 *
 * @version 1.0.0
 * @author Trading Platform Trading Platform
 */

import ccxt, { Ticker, OrderBook, OHLCV, Balance, Order, Trade } from 'ccxt';
import { createBinanceSpotClient, createBinanceFuturesClient, binanceConfig } from '../config';
import { logger } from '../utils/logger';

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

export interface BinanceTicker {
  symbol: string;
  price: number;
  bid: number;
  ask: number;
  high24h: number;
  low24h: number;
  volume24h: number;
  change24h: number;
  timestamp: number;
}

export interface BinanceOrderBook {
  symbol: string;
  bids: [number, number][];
  asks: [number, number][];
  spread: number;
  spreadPercentage: number;
  timestamp: number;
}

export interface BinanceKline {
  timestamp: number;
  open: number;
  high: number;
  low: number;
  close: number;
  volume: number;
}

export interface BinanceAccountBalance {
  asset: string;
  free: number;
  locked: number;
  total: number;
}

export interface BinanceAccount {
  accountType: string;
  balances: BinanceAccountBalance[];
  canTrade: boolean;
  canWithdraw: boolean;
  updateTime: number;
}

export interface BinanceOrder {
  id: string;
  symbol: string;
  side: string;
  type: string;
  price: number | null;
  amount: number;
  filled: number;
  remaining: number;
  status: string;
  createdAt: number;
}

export interface CreateOrderParams {
  symbol: string;
  side: 'buy' | 'sell';
  type: 'market' | 'limit' | 'stop_loss' | 'take_profit';
  amount: number;
  price?: number;
  stopPrice?: number;
}

export interface OrderResult {
  success: boolean;
  order?: BinanceOrder;
  error?: string;
}

// ==========================================
// Binance Client Class
// ==========================================

export class BinanceClient {
  private spotClient: ccxt.binance;
  private futuresClient: ccxt.binance;
  private marketsLoaded: boolean = false;

  constructor() {
    this.spotClient = createBinanceSpotClient();
    this.futuresClient = createBinanceFuturesClient();
  }

  /**
   * Check if client is properly configured
   */
  isConfigured(): boolean {
    return binanceConfig.apiKey !== '' && binanceConfig.apiSecret !== '';
  }

  /**
   * Test connectivity to Binance
   */
  async isConnected(): Promise<boolean> {
    try {
      await this.spotClient.fetchTime();
      return true;
    } catch {
      return false;
    }
  }

  /**
   * Load markets if not already loaded
   */
  private async ensureMarketsLoaded(): Promise<void> {
    if (!this.marketsLoaded) {
      await this.spotClient.loadMarkets();
      this.marketsLoaded = true;
    }
  }

  // ==========================================
  // Market Data Methods
  // ==========================================

  /**
   * Get ticker for a symbol
   */
  async getTicker(symbol: string): Promise<BinanceTicker> {
    try {
      await this.ensureMarketsLoaded();
      const ticker: Ticker = await this.spotClient.fetchTicker(symbol);

      return {
        symbol: ticker.symbol,
        price: ticker.last ?? 0,
        bid: ticker.bid ?? 0,
        ask: ticker.ask ?? 0,
        high24h: ticker.high ?? 0,
        low24h: ticker.low ?? 0,
        volume24h: ticker.baseVolume ?? 0,
        change24h: ticker.percentage ?? 0,
        timestamp: ticker.timestamp ?? Date.now(),
      };
    } catch (error) {
      logger.error(`Failed to get ticker for ${symbol}`, { error });
      throw error;
    }
  }

  /**
   * Get order book for a symbol
   */
  async getOrderBook(symbol: string, limit: number = 20): Promise<BinanceOrderBook> {
    try {
      await this.ensureMarketsLoaded();
      const orderbook: OrderBook = await this.spotClient.fetchOrderBook(symbol, limit);

      const topBid = orderbook.bids[0]?.[0] ?? 0;
      const topAsk = orderbook.asks[0]?.[0] ?? 0;
      const spread = topAsk - topBid;
      const spreadPercentage = topBid > 0 ? (spread / topBid) * 100 : 0;

      return {
        symbol,
        bids: orderbook.bids.slice(0, limit) as [number, number][],
        asks: orderbook.asks.slice(0, limit) as [number, number][],
        spread,
        spreadPercentage,
        timestamp: orderbook.timestamp ?? Date.now(),
      };
    } catch (error) {
      logger.error(`Failed to get order book for ${symbol}`, { error });
      throw error;
    }
  }

  /**
   * Get OHLCV (klines/candles) for a symbol
   */
  async getKlines(
    symbol: string,
    interval: string = '5m',
    limit: number = 100
  ): Promise<BinanceKline[]> {
    try {
      await this.ensureMarketsLoaded();
      const ohlcv: OHLCV[] = await this.spotClient.fetchOHLCV(symbol, interval, undefined, limit);

      return ohlcv.map((candle) => ({
        timestamp: candle[0] as number,
        open: candle[1] as number,
        high: candle[2] as number,
        low: candle[3] as number,
        close: candle[4] as number,
        volume: candle[5] as number,
      }));
    } catch (error) {
      logger.error(`Failed to get klines for ${symbol}`, { error });
      throw error;
    }
  }

  // ==========================================
  // Account Methods
  // ==========================================

  /**
   * Get account balance
   */
  async getAccount(): Promise<BinanceAccount> {
    try {
      if (!this.isConfigured()) {
        throw new Error('Binance API keys not configured');
      }

      const balance: Balance = await this.spotClient.fetchBalance();

      // Filter non-zero balances
      const balances: BinanceAccountBalance[] = Object.entries(balance.total)
        .filter(([_, amount]) => (amount as number) > 0)
        .map(([asset, total]) => ({
          asset,
          free: (balance.free[asset] as number) ?? 0,
          locked: (balance.used[asset] as number) ?? 0,
          total: total as number,
        }));

      return {
        accountType: 'SPOT',
        balances,
        canTrade: true,
        canWithdraw: true,
        updateTime: Date.now(),
      };
    } catch (error) {
      logger.error('Failed to get account info', { error });
      throw error;
    }
  }

  /**
   * Get open orders
   */
  async getOpenOrders(symbol?: string): Promise<BinanceOrder[]> {
    try {
      if (!this.isConfigured()) {
        throw new Error('Binance API keys not configured');
      }

      const orders: Order[] = await this.spotClient.fetchOpenOrders(symbol);

      return orders.map((order) => ({
        id: order.id,
        symbol: order.symbol,
        side: order.side,
        type: order.type,
        price: order.price,
        amount: order.amount,
        filled: order.filled,
        remaining: order.remaining,
        status: order.status,
        createdAt: order.timestamp ?? Date.now(),
      }));
    } catch (error) {
      logger.error('Failed to get open orders', { error });
      throw error;
    }
  }

  /**
   * Get trade history
   */
  async getTradeHistory(symbol: string, limit: number = 50): Promise<Trade[]> {
    try {
      if (!this.isConfigured()) {
        throw new Error('Binance API keys not configured');
      }

      return await this.spotClient.fetchMyTrades(symbol, undefined, limit);
    } catch (error) {
      logger.error(`Failed to get trade history for ${symbol}`, { error });
      throw error;
    }
  }

  // ==========================================
  // Order Methods
  // ==========================================

  /**
   * Create a new order
   */
  async createOrder(params: CreateOrderParams): Promise<OrderResult> {
    try {
      if (!this.isConfigured()) {
        throw new Error('Binance API keys not configured');
      }

      await this.ensureMarketsLoaded();

      let order: Order;

      switch (params.type) {
        case 'market':
          order = await this.spotClient.createMarketOrder(
            params.symbol,
            params.side,
            params.amount
          );
          break;

        case 'limit':
          if (!params.price) {
            return { success: false, error: 'Price is required for limit orders' };
          }
          order = await this.spotClient.createLimitOrder(
            params.symbol,
            params.side,
            params.amount,
            params.price
          );
          break;

        case 'stop_loss':
          if (!params.stopPrice) {
            return { success: false, error: 'Stop price is required for stop loss orders' };
          }
          order = await this.spotClient.createOrder(
            params.symbol,
            'stop_loss',
            params.side,
            params.amount,
            undefined,
            { stopPrice: params.stopPrice }
          );
          break;

        case 'take_profit':
          if (!params.stopPrice) {
            return { success: false, error: 'Stop price is required for take profit orders' };
          }
          order = await this.spotClient.createOrder(
            params.symbol,
            'take_profit',
            params.side,
            params.amount,
            undefined,
            { stopPrice: params.stopPrice }
          );
          break;

        default:
          return { success: false, error: `Unsupported order type: ${params.type}` };
      }

      return {
        success: true,
        order: {
          id: order.id,
          symbol: order.symbol,
          side: order.side,
          type: order.type,
          price: order.price ?? order.average ?? null,
          amount: order.amount,
          filled: order.filled,
          remaining: order.remaining,
          status: order.status,
          createdAt: order.timestamp ?? Date.now(),
        },
      };
    } catch (error) {
      const message = error instanceof Error ? error.message : 'Unknown error';
      logger.error('Failed to create order', { error, params });
      return { success: false, error: message };
    }
  }

  /**
   * Cancel an order
   */
  async cancelOrder(orderId: string, symbol: string): Promise<OrderResult> {
    try {
      if (!this.isConfigured()) {
        throw new Error('Binance API keys not configured');
      }

      const result = await this.spotClient.cancelOrder(orderId, symbol);

      return {
        success: true,
        order: {
          id: result.id,
          symbol: result.symbol,
          side: result.side,
          type: result.type,
          price: result.price,
          amount: result.amount,
          filled: result.filled,
          remaining: result.remaining,
          status: 'CANCELLED',
          createdAt: result.timestamp ?? Date.now(),
        },
      };
    } catch (error) {
      const message = error instanceof Error ? error.message : 'Unknown error';
      logger.error('Failed to cancel order', { error, orderId, symbol });
      return { success: false, error: message };
    }
  }

  /**
   * Cancel all orders for a symbol
   */
  async cancelAllOrders(symbol: string): Promise<{ success: boolean; cancelledCount: number; error?: string }> {
    try {
      if (!this.isConfigured()) {
        throw new Error('Binance API keys not configured');
      }

      const result = await this.spotClient.cancelAllOrders(symbol);

      return {
        success: true,
        cancelledCount: Array.isArray(result) ? result.length : 0,
      };
    } catch (error) {
      const message = error instanceof Error ? error.message : 'Unknown error';
      logger.error('Failed to cancel all orders', { error, symbol });
      return { success: false, cancelledCount: 0, error: message };
    }
  }

  /**
   * Get current price for a symbol (helper method)
   */
  async getCurrentPrice(symbol: string): Promise<number> {
    const ticker = await this.getTicker(symbol);
    return ticker.price;
  }
}

// ==========================================
// Singleton Instance
// ==========================================

let clientInstance: BinanceClient | null = null;

export function getBinanceClient(): BinanceClient {
  if (!clientInstance) {
    clientInstance = new BinanceClient();
  }
  return clientInstance;
}

export function resetBinanceClient(): void {
  clientInstance = null;
}
