/**
 * Crypto.com OTC 2.0 Service
 * Main service class that orchestrates REST and WebSocket clients
 *
 * Based on: https://exchange-docs.crypto.com/exchange/v1/rest-ws/index_OTC2.html
 *
 * Usage:
 * ```typescript
 * const otcService = new CryptoComOtcService({
 *   apiKey: process.env.CRYPTO_COM_API_KEY!,
 *   apiSecret: process.env.CRYPTO_COM_API_SECRET!,
 *   environment: 'production',
 * });
 *
 * // Initialize (connects WebSocket and subscribes to channels)
 * await otcService.initialize();
 *
 * // Listen for events
 * otcService.on('newQuote', (quote) => {
 *   console.log('Received quote:', quote);
 * });
 *
 * // Request a quote
 * await otcService.requestQuote({
 *   cl_quote_req_id: 'my-unique-id',
 *   leg_list: [{ instrument_name: 'BTC_USD', side: 'BUY', quantity: '1' }],
 * });
 * ```
 */

import { EventEmitter } from 'events';
import { CryptoComRestClient, createRestClient } from './clients/rest-client';
import { CryptoComWebSocketClient, createWebSocketClient } from './clients/websocket-client';
import { OtcPriceProvider } from './services/otc-price-provider';
import { otcTradePersistenceService } from './services/otc-trade-persistence.service';
import {
  CryptoComOtcConfig,
  CryptoComApiResponse,
  OtcInstrument,
  RequestQuoteParams,
  RequestDealParams,
  Quote,
  Deal,
  QuoteRequestUpdate,
  GetOpenQuoteRequestsParams,
  GetOpenQuoteRequestsResult,
  GetOpenQuotesParams,
  GetOpenQuotesResult,
  GetQuoteRequestHistoryParams,
  GetQuoteRequestHistoryResult,
  GetQuoteHistoryParams,
  GetQuoteHistoryResult,
  GetOpenDealsParams,
  GetOpenDealsResult,
  GetDealHistoryParams,
  GetDealHistoryResult,
  SettleLaterLimit,
  UnsettledAmounts,
  TradeSide,
  DealLeg,
} from './types';
import { ENVIRONMENTS, isValidClientId, RESPONSE_CODES } from './config';

export interface CryptoComOtcServiceEvents {
  initialized: () => void;
  connected: () => void;
  disconnected: (code: number) => void;
  reconnected: () => void;
  quoteRequestUpdate: (data: QuoteRequestUpdate) => void;
  newQuote: (quote: Quote) => void;
  dealUpdate: (deal: Deal) => void;
  error: (error: Error) => void;
}

export class CryptoComOtcService extends EventEmitter {
  private restClient: CryptoComRestClient;
  private wsClient: CryptoComWebSocketClient;
  private instruments: Map<string, OtcInstrument> = new Map();
  private activeQuotes: Map<string, Quote> = new Map();
  private priceProvider: OtcPriceProvider;
  private initialized: boolean = false;
  private config: CryptoComOtcConfig;

  constructor(config: CryptoComOtcConfig) {
    super();
    this.config = config;

    const environment = config.environment || 'production';
    const envConfig = ENVIRONMENTS[environment];

    // Initialize REST client
    this.restClient = createRestClient({
      apiKey: config.apiKey,
      apiSecret: config.apiSecret,
      baseUrl: envConfig.restBaseUrl,
      timeout: config.requestTimeout,
    });

    this.priceProvider = new OtcPriceProvider({
      restClient: this.restClient,
    });

    // Initialize WebSocket client
    this.wsClient = createWebSocketClient({
      apiKey: config.apiKey,
      apiSecret: config.apiSecret,
      wsUrl: envConfig.wsUrl,
      reconnectInterval: config.reconnectInterval,
      heartbeatInterval: config.heartbeatInterval,
    });

    this.setupEventHandlers();
  }

  /**
   * Set up event handlers for WebSocket client
   */
  private setupEventHandlers(): void {
    // Quote request status updates
    this.wsClient.on('quoteRequest', (data: QuoteRequestUpdate[]) => {
      for (const item of data) {
        console.log(
          `[CryptoComOTC] Quote request ${item.quote_req_id}: ${item.status || 'update'}`
        );
        this.emit('quoteRequestUpdate', item);
      }
    });

    // New quotes received
    this.wsClient.on('quote', (data: Quote[]) => {
      for (const quote of data) {
        console.log(`[CryptoComOTC] Quote received: ${quote.quote_id}`);
        this.activeQuotes.set(quote.quote_id, quote);
        this.priceProvider.updateFromQuote(quote);
        this.emit('newQuote', quote);
      }
    });

    // Deal updates
    this.wsClient.on('deal', (data: Deal[]) => {
      for (const deal of data) {
        console.log(`[CryptoComOTC] Deal ${deal.deal_id}: ${deal.deal_status}`);
        otcTradePersistenceService.persistDeal(deal);
        this.emit('dealUpdate', deal);
      }
    });

    // Connection events
    this.wsClient.on('connected', () => {
      this.emit('connected');
    });

    this.wsClient.on('disconnected', (code: number) => {
      this.emit('disconnected', code);
    });

    this.wsClient.on('reconnected', () => {
      this.emit('reconnected');
    });

    this.wsClient.on('error', (error: Error) => {
      this.emit('error', error);
    });
  }

  // ============================================================================
  // Initialization
  // ============================================================================

  /**
   * Initialize the service
   * - Loads available instruments
   * - Connects to WebSocket
   * - Subscribes to all OTC channels
   */
  async initialize(): Promise<void> {
    if (this.initialized) {
      console.log('[CryptoComOTC] Already initialized');
      return;
    }

    console.log('[CryptoComOTC] Initializing...');

    // Load available instruments
    await this.loadInstruments();

    // Connect WebSocket
    await this.wsClient.connect();

    // Subscribe to all OTC channels
    await this.wsClient.subscribeToAllOtcChannels();

    this.initialized = true;
    console.log('[CryptoComOTC] Initialized successfully');
    this.emit('initialized');
  }

  /**
   * Load available OTC instruments from API
   */
  async loadInstruments(): Promise<OtcInstrument[]> {
    const response = await this.restClient.getOtcInstruments();

    if (response.code !== RESPONSE_CODES.SUCCESS) {
      throw new Error(`Failed to load instruments: ${response.message || `code ${response.code}`}`);
    }

    const instruments = response.result?.instrument_list || [];

    this.instruments.clear();
    for (const instrument of instruments) {
      this.instruments.set(instrument.instrument_name, instrument);
    }

    console.log(`[CryptoComOTC] Loaded ${instruments.length} instruments`);
    return instruments;
  }

  /**
   * Get available instruments
   */
  getInstruments(): OtcInstrument[] {
    return Array.from(this.instruments.values());
  }

  /**
   * Get a specific instrument by name
   */
  getInstrument(instrumentName: string): OtcInstrument | undefined {
    return this.instruments.get(instrumentName);
  }

  /**
   * Get an active quote by ID
   */
  getActiveQuote(quoteId: string): Quote | undefined {
    return this.activeQuotes.get(quoteId);
  }

  // ============================================================================
  // RFQ (Request for Quote) Operations
  // ============================================================================

  /**
   * Request a quote for OTC trade
   *
   * @example
   * ```typescript
   * const response = await service.requestQuote({
   *   cl_quote_req_id: 'my-unique-id-123',
   *   firm_quote: false,
   *   settlement_arrangement: 'IMMEDIATE',
   *   duration: '10000',
   *   leg_list: [{
   *     instrument_name: 'BTC_USD',
   *     side: 'BUY',
   *     quantity: '1'
   *   }]
   * });
   * ```
   */
  async requestQuote(params: RequestQuoteParams): Promise<CryptoComApiResponse> {
    // Validate client ID format
    if (!isValidClientId(params.cl_quote_req_id)) {
      throw new Error(
        'Invalid cl_quote_req_id format. Use only a-z, A-Z, 0-9, _, -'
      );
    }

    // Validate instruments exist
    for (const leg of params.leg_list) {
      if (!this.instruments.has(leg.instrument_name)) {
        throw new Error(`Invalid instrument: ${leg.instrument_name}`);
      }

      // Ensure either quantity or notional is provided, but not both
      if (leg.quantity && leg.notional) {
        throw new Error(
          `Cannot provide both quantity and notional for ${leg.instrument_name}`
        );
      }
      if (!leg.quantity && !leg.notional) {
        throw new Error(
          `Must provide either quantity or notional for ${leg.instrument_name}`
        );
      }
    }

    return this.wsClient.requestQuote(params);
  }

  /**
   * Execute a deal based on request parameters
   *
   * @example
   * ```typescript
   * const response = await service.requestDeal({
   *   deal_type: 'QUOTE_REQUEST',
   *   cl_deal_id: 'my-deal-id-123',
   *   quote_id: '4611686018492073851',
   *   quote_req_id: '4611686018427623960',
   *   leg_list: [{
   *     instrument_name: 'BTC_USD',
   *     price: '95734.2',
   *     quantity: '1',
   *     side: 'BUY'
   *   }]
   * });
   * ```
   */
  async requestDeal(params: RequestDealParams): Promise<CryptoComApiResponse> {
    // Validate client ID format
    if (!isValidClientId(params.cl_deal_id)) {
      throw new Error(
        'Invalid cl_deal_id format. Use only a-z, A-Z, 0-9, _, -'
      );
    }

    return this.wsClient.requestDeal(params);
  }

  /**
   * Convenience method to execute a deal from a received quote
   *
   * @param quote - The quote received from the newQuote event
   * @param side - Whether to BUY (use ask price) or SELL (use bid price)
   * @param clDealId - Unique client-provided deal ID
   */
  async executeDealFromQuote(
    quote: Quote,
    side: TradeSide,
    clDealId: string
  ): Promise<CryptoComApiResponse> {
    if (!isValidClientId(clDealId)) {
      throw new Error(
        'Invalid cl_deal_id format. Use only a-z, A-Z, 0-9, _, -'
      );
    }

    const legList: DealLeg[] = quote.leg_list.map((leg) => {
      const price = side === 'BUY' ? leg.ask : leg.bid;

      if (!price) {
        throw new Error(
          `No ${side === 'BUY' ? 'ask' : 'bid'} price available for ${leg.instrument_name}`
        );
      }

      return {
        instrument_name: leg.instrument_name,
        price,
        quantity: leg.quantity,
        notional: leg.notional,
        side,
      };
    });

    return this.requestDeal({
      deal_type: 'QUOTE_REQUEST',
      cl_deal_id: clDealId,
      quote_id: quote.quote_id,
      quote_req_id: quote.quote_req_id,
      leg_list: legList,
    });
  }

  // ============================================================================
  // Query APIs (REST)
  // ============================================================================

  /**
   * Get open quote requests (status = NEW or ACTIVE)
   */
  async getOpenQuoteRequests(
    params?: GetOpenQuoteRequestsParams
  ): Promise<CryptoComApiResponse<GetOpenQuoteRequestsResult>> {
    return this.restClient.getOpenQuoteRequests(params);
  }

  /**
   * Get open quotes (status = ACTIVE)
   */
  async getOpenQuotes(
    params?: GetOpenQuotesParams
  ): Promise<CryptoComApiResponse<GetOpenQuotesResult>> {
    return this.restClient.getOpenQuotes(params);
  }

  /**
   * Get quote request history (status = COMPLETED or REJECTED)
   */
  async getQuoteRequestHistory(
    params?: GetQuoteRequestHistoryParams
  ): Promise<CryptoComApiResponse<GetQuoteRequestHistoryResult>> {
    return this.restClient.getQuoteRequestHistory(params);
  }

  /**
   * Get quote history
   */
  async getQuoteHistory(
    params?: GetQuoteHistoryParams
  ): Promise<CryptoComApiResponse<GetQuoteHistoryResult>> {
    return this.restClient.getQuoteHistory(params);
  }

  /**
   * Get open deals (not yet SETTLED)
   */
  async getOpenDeals(
    params?: Omit<GetOpenDealsParams, 'deal_type'>
  ): Promise<CryptoComApiResponse<GetOpenDealsResult>> {
    return this.restClient.getOpenDeals({
      deal_type: 'QUOTE_REQUEST',
      ...params,
    });
  }

  /**
   * Get deal history
   */
  async getDealHistory(
    params?: Omit<GetDealHistoryParams, 'deal_type'>
  ): Promise<CryptoComApiResponse<GetDealHistoryResult>> {
    return this.restClient.getDealHistory({
      deal_type: 'QUOTE_REQUEST',
      ...params,
    });
  }

  // ============================================================================
  // Settle Later APIs
  // ============================================================================

  /**
   * Get settle later limit for account
   */
  async getSettleLaterLimit(): Promise<SettleLaterLimit | null> {
    const response = await this.restClient.getSettleLaterLimit();
    return response.code === RESPONSE_CODES.SUCCESS
      ? (response.result as SettleLaterLimit)
      : null;
  }

  /**
   * Get unsettled amounts aggregated by date and instrument
   */
  async getUnsettledAmounts(): Promise<UnsettledAmounts | null> {
    const response = await this.restClient.getUnsettledAmounts();
    return response.code === RESPONSE_CODES.SUCCESS
      ? (response.result as UnsettledAmounts)
      : null;
  }

  // ============================================================================
  // Connection Management
  // ============================================================================

  /**
   * Check if the service is initialized and connected
   */
  isConnected(): boolean {
    return this.initialized && this.wsClient.isConnected();
  }

  /**
   * Disconnect and cleanup resources
   */
  disconnect(): void {
    this.wsClient.disconnect();
    this.initialized = false;
    this.activeQuotes.clear();
    console.log('[CryptoComOTC] Service disconnected');
  }

  /**
   * Get the REST client for advanced usage
   */
  getRestClient(): CryptoComRestClient {
    return this.restClient;
  }

  /**
   * Get the WebSocket client for advanced usage
   */
  getWebSocketClient(): CryptoComWebSocketClient {
    return this.wsClient;
  }

  /**
   * Get the OTC price provider for FX integration
   */
  getPriceProvider(): OtcPriceProvider {
    return this.priceProvider;
  }
}

/**
 * Factory function to create an OTC service
 */
export function createCryptoComOtcService(
  config: CryptoComOtcConfig
): CryptoComOtcService {
  return new CryptoComOtcService(config);
}

// Default export
export default CryptoComOtcService;
