import type { BaseHandler } from '@stock-bot/handlers';
import type { DataIngestionServices } from '../../../types';
import type { EodHandler } from '../eod.handler';
import { EOD_CONFIG } from '../shared';
import { getEodExchangeSuffix } from '../shared/utils';

interface BulkFundamentalsInput {
  symbols: Array<{ symbol: string; exchange: string; country?: string }>;
}

interface FetchSingleFundamentalsInput {
  symbol: string;
  exchange: string;
  country?: string;
}

export async function scheduleFetchFundamentals(
  this: EodHandler
): Promise<{ success: boolean; jobsScheduled: number }> {
  const logger = this.logger;
  
  try {
    logger.info('Scheduling fundamentals fetch jobs');
    
    // Get Canadian exchanges for now
    const canadianExchanges = ['TO', 'V', 'CN', 'NEO'];
    
    // Use OperationTracker to find stale symbols
    const allStaleSymbols = await this.operationRegistry.getStaleSymbols('eod', 'fundamentals_update', {
      limit: 50000 // Get all symbols needing update
    });
    
    // Filter for Canadian exchanges and non-delisted symbols
    const staleSymbols = allStaleSymbols.filter(item => 
      canadianExchanges.includes(item.symbol.Exchange) && 
      item.symbol.delisted === false
    ); // Remove the slice limit to process all symbols
    
    if (!staleSymbols || staleSymbols.length === 0) {
      logger.info('No symbols need fundamentals update');
      return { success: true, jobsScheduled: 0 };
    }
    
    logger.info(`Found ${staleSymbols.length} symbols needing fundamentals update`);
    
    // Separate ETFs from regular stocks
    const etfs = staleSymbols.filter(s => s.symbol.Type === 'ETF');
    const nonEtfs = staleSymbols.filter(s => s.symbol.Type !== 'ETF');
    
    logger.info(`Found ${etfs.length} ETFs and ${nonEtfs.length} non-ETFs`, {
      etfSamples: etfs.slice(0, 5).map(e => ({
        symbol: e.symbol.Code,
        exchange: e.symbol.Exchange,
        name: e.symbol.Name
      })),
      nonEtfSamples: nonEtfs.slice(0, 5).map(s => ({
        symbol: s.symbol.Code,
        exchange: s.symbol.Exchange,
        name: s.symbol.Name,
        type: s.symbol.Type
      }))
    });
    
    let jobsScheduled = 0;
    
    // Schedule individual jobs for ETFs
    for (let i = 0; i < etfs.length; i++) {
      const { symbol: etf } = etfs[i];
      await this.scheduleOperation('fetch-single-fundamentals', {
        symbol: etf.Code,
        exchange: etf.eodExchange || etf.Exchange, // Use eodExchange if available
        country: etf.Country
      }, {
        attempts: 3,
        backoff: {
          type: 'exponential',
          delay: 10000
        },
        delay: jobsScheduled * 1000 // Stagger by 1 second
      });
      jobsScheduled++;
    }
    
    if (etfs.length > 0) {
      logger.info(`Scheduled ${etfs.length} individual ETF fundamentals jobs`);
    }
    
    // Create batches of 500 symbols for non-ETFs
    const batchSize = 500; // EOD allows up to 500 symbols per bulk request
    
    for (let i = 0; i < nonEtfs.length; i += batchSize) {
      const batch = nonEtfs.slice(i, i + batchSize);
      
      // Convert to array of {symbol, exchange, country} objects
      const symbolBatch = batch.map(s => ({
        symbol: s.symbol.Code,
        exchange: s.symbol.eodExchange || s.symbol.Exchange, // Use eodExchange if available
        country: s.symbol.Country
      }));
      
      await this.scheduleOperation('fetch-bulk-fundamentals', {
        symbols: symbolBatch
      }, {
        attempts: 3,
        backoff: {
          type: 'exponential',
          delay: 10000
        },
        delay: jobsScheduled * 5000 // Stagger batches by 5 seconds
      });
      
      jobsScheduled++;
      logger.info(`Scheduled fundamentals batch with ${symbolBatch.length} non-ETF symbols`);
    }
    
    logger.info(`Successfully scheduled ${jobsScheduled} fundamentals fetch jobs`);
    
    return {
      success: true,
      jobsScheduled
    };
  } catch (error) {
    logger.error('Failed to schedule fundamentals fetch jobs', { error });
    throw error;
  }
}

export async function fetchBulkFundamentals(
  this: EodHandler,
  input: BulkFundamentalsInput
): Promise<{ success: boolean; symbolsProcessed: number }> {
  const logger = this.logger;
  const { symbols } = input;
  
  try {
    logger.info('Fetching bulk fundamentals', { symbolCount: symbols.length });
    
    // Get API key
    const apiKey = EOD_CONFIG.API_TOKEN;
    if (!apiKey) {
      throw new Error('EOD API key not configured');
    }
    
    // Group symbols by actual exchange for API endpoint, but use country for symbol suffix
    const exchangeGroups = symbols.reduce((acc, { symbol, exchange, country }) => {
      if (!acc[exchange]) {
        acc[exchange] = [];
      }
      // Use utility function to handle US symbols and EUFUND special case
      const exchangeSuffix = getEodExchangeSuffix(exchange, country);
      acc[exchange].push(`${symbol}.${exchangeSuffix}`);
      return acc;
    }, {} as Record<string, string[]>);
    
    let totalProcessed = 0;
    
    // Process each exchange group
    for (const [exchange, symbolList] of Object.entries(exchangeGroups)) {
      // Build URL - using the exchange as the endpoint
      const url = new URL(`https://eodhd.com/api/bulk-fundamentals/${exchange}`);
      url.searchParams.append('api_token', apiKey);
      url.searchParams.append('fmt', 'json');
      url.searchParams.append('symbols', symbolList.join(','));
      
      logger.info(`Fetching bulk fundamentals for ${symbolList.length} ${exchange} symbols`, {
        exchange,
        count: symbolList.length,
        samples: symbolList.slice(0, 5)
      });
      
      // Fetch data
      const response = await fetch(url.toString());
      
      if (!response.ok) {
        throw new Error(`EOD Bulk Fundamentals API returned ${response.status}: ${response.statusText}`);
      }
      
      const data = await response.json();
      
      // EOD bulk fundamentals returns an object with symbol keys
      if (typeof data !== 'object' || !data) {
        throw new Error('Invalid response format from EOD API - expected object');
      }
      
      // Process each symbol's fundamentals
      const fundamentalsToSave = [];
      const symbolsToUpdate = [];
      
      for (const [symbolExchange, fundamentals] of Object.entries(data)) {
        if (!fundamentals || typeof fundamentals !== 'object') {
          logger.warn(`No fundamentals data for ${symbolExchange}`);
          continue;
        }
        
        // Extract symbol and exchange from the key
        const [symbol, exc] = symbolExchange.split('.');
        
        // Add metadata
        const fundamentalsWithMetadata = {
          symbol,
          exchange: exc,
          symbolExchange,
          ...fundamentals,
          updatedAt: new Date(),
          source: 'eod'
        };
        
        fundamentalsToSave.push(fundamentalsWithMetadata);
        symbolsToUpdate.push({ symbol, exchange: exc });
      }
      
      if (fundamentalsToSave.length > 0) {
        // Save fundamentals to MongoDB
        const result = await this.mongodb.batchUpsert(
          'eodFundamentals',
          fundamentalsToSave,
          ['symbolExchange']
        );
        
        logger.info(`Saved ${result.insertedCount} fundamentals records for ${exchange}`);
        
        // Update operation tracker for each symbol
        const updatePromises = symbolsToUpdate.map(({ symbol, exchange }) => {
          const eodSearchCode = `${symbol}.${exchange}`;
          return this.operationRegistry.updateOperation('eod', eodSearchCode, 'fundamentals_update', {
            status: 'success',
            recordCount: 1,
            metadata: {
              hasData: true,
              exchange: exchange
            }
          });
        });
        
        await Promise.all(updatePromises);
        totalProcessed += fundamentalsToSave.length;
      }
    }
    
    logger.info(`Successfully processed fundamentals for ${totalProcessed} symbols`);
    
    return {
      success: true,
      symbolsProcessed: totalProcessed
    };
  } catch (error) {
    logger.error('Failed to fetch bulk fundamentals', { error });
    
    // Mark all symbols as failed
    const failPromises = input.symbols.map(({ symbol, exchange }) => {
      const eodSearchCode = `${symbol}.${exchange}`;
      return this.operationRegistry.updateOperation('eod', eodSearchCode, 'fundamentals_update', {
        status: 'failure',
        error: error.message
      });
    });
    
    await Promise.all(failPromises);
    throw error;
  }
}

export async function fetchSingleFundamentals(
  this: EodHandler,
  input: FetchSingleFundamentalsInput
): Promise<{ success: boolean; saved: boolean }> {
  const logger = this.logger;
  const { symbol, exchange, country } = input;
  
  try {
    logger.info(`Fetching single fundamentals for ${symbol}.${exchange}`);
    
    // Get country if not provided
    let symbolCountry = country;
    if (!symbolCountry) {
      const symbolDoc = await this.mongodb.collection('eodSymbols').findOne({
        Code: symbol,
        Exchange: exchange
      });
      
      if (!symbolDoc) {
        throw new Error(`Symbol ${symbol}.${exchange} not found in database`);
      }
      symbolCountry = symbolDoc.Country;
    }
    
    // Get API key
    const apiKey = EOD_CONFIG.API_TOKEN;
    if (!apiKey) {
      throw new Error('EOD API key not configured');
    }
    
    // Build URL for single fundamentals endpoint
    // Use utility function to handle US symbols and EUFUND special case
    const exchangeSuffix = getEodExchangeSuffix(exchange, symbolCountry);
    
    const url = new URL(`https://eodhd.com/api/fundamentals/${symbol}.${exchangeSuffix}`);
    url.searchParams.append('api_token', apiKey);
    url.searchParams.append('fmt', 'json');
    
    // Fetch data
    const response = await fetch(url.toString());
    
    if (!response.ok) {
      throw new Error(`EOD Single Fundamentals API returned ${response.status}: ${response.statusText}`);
    }
    
    const fundamentals = await response.json();
    
    // Check if we got valid data
    if (!fundamentals || typeof fundamentals !== 'object') {
      logger.warn(`No fundamentals data returned for ${symbol}.${exchange}`);
      return { success: true, saved: false };
    }
    
    // Add metadata
    const fundamentalsWithMetadata = {
      symbol,
      exchange,
      symbolExchange: `${symbol}.${exchange}`,
      ...fundamentals,
      updatedAt: new Date(),
      source: 'eod'
    };
    
    // Save to MongoDB
    const result = await this.mongodb.batchUpsert(
      'eodFundamentals',
      [fundamentalsWithMetadata],
      ['symbolExchange']
    );
    
    // Update operation tracker
    const eodSearchCode = `${symbol}.${exchange}`;
    await this.operationRegistry.updateOperation('eod', eodSearchCode, 'fundamentals_update', {
      status: 'success',
      recordCount: result.insertedCount,
      metadata: {
        hasData: true,
        exchange: exchange,
        saved: result.insertedCount > 0
      }
    });
    
    logger.info(`Successfully saved fundamentals for ${symbol}.${exchange}`);
    
    return {
      success: true,
      saved: result.insertedCount > 0
    };
  } catch (error) {
    logger.error('Failed to fetch single fundamentals', { error, symbol, exchange });
    
    // Update operation tracker with failure
    const eodSearchCode = `${symbol}.${exchange}`;
    await this.operationRegistry.updateOperation('eod', eodSearchCode, 'fundamentals_update', {
      status: 'failure',
      error: error.message
    });
    
    throw error;
  }
}