import type { EodHandler } from '../eod.handler';
import { EOD_CONFIG } from '../shared';

interface FetchPricesInput {
  eodSearchCode: string;
}

export async function scheduleFetchPrices(
  this: EodHandler
): Promise<{ success: boolean; jobsScheduled: number }> {
  const logger = this.logger;
  
  try {
    logger.info('Scheduling price fetch jobs for all symbols');
    
    // Use OperationTracker to find stale symbols
    const staleSymbols = await this.operationRegistry.getStaleSymbols('eod', 'price_update', {
      limit: 100000 // Higher limit to process all symbols
    });
    
    if (!staleSymbols || staleSymbols.length === 0) {
      logger.info('No symbols need price updates');
      return { success: true, jobsScheduled: 0 };
    }
    
    logger.info(`Found ${staleSymbols.length} symbols needing price updates`, {
      symbols: staleSymbols.slice(0, 10).map(s => ({
        symbol: s.symbol.Code,
        exchange: s.symbol.Exchange,
        name: s.symbol.Name,
        lastUpdate: s.lastRun
      }))
    });
    
    let jobsScheduled = 0;
    
    // Schedule jobs with staggered delays
    for (let i = 0; i < staleSymbols.length; i++) {
      const staleSymbol = staleSymbols[i];
      if (!staleSymbol || !staleSymbol.symbol) {
        logger.warn(`Skipping invalid stale symbol at index ${i}`, { staleSymbol });
        continue;
      }
      const { symbol } = staleSymbol;
      const jobId = `fetch-prices-${symbol.eodSearchCode}`;
      logger.debug(`Scheduling price fetch for ${symbol.Code}.${symbol.Exchange}`, {
        name: symbol.Name,
        lastUpdate: staleSymbol.lastRun,
        delay: i * 100,
        jobId
      });
      
      try {
        await this.scheduleOperation('fetch-prices', {
          eodSearchCode: symbol.eodSearchCode
        }, {
          jobId,
          attempts: 3,
          backoff: {
            type: 'exponential',
            delay: 5000
          },
          delay: i * 100 // Stagger jobs by 100ms per symbol to avoid rate limit spikes
        });
        jobsScheduled++;
      } catch (error: any) {
        if (error?.message?.includes('Job already exists')) {
          logger.debug(`Job already exists: ${jobId}`);
        } else {
          throw error;
        }
      }
    }
    
    logger.info(`Successfully scheduled ${jobsScheduled} price fetch jobs`);
    
    return {
      success: true,
      jobsScheduled
    };
  } catch (error) {
    logger.error('Failed to schedule price fetch jobs', { error });
    throw error;
  }
}

export async function fetchPrices(
  this: EodHandler,
  input: FetchPricesInput
): Promise<{ success: boolean; priceCount: number }> {
  const logger = this.logger;
  const { eodSearchCode } = input;
  
  // Declare variables that need to be accessible in catch block
  let symbol: string = '';
  let exchange: string = '';
  
  try {
    // Lookup symbol using eodSearchCode
    const symbolDoc = await this.mongodb.collection('eodSymbols').findOne({
      eodSearchCode: eodSearchCode
    });
    
    if (!symbolDoc) {
      logger.error(`Symbol not found for eodSearchCode: ${eodSearchCode}`);
      throw new Error(`Symbol not found: ${eodSearchCode}`);
    }
    
    symbol = symbolDoc.Code;
    exchange = symbolDoc.Exchange;
    
    logger.info(`Fetching prices for ${eodSearchCode}`);
    
    // Get API key from config
    const apiKey = EOD_CONFIG.API_TOKEN;
    if (!apiKey) {
      throw new Error('EOD API key not configured');
    }
    
    const url = new URL(`https://eodhd.com/api/eod/${eodSearchCode}`);
    url.searchParams.append('api_token', apiKey);
    url.searchParams.append('fmt', 'json');
    // Fetch price data from EOD API
    const response = await fetch(url.toString());
    
    if (!response.ok) {
      throw new Error(`EOD Prices API returned ${response.status}: ${response.statusText}`);
    }
    
    const priceData = await response.json();
    
    // EOD returns an array of historical prices
    if (!Array.isArray(priceData)) {
      throw new Error('Invalid response format from EOD API - expected array');
    }
    
    logger.info(`Fetched ${priceData.length} price records for ${eodSearchCode}`);
    
    // Log date range of prices
    if (priceData.length > 0) {
      logger.debug(`Price data range for ${eodSearchCode}:`, {
        oldest: priceData[0].date,
        newest: priceData[priceData.length - 1].date,
        count: priceData.length
      });
    }
    
    // Add metadata to each price record
    const pricesWithMetadata = priceData.map(price => ({
      symbol,
      exchange,
      eodSearchCode,
      date: price.date,
      open: price.open,
      high: price.high,
      low: price.low,
      close: price.close,
      adjustedClose: price.adjusted_close,
      volume: price.volume,
    }));
    
    // Save to MongoDB - use date and eodSearchCode as unique identifier
    const result = await this.mongodb.batchUpsert(
      'eodPrices',
      pricesWithMetadata,
      ['date', 'eodSearchCode']
    );
    
    // Update operation tracker instead of directly updating the symbol
    await this.operationRegistry.updateOperation('eod', eodSearchCode, 'price_update', {
      status: 'success',
      lastRecordDate: priceData.length > 0 ? priceData[priceData.length - 1].date : null,
      recordCount: priceData.length,
      metadata: {
        insertedCount: result.insertedCount,
        updatedCount: priceData.length - result.insertedCount
      }
    });
    
    logger.info(`Successfully saved ${result.insertedCount} price records for ${eodSearchCode}`);
    
    return {
      success: true,
      priceCount: result.insertedCount
    };
  } catch (error: unknown) {
    logger.error('Failed to fetch or save prices', { error, symbol, exchange });
    
    // Update operation tracker with failure
    await this.operationRegistry.updateOperation('eod', eodSearchCode, 'price_update', {
      status: 'failure',
      error: error instanceof Error ? error.message : String(error)
    });
    
    throw error;
  }
}