/**
 * Symbol Index Service
 *
 * Provides fast in-memory search for symbol metadata.
 * Loads initial data from Iceberg and stays synced via Kafka subscription.
 */

import type { FastifyBaseLogger } from 'fastify';
import type { IcebergClient } from '../clients/iceberg-client.js';
import type { SearchResult, SymbolInfo, SymbolMetadata } from '../types/ohlc.js';
import { DEFAULT_SUPPORTED_RESOLUTIONS } from '../types/ohlc.js';

export interface SymbolIndexServiceConfig {
  icebergClient: IcebergClient;
  logger: FastifyBaseLogger;
}

/**
 * Symbol Index Service
 *
 * Maintains an in-memory index of all available symbols for fast search.
 */
export class SymbolIndexService {
  private icebergClient: IcebergClient;
  private logger: FastifyBaseLogger;
  private symbols: Map<string, SymbolMetadata> = new Map(); // key: "MARKET_ID.EXCHANGE" (Nautilus format)
  private initialized: boolean = false;

  constructor(config: SymbolIndexServiceConfig) {
    this.icebergClient = config.icebergClient;
    this.logger = config.logger;
  }

  /**
   * Initialize the index by loading symbols from Iceberg
   */
  async initialize(): Promise<void> {
    this.logger.info('Initializing symbol index from Iceberg');

    try {
      // Load all symbols from Iceberg symbol_metadata table
      const symbols = await this.icebergClient.queryAllSymbols();

      this.logger.info({
        symbolsType: typeof symbols,
        symbolsIsArray: Array.isArray(symbols),
        symbolsLength: symbols?.length,
        firstSymbol: symbols[0]
      }, 'Loaded symbols debug info');

      // Track unique keys for debugging
      const uniqueKeys = new Set<string>();

      for (const symbol of symbols) {
        const key = `${symbol.market_id}.${symbol.exchange_id}`;
        uniqueKeys.add(key);
        this.symbols.set(key, symbol);
      }

      this.initialized = true;
      this.logger.info({
        count: this.symbols.size,
        totalRows: symbols.length,
        uniqueKeys: uniqueKeys.size,
        sampleKeys: Array.from(uniqueKeys).slice(0, 5)
      }, 'Symbol index initialized');
    } catch (error: any) {
      this.logger.warn({ error: error.message }, 'Failed to initialize symbol index (will retry on first request)');
      // Don't throw - allow lazy loading
    }
  }

  /**
   * Ensure index is initialized (with retry on failure)
   */
  private async ensureInitialized(): Promise<void> {
    if (this.initialized) {
      return;
    }

    this.logger.info('Lazy-loading symbol index');
    await this.initialize();
  }

  /**
   * Update or add a symbol to the index
   */
  updateSymbol(symbol: SymbolMetadata): void {
    const key = `${symbol.market_id}.${symbol.exchange_id}`;
    this.symbols.set(key, symbol);
    this.logger.debug({ key }, 'Updated symbol in index');
  }

  /**
   * Search for symbols matching a query
   *
   * Simple case-insensitive substring matching across:
   * - Ticker (EXCHANGE:MARKET_ID)
   * - Base asset
   * - Quote asset
   * - Description
   */
  async search(query: string, limit: number = 30): Promise<SearchResult[]> {
    await this.ensureInitialized();

    if (!this.initialized) {
      this.logger.warn('Symbol index not initialized, returning empty results');
      return [];
    }

    const queryLower = query.toLowerCase();
    const results: SearchResult[] = [];

    for (const [key, metadata] of this.symbols) {
      // Match against various fields
      const ticker = key;
      const base = metadata.base_asset || '';
      const quote = metadata.quote_asset || '';
      const desc = metadata.description || '';
      const marketId = metadata.market_id || '';

      if (
        ticker.toLowerCase().includes(queryLower) ||
        base.toLowerCase().includes(queryLower) ||
        quote.toLowerCase().includes(queryLower) ||
        desc.toLowerCase().includes(queryLower) ||
        marketId.toLowerCase().includes(queryLower)
      ) {
        results.push(this.metadataToSearchResult(metadata));

        if (results.length >= limit) {
          break;
        }
      }
    }

    this.logger.debug({ query, count: results.length }, 'Symbol search completed');
    return results;
  }

  /**
   * Resolve symbol metadata by ticker
   */
  async resolveSymbol(ticker: string): Promise<SymbolInfo | null> {
    await this.ensureInitialized();

    if (!this.initialized) {
      this.logger.warn('Symbol index not initialized after retry');
      return null;
    }

    // ticker format: "MARKET_ID.EXCHANGE" (Nautilus) or just "MARKET_ID"
    let key = ticker;

    // If no dot separator, search for first match by market_id
    if (!ticker.includes('.')) {
      for (const [k, metadata] of this.symbols) {
        if (metadata.market_id === ticker) {
          key = k;
          break;
        }
      }
    }

    const metadata = this.symbols.get(key);
    if (!metadata) {
      this.logger.debug({ ticker }, 'Symbol not found');
      return null;
    }

    return this.metadataToSymbolInfo(metadata);
  }

  /**
   * Convert SymbolMetadata to SearchResult
   */
  private metadataToSearchResult(metadata: SymbolMetadata): SearchResult {
    const symbol = metadata.market_id; // Clean format: "BTC/USDT"
    const ticker = `${metadata.market_id}.${metadata.exchange_id}`; // "BTC/USDT.BINANCE"
    const fullName = `${metadata.market_id} (${metadata.exchange_id})`;

    return {
      symbol,
      ticker,
      full_name: fullName,
      description: metadata.description || `${metadata.base_asset}/${metadata.quote_asset} ${metadata.market_type || 'spot'} pair on ${metadata.exchange_id}`,
      exchange: metadata.exchange_id,
      type: metadata.market_type || 'spot',
    };
  }

  /**
   * Convert SymbolMetadata to SymbolInfo
   */
  private metadataToSymbolInfo(metadata: SymbolMetadata): SymbolInfo {
    const symbol = metadata.market_id;
    const ticker = `${metadata.market_id}.${metadata.exchange_id}`; // "BTC/USDT.BINANCE"

    const supportedResolutions = this.periodSecondsToResolutions(metadata.supported_period_seconds || []);

    // pricescale = 10^price_precision (e.g., price_precision=2 → pricescale=100)
    const pricescale = metadata.price_precision != null ? Math.pow(10, metadata.price_precision) : 100;

    return {
      symbol,
      ticker,
      name: symbol,
      description: metadata.description || `${metadata.base_asset}/${metadata.quote_asset} ${metadata.market_type || 'spot'} pair on ${metadata.exchange_id}`,
      type: metadata.market_type || 'spot',
      exchange: metadata.exchange_id,
      timezone: 'Etc/UTC',
      session: '24x7',
      supported_resolutions: supportedResolutions.length > 0 ? supportedResolutions : DEFAULT_SUPPORTED_RESOLUTIONS,
      has_intraday: true,
      has_daily: true,
      has_weekly_and_monthly: false,
      pricescale,
      minmov: 1,
      base_currency: metadata.base_asset,
      quote_currency: metadata.quote_asset,
      data_status: 'streaming',
      price_precision: metadata.price_precision,
      size_precision: metadata.size_precision,
      tick_size: metadata.tick_size,
      lot_size: metadata.lot_size,
      maker_fee: metadata.maker_fee,
      taker_fee: metadata.taker_fee,
    };
  }

  /**
   * Convert period_seconds array to TradingView resolution strings
   */
  private periodSecondsToResolutions(periods: number[]): string[] {
    const resolutions: string[] = [];

    for (const seconds of periods) {
      if (seconds < 3600) {
        // Minutes
        resolutions.push(String(seconds / 60));
      } else if (seconds === 3600) {
        resolutions.push('60');
      } else if (seconds === 14400) {
        resolutions.push('240');
      } else if (seconds === 86400) {
        resolutions.push('1D');
      } else if (seconds === 604800) {
        resolutions.push('1W');
      } else if (seconds === 2592000) {
        resolutions.push('1M');
      }
    }

    return resolutions;
  }

  /**
   * Get statistics about the symbol index
   */
  getStats() {
    return {
      symbolCount: this.symbols.size,
      initialized: this.initialized,
    };
  }
}
