/**
 * QM Intraday Crawl Actions - Sophisticated crawling with resumption support
 */

import type { ExecutionContext } from '@stock-bot/handlers';
import type { CrawlState } from '../../../shared/operation-manager/types';
import type { QMHandler } from '../qm.handler';
import { getLastWeek, QM_CONFIG, QM_SESSION_IDS } from '../shared/config';
import { QMSessionManager } from '../shared/session-manager';

interface IntradayCrawlInput {
  symbol: string;
  exchange: string;
  qmSearchCode: string;
  targetOldestDate?: string; // ISO date string for how far back to crawl
  existingCrawlState?: CrawlState; // Pass existing crawl state to avoid re-querying
  gaps?: { forward?: boolean; backward?: boolean }; // Pass gap information
}


/**
 * Main intraday crawl handler - crawls backwards week by week (Sunday to Sunday)
 */
export async function crawlIntradayData(
  this: QMHandler,
  input: IntradayCrawlInput,
  _context?: ExecutionContext
): Promise<{
  success: boolean;
  symbol: string;
  exchange: string;
  qmSearchCode: string;
  message: string;
  data?: any;
}> {
  const { 
    symbol, 
    exchange, 
    qmSearchCode,
    targetOldestDate = '1960-01-01', // Default to ~5 years of data
    existingCrawlState,
    gaps
  } = input;

  this.logger.info('Starting intraday crawl', { 
    symbol, 
    exchange, 
    targetOldestDate,
    hasExistingState: !!existingCrawlState,
    gaps
  });

  const sessionManager = QMSessionManager.getInstance();
  await sessionManager.initialize(this.cache, this.logger);

  const sessionId = QM_SESSION_IDS.PRICES;
  const session = await sessionManager.getSession(sessionId);
  
  if (!session || !session.uuid) {
    throw new Error(`No active session found for QM intraday`);
  }

  try {
    // Use passed crawl state if available, otherwise query for it
    let currentCrawlState: CrawlState;
    
    if (existingCrawlState) {
      currentCrawlState = existingCrawlState;
    } else {
      // Only query if not passed
      const symbolData = await this.mongodb.findOne('qmSymbols', {
        qmSearchCode
      });
      
      currentCrawlState = symbolData?.operations?.intraday_bars?.crawlState || {
        finished: false
      };
    }

    // Determine starting point for crawl
    let currentWeek: Date;
    if (currentCrawlState.oldestDateReached && !currentCrawlState.finished) {
      // Start from oldest date reached
      currentWeek = new Date(currentCrawlState.oldestDateReached);
    } else if (currentCrawlState.lastProcessedDate) {
      // Resume from last processed date
      currentWeek = new Date(currentCrawlState.lastProcessedDate);
    } else  {
      // New crawl, start from today
      currentWeek = new Date();
      // console.log('Current week before adjustment:', currentWeek);
    }
    
    
    // Get the Sunday of the current week
    currentWeek = getLastWeek(currentWeek);
    
    const targetOldest = new Date(targetOldestDate);
    targetOldest.setHours(0, 0, 0, 0);

    // Check if already finished
    if (currentCrawlState.finished || currentWeek <= targetOldest) {
      this.logger.info('Intraday crawl already complete', { symbol });
      return {
        success: true,
        qmSearchCode,
        symbol,
        exchange,
        message: 'Intraday crawl already complete'
      };
    }

    let totalRecords = 0;
    let weeksProcessed = 0;
    const errors: string[] = [];
    
    const endOfWeek = new Date(currentWeek)
    endOfWeek.setDate(endOfWeek.getDate() + 6); // Set to next Saturday
    // Build API request for the week
    const searchParams = new URLSearchParams({
      adjType:'none',
      adjusted:'true',
      freq:'day',
      interval:'1',
      marketSession:'mkt',
      pathName:'/demo/portal/company-quotes.php',
      qmodTool:'InteractiveChart',
      start: currentWeek.toISOString().split('T')[0],
      end: endOfWeek.toISOString().split('T')[0], // Next Sunday
      symbol: qmSearchCode,
      unadjusted:'false',
      webmasterId:'500',
      zeroTradeDays:'false',
    } as Record<string, string>);

    try {
      const response = await fetch(`${QM_CONFIG.PRICES_URL}?${searchParams.toString()}`, {
        method: 'GET',
        headers: session.headers,
        proxy: session.proxy,
      });

      if (!response.ok) {
        throw new Error(`API request failed: ${response.status}`);
      }

      const barsResults = await response.json();
      
      // Parse the results based on the API response structure
      const barsData = barsResults.results?.intraday?.[0]?.interval || null;
      const barsLength = barsData !== null ? barsData.length : 0;
      this.logger.info(`Fetched ${barsLength} bars for ${qmSearchCode} for week of ${currentWeek}`, {
        qmSearchCode,
        currentWeek,
        records: barsLength
      });

      // Update session success stats
      await sessionManager.incrementSuccessfulCalls(sessionId, session.uuid);

      // Process and store data if we got any
      if (barsData !== null && barsLength > 0) {
        const processedBars = barsData.map((bar: any) => ({
          ...bar,
          qmSearchCode,
          symbol,
          exchange,
          timestamp: new Date(bar.startdatetime || bar.date || bar.datetime),
        }));

        await this.mongodb.batchUpsert(
          'qmIntraday',
          processedBars,
          ['qmSearchCode', 'timestamp']
        );

        totalRecords += barsLength;
      }
      
      weeksProcessed = 1;
      const nextWeek = getLastWeek(new Date(currentWeek));
      const finished = (barsData === null) && nextWeek < new Date('2024-01-01')
      this.operationRegistry.updateOperation('qm', qmSearchCode, 'intraday_bars', {
        status: 'success',
        crawlState: {
          finished: finished,
          oldestDateReached: currentCrawlState.oldestDateReached,
          lastProcessedDate: currentWeek
        }
      });
      
      // Calculate next week to process (previous Sunday)
      
      // console.log(barsData === null , existingCrawlState);
      if(!finished) {
        currentCrawlState.lastProcessedDate = nextWeek;
        await this.scheduleOperation('crawl-intraday-data', {
          symbol: symbol,
          exchange: exchange,
          qmSearchCode: qmSearchCode,
          targetOldestDate,
          // Pass existing crawl state and gaps to avoid re-querying
          existingCrawlState: currentCrawlState,
          gaps: gaps
        }, {
          priority: 4, // Standard priority for backward crawls
        });
      }

      return {
        success: true,
        symbol,
        exchange,
        qmSearchCode,
        message: `Intraday crawl completed for ${symbol} - Processed ${weeksProcessed} week(s), ${totalRecords} records`,
        data: {
          totalRecords,
          weeksProcessed,
          nextWeek: nextWeek.toISOString().split('T')[0],
          errors: errors.length > 0 ? errors : undefined
        }
      }

    } catch (error) {
      this.logger.error('Intraday crawl failed', { 
        symbol,
        error: error instanceof Error ? error.message : 'Unknown error'
      });

      await this.operationRegistry.updateOperation('qm', qmSearchCode, 'intraday_bars', {
        status: 'failure',
        error: error instanceof Error ? error.message : 'Unknown error'
      });

      return {
        success: false,
        symbol,
        exchange,
        qmSearchCode,
        message: `Intraday crawl failed: ${error instanceof Error ? error.message : 'Unknown error'}`
      };
    }
  }catch (error) {
    return {
      success: false,
      symbol,
      exchange,
      qmSearchCode,
      message: `Intraday crawl failed: ${error instanceof Error ? error.message : 'Unknown error'}`
    };
  }
}

/**
 * Schedule intraday crawls for multiple symbols
 */
export async function scheduleIntradayCrawls(
  this: QMHandler,
  input: {
    limit?: number;
    targetOldestDate?: string;
    priorityMode?: 'never_run' | 'incomplete' | 'all';
  } = {},
  _context?: ExecutionContext
): Promise<{
  message: string;
  symbolsQueued: number;
  errors: number;
}> {
  const { 
    limit = 999999999, 
    targetOldestDate = '1960-01-01',
    priorityMode = 'all'
  } = input;

  this.logger.info('Scheduling intraday crawls', { 
    limit, 
    targetOldestDate,
    priorityMode 
  });

  try {
    // Get symbols based on priority mode
    let symbolsToProcess: any[] = [];
    const tracker = this.operationRegistry.getTracker('qm');

    switch (priorityMode) {
      case 'never_run':
        // Get symbols that have never been crawled
        symbolsToProcess = await this.mongodb.find('qmSymbols', {
          'operations.intraday_bars': { $exists: false },
          active: { $ne: false }
        }, {
          limit,
          projection: { symbol: 1, exchange: 1, qmSearchCode: 1, operations: 1 }
        });
        break;

      case 'incomplete':
        // Get symbols with incomplete crawls
        symbolsToProcess = await tracker.getSymbolsForIntradayCrawl(
          'intraday_bars',
          { 
            limit, 
            targetOldestDate: new Date(targetOldestDate), 
            includeNewDataGaps: false,  // Only backward gaps
            // symbolFilter: { symbol: 'ETU' }  // Filter for testing
          }
        );
        break;


      case 'all':
      default:
        // Get all symbols that need any processing
        symbolsToProcess = await tracker.getSymbolsForIntradayCrawl(
          'intraday_bars',
          { 
            limit, 
            targetOldestDate: new Date(targetOldestDate),
            includeNewDataGaps: false,  // Only backward gaps since we removed forward crawling
            // symbolFilter: { symbol: 'AAPL' }  // Filter for testing
          }
        );
        break;
    }

    if (symbolsToProcess.length === 0) {
      return {
        message: `No symbols found for priority mode: ${priorityMode}`,
        symbolsQueued: 0,
        errors: 0
      };
    }

    this.logger.info('Symbols to process from getSymbolsForIntradayCrawl', {
      count: symbolsToProcess.length,
      firstSymbol: symbolsToProcess[0] ? {
        symbol: symbolsToProcess[0].symbol,
        gaps: symbolsToProcess[0].gaps,
        hasOperationStatus: !!symbolsToProcess[0].operationStatus,
        crawlState: symbolsToProcess[0].operationStatus?.crawlState
      } : null
    });

    // Get full symbol data if needed
    if (priorityMode !== 'never_run') {
      const qmSearchCodes = symbolsToProcess.map(s => s.symbol);
      const fullSymbols = await this.mongodb.find('qmSymbols', {
        qmSearchCode: { $in: qmSearchCodes }
      }, {
        projection: { symbol: 1, exchange: 1, qmSearchCode: 1, operations: 1 }
      });

      // Map back the full data while preserving gaps and operation status
      symbolsToProcess = symbolsToProcess.map(sp => {
        const full = fullSymbols.find(f => f.qmSearchCode === sp.symbol);
        if (full) {
          return {
            ...full,
            gaps: sp.gaps,  // Preserve gap information
            operationStatus: sp.operationStatus  // Preserve original operation status
          };
        }
        return sp;
      });
    }

    this.logger.info('After mapping, symbols to process', {
      count: symbolsToProcess.length,
      firstSymbol: symbolsToProcess[0] ? {
        symbol: symbolsToProcess[0].symbol,
        exchange: symbolsToProcess[0].exchange,
        qmSearchCode: symbolsToProcess[0].qmSearchCode,
        gaps: symbolsToProcess[0].gaps,
        hasOperationStatus: !!symbolsToProcess[0].operationStatus,
        crawlState: symbolsToProcess[0].operationStatus?.crawlState
      } : null
    });

    let symbolsQueued = 0;
    let errors = 0;


    // Schedule crawl jobs
    for (const doc of symbolsToProcess) {
      try {
        await this.scheduleOperation('crawl-intraday-data', {
          symbol: doc.symbol,
          exchange: doc.exchange,
          qmSearchCode: doc.qmSearchCode,
          targetOldestDate,
          // Pass existing crawl state and gaps to avoid re-querying
          existingCrawlState: doc.operationStatus?.crawlState,
          gaps: doc.gaps
        }, {
          priority: 6, // Standard priority for backward crawls
        });

        symbolsQueued++;
      } catch (error) {
        this.logger.error(`Failed to schedule intraday crawl for ${doc.symbol}`, { error });
        errors++;
      }
    }

    this.logger.info('Intraday crawl scheduling completed', {
      priorityMode,
      symbolsQueued,
      errors
    });

    return {
      message: `Scheduled ${symbolsQueued} symbols for intraday crawl (${priorityMode} mode)`,
      symbolsQueued,
      errors
    };

  } catch (error) {
    this.logger.error('Intraday crawl scheduling failed', { error });
    throw error;
  }
}