import express from 'express';
import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
import { rawQuery, toPgDate } from '../db.js';
import { optionsFlowQuery } from '../queries/optionsFlowQuery.js';
import { getOptionsFlowFromPython, calculatePhase1ForSignal } from '../services/pythonServiceClient.js';
import { calculateBadges } from '../services/badgeCalculator.js';
import { calculateRocketScore } from '../services/scoringEngine.js';
import { checkTapeAlignment, getTapeAlignmentArrow } from '../services/tapeAlignment.js';
import { generateTradeSignal, formatTradeSignal } from '../services/tradeSignalGenerator.js';
import { detectFlowDecay, batchDetectFlowDecay, formatFlowDecay } from '../services/flowDecayDetector.js';
import { detectFlowReversal, batchDetectFlowReversals, formatFlowReversal } from '../services/flowReversalDetector.js';
import { batchDetectFlowTrends, formatFlowTrend } from '../services/flowTrendDetector.js';
import { calculateNoiseScore, getSignalQuality, getFlowWarnings } from '../services/falseSignalDetector.js';
import { generateAIAnalysis } from '../services/aiAnalysisService.js';
import { QueryProfiler } from '../utils/queryProfiler.js';
import { calculateFlowMomentum, getMomentumLabel } from '../services/momentumScore.js';
import { getMoneynessContext, isGammaSqueezeSetup, getMoneynessLabel } from '../utils/moneynessHelper.js';
import { batchFetchYahooFinanceData } from '../services/yahooFinanceService.js';
import { fetchVolumeHistoryBulk } from '../services/volumeHistoryService.js';
import { generateTradePlan } from '../services/tradePlanGenerator.js';

const router = express.Router();

// Get directory path for reading SQL files
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

// Feature flag: use Python service if available, fallback to SQL
const USE_PYTHON_SERVICE = process.env.USE_PYTHON_SERVICE !== 'false';

router.get('/flow', async (req, res) => {
  try {
    const { startDate, endDate, minPremium: minPremiumParam = 80000, minScore: minScoreParam, tolPct = 0.20, skipPrices = 'false', filterNoise = 'true' } = req.query;
    // Parse minPremium to number (query params come as strings)
    const minPremium = parseFloat(minPremiumParam) || 80000;
    // Parse minScore to number (query params come as strings)
    const minScore = minScoreParam !== undefined ? parseFloat(minScoreParam) : null;
    
    // Normalize dates to PostgreSQL-safe ISO 8601 format at the Node.js boundary
    // This ensures consistent date handling and prevents parsing errors
    const normalizedStartDate = startDate ? toPgDate(startDate) : null;
    const normalizedEndDate = endDate ? toPgDate(endDate) : null;
    
    let rawData = [];
    
    // Try Python service first (if enabled)
    if (USE_PYTHON_SERVICE) {
      console.log(`🐍 Attempting to use Python service (USE_PYTHON_SERVICE=${USE_PYTHON_SERVICE})`);
      try {
        const pythonResponse = await getOptionsFlowFromPython({
          startDate,
          endDate,
          minPremium: parseFloat(minPremium),
          tolPct: parseFloat(tolPct)
        });
        
        console.log(`🐍 Python service response: success=${pythonResponse.success}, data length=${pythonResponse.data?.length || 0}`);
        
        if (pythonResponse.success && pythonResponse.data && pythonResponse.data.length > 0) {
          rawData = pythonResponse.data;
          console.log(`✅ Using Python service: ${rawData.length} rows`);
          
          // Check if Phase 1 fields are present
          const sampleRow = rawData[0];
          const hasPhase1Fields = sampleRow && (
            sampleRow.signal_tier !== undefined ||
            sampleRow.checklist_passed !== undefined ||
            sampleRow.flow_led_to_move !== undefined
          );
          
          if (hasPhase1Fields) {
            const tier1Count = rawData.filter(r => r.signal_tier === 'TIER_1').length;
            const checklistCount = rawData.filter(r => r.checklist_passed === true).length;
            console.log(`📊 Phase 1 fields detected: Tier-1=${tier1Count}, Checklist passed=${checklistCount}`);
          } else {
            console.warn('⚠️ Phase 1 fields not found in Python service response');
          }
          
          // Mark source for logging
          res.locals.dataSource = 'python';
        } else {
          console.warn('⚠️ Python service returned empty data, falling back to SQL');
        }
      } catch (error) {
        if (error.message === 'PYTHON_SERVICE_UNAVAILABLE') {
          console.warn('⚠️ Python service unavailable, falling back to SQL');
        } else {
          console.error('❌ Python service error:', error.message);
          console.error('   Error details:', error);
          // Fall through to SQL fallback
        }
      }
    } else {
      console.log('📊 Python service disabled (USE_PYTHON_SERVICE=false or not set)');
    }
    
    // Fallback to SQL if Python service not used or failed
    if (rawData.length === 0) {
      console.log('📊 Using SQL query (fallback or Python disabled)');
      const queryStartDate = normalizedStartDate || startDate;
      const queryEndDate = normalizedEndDate || endDate;
      console.log(`📅 Date range: ${queryStartDate} to ${queryEndDate} (full 24-hour days: 00:00:00 to 23:59:59)`);
      
      // First, check if there's any data in the database for these dates
      try {
        const checkQuery = `
          SELECT COUNT(*) as total_count,
                 COUNT(CASE WHEN "Premium" IS NOT NULL AND TRIM("Premium"::text) <> '' THEN 1 END) as with_premium,
                 COUNT(CASE WHEN "StockEtf" = 'STOCK' THEN 1 END) as stocks,
                 MIN("CreatedDate") as min_date,
                 MAX("CreatedDate") as max_date
          FROM public."OptionsFlow_monthly"
          WHERE (
            CASE 
              WHEN "CreatedDate"::text ~ '^\\d{4}-\\d{2}-\\d{2}$' 
                THEN "CreatedDate"::date
              WHEN "CreatedDate"::text ~ '^\\d{1,2}/\\d{1,2}/\\d{2,4}$'
                THEN to_date("CreatedDate"::text, 'MM/DD/YYYY')
              ELSE NULL
            END
          ) >= $1::date 
            AND (
            CASE 
              WHEN "CreatedDate"::text ~ '^\\d{4}-\\d{2}-\\d{2}$' 
                THEN "CreatedDate"::date
              WHEN "CreatedDate"::text ~ '^\\d{1,2}/\\d{1,2}/\\d{2,4}$'
                THEN to_date("CreatedDate"::text, 'MM/DD/YYYY')
              ELSE NULL
            END
          ) <= $2::date
        `;
        const checkResult = await rawQuery(checkQuery, [normalizedStartDate || startDate, normalizedEndDate || endDate]);
        if (checkResult && checkResult.length > 0) {
          const stats = checkResult[0];
          console.log(`📊 Database stats for date range:`);
          console.log(`   Total rows: ${stats.total_count}`);
          console.log(`   With premium: ${stats.with_premium}`);
          console.log(`   Stocks: ${stats.stocks}`);
          console.log(`   Date range in DB: ${stats.min_date} to ${stats.max_date}`);
        }
        
        // Print sample raw records to verify data format
        const sampleQuery = `
          SELECT "CreatedDate", "CreatedTime", "Symbol", "Premium", "StockEtf"
          FROM public."OptionsFlow_monthly"
          WHERE (
            CASE 
              WHEN "CreatedDate"::text ~ '^\\d{4}-\\d{2}-\\d{2}$' 
                THEN "CreatedDate"::date
              WHEN "CreatedDate"::text ~ '^\\d{1,2}/\\d{1,2}/\\d{2,4}$'
                THEN to_date("CreatedDate"::text, 'MM/DD/YYYY')
              ELSE NULL
            END
          ) >= $1::date 
            AND (
            CASE 
              WHEN "CreatedDate"::text ~ '^\\d{4}-\\d{2}-\\d{2}$' 
                THEN "CreatedDate"::date
              WHEN "CreatedDate"::text ~ '^\\d{1,2}/\\d{1,2}/\\d{2,4}$'
                THEN to_date("CreatedDate"::text, 'MM/DD/YYYY')
              ELSE NULL
            END
          ) <= $2::date
            AND "CreatedTime"::text ~* '(AM|PM)'
          ORDER BY "CreatedDate" DESC, "CreatedTime" DESC
          LIMIT 50
        `;
        const sampleRecords = await rawQuery(sampleQuery, [normalizedStartDate || startDate, normalizedEndDate || endDate]);
        console.log(`\n📋 Sample raw records from database (first 50 with AM/PM times):`);
        sampleRecords.forEach((record, idx) => {
          console.log(`   [${idx + 1}] Date: "${record.CreatedDate}" | Time: "${record.CreatedTime}" | Symbol: ${record.Symbol} | Premium: ${record.Premium} | StockEtf: ${record.StockEtf}`);
        });
        console.log('');
      } catch (checkError) {
        console.warn('⚠️ Could not check database stats:', checkError.message);
      }
      
      // Run diagnostic query to see why data is being filtered out
      // This uses a simplified version of the main query to show filter breakdown
      try {
        const diagnosticQuery = `
          WITH cfg AS (
            SELECT $1::date AS start_day, $2::date AS end_day
          ),
          base AS (
            SELECT
              ofm.ctid AS rid,
              UPPER(TRIM(ofm."Symbol")) AS symbol_norm,
              NULLIF(REGEXP_REPLACE(ofm."Premium"::text, '[\\s$,]', '', 'g'),'')::numeric AS premium_num,
              CASE WHEN UPPER(TRIM(ofm."CallPut")) IN ('C','CALL','CALLS','CE') THEN 'CALL'
                   WHEN UPPER(TRIM(ofm."CallPut")) IN ('P','PUT','PUTS','PE') THEN 'PUT' END AS cp_norm,
              CASE
                WHEN UPPER(TRIM(ofm."Side")) IN ('A','AA','ASK','BUY','BOT','BTO','AT_ASK') THEN 'BUY'
                WHEN UPPER(TRIM(ofm."Side")) IN ('B','BB','BID','SELL','SLD','STO','AT_BID') THEN 'SELL'
                ELSE NULL
              END AS side_norm,
              NULLIF(REGEXP_REPLACE(ofm."Strike"::text, '[\\s$,]', '', 'g'),'')::numeric AS strike_num,
              NULLIF(REGEXP_REPLACE(ofm."Spot"::text, '[\\s$,]', '', 'g'),'')::numeric AS spot_num,
              NULLIF(REGEXP_REPLACE(ofm."Volume"::text, '[\\s$,]', '', 'g'),'')::numeric AS vol_num,
              NULLIF(REGEXP_REPLACE(ofm."OI"::text, '[\\s$,]', '', 'g'),'')::numeric AS oi_num,
              CASE
                WHEN ofm."CreatedDate"::text ~ '^\\d{4}-\\d{2}-\\d{2}$' AND ofm."CreatedTime"::text ~* '(AM|PM)'
                  THEN CASE
                    /* Has seconds: "09:30:45 AM" or "3:59:59 PM" - MUST pad single-digit hours */
                    WHEN ofm."CreatedTime"::text ~ ':\\d{2}:\\d{2}\\s*(AM|PM)'
                      THEN to_timestamp(
                        ofm."CreatedDate"::text || ' ' || 
                        LPAD((SPLIT_PART(btrim(ofm."CreatedTime"::text), ':', 1))::text, 2, '0') || 
                        SUBSTRING(btrim(ofm."CreatedTime"::text) FROM position(':' IN btrim(ofm."CreatedTime"::text))),
                        'YYYY-MM-DD HH12:MI:SS AM'
                      )
                    /* Has minutes: "09:30 AM" - MUST pad single-digit hours */
                    WHEN ofm."CreatedTime"::text ~ ':\\d{2}\\s*(AM|PM)'
                      THEN to_timestamp(
                        ofm."CreatedDate"::text || ' ' || 
                        LPAD((SPLIT_PART(btrim(ofm."CreatedTime"::text), ':', 1))::text, 2, '0') || 
                        SUBSTRING(btrim(ofm."CreatedTime"::text) FROM position(':' IN btrim(ofm."CreatedTime"::text))),
                        'YYYY-MM-DD HH12:MI AM'
                      )
                    /* Just hour: "09 AM" or "09AM" - convert to "09:00:00 AM" */
                    WHEN ofm."CreatedTime"::text ~ '^\\d{1,2}\\s*(AM|PM)$'
                      THEN to_timestamp(
                        ofm."CreatedDate"::text || ' ' || 
                        LPAD((REGEXP_REPLACE(btrim(ofm."CreatedTime"::text), '\\s*(AM|PM)$', '', 'i'))::text, 2, '0') || ':00:00 ' ||
                        SUBSTRING(btrim(ofm."CreatedTime"::text) FROM '(AM|PM)$'),
                        'YYYY-MM-DD HH12:MI:SS AM'
                      )
                    /* Fallback: try to parse with seconds format using LPAD */
                    ELSE COALESCE(
                      /* Try with seconds format - pad hour if single digit */
                      NULLIF(to_timestamp(
                        ofm."CreatedDate"::text || ' ' || 
                        LPAD((SPLIT_PART(btrim(ofm."CreatedTime"::text), ':', 1))::text, 2, '0') || 
                        SUBSTRING(btrim(ofm."CreatedTime"::text) FROM position(':' IN btrim(ofm."CreatedTime"::text))),
                        'YYYY-MM-DD HH12:MI:SS AM'
                      ), NULL),
                      /* Try with minutes only format - pad hour if single digit */
                      NULLIF(to_timestamp(
                        ofm."CreatedDate"::text || ' ' || 
                        LPAD((SPLIT_PART(btrim(ofm."CreatedTime"::text), ':', 1))::text, 2, '0') || 
                        SUBSTRING(btrim(ofm."CreatedTime"::text) FROM position(':' IN btrim(ofm."CreatedTime"::text))),
                        'YYYY-MM-DD HH12:MI AM'
                      ), NULL)
                    )
                  END
                ELSE NULL
              END AS flow_ts_local
            FROM public."OptionsFlow_monthly" ofm
            WHERE ofm."Premium" IS NOT NULL
              AND btrim(ofm."Premium"::text) <> ''
              AND ofm."StockEtf" = 'STOCK'
              AND ofm."Symbol" NOT IN ('TSLA','NVDA')
          ),
          flow AS (
            SELECT b.*, (b.flow_ts_local)::date AS flow_date_cst
            FROM base b
            WHERE b.flow_ts_local IS NOT NULL
              AND (b.flow_ts_local)::date BETWEEN (SELECT start_day FROM cfg) AND (SELECT end_day FROM cfg)
          ),
          mny AS (
            SELECT
              f.*,
              CASE
                WHEN (f.cp_norm='CALL' AND f.side_norm='BUY') OR (f.cp_norm='PUT' AND f.side_norm='SELL') THEN 'BULL'
                WHEN (f.cp_norm='PUT' AND f.side_norm='BUY') OR (f.cp_norm='CALL' AND f.side_norm='SELL') THEN 'BEAR'
                ELSE NULL
              END AS direction
            FROM flow f
          ),
          agg AS (
            SELECT
              m.*,
              SUM(CASE WHEN cp_norm='CALL' AND side_norm='BUY' AND (CASE WHEN cp_norm='CALL' AND strike_num > spot_num THEN 'OTM' WHEN cp_norm='CALL' AND strike_num <= spot_num THEN 'ITM' WHEN cp_norm='PUT' AND strike_num < spot_num THEN 'OTM' WHEN cp_norm='PUT' AND strike_num >= spot_num THEN 'ITM' END)='ITM' THEN premium_num ELSE 0 END)
                OVER (PARTITION BY symbol_norm ORDER BY flow_ts_local, rid) AS prem_cb_itm,
              SUM(CASE WHEN cp_norm='PUT' AND side_norm='SELL' AND (CASE WHEN cp_norm='CALL' AND strike_num > spot_num THEN 'OTM' WHEN cp_norm='CALL' AND strike_num <= spot_num THEN 'ITM' WHEN cp_norm='PUT' AND strike_num < spot_num THEN 'OTM' WHEN cp_norm='PUT' AND strike_num >= spot_num THEN 'ITM' END)='ITM' THEN premium_num ELSE 0 END)
                OVER (PARTITION BY symbol_norm ORDER BY flow_ts_local, rid) AS prem_ps_itm,
              SUM(CASE WHEN cp_norm='PUT' AND side_norm='BUY' AND (CASE WHEN cp_norm='CALL' AND strike_num > spot_num THEN 'OTM' WHEN cp_norm='CALL' AND strike_num <= spot_num THEN 'ITM' WHEN cp_norm='PUT' AND strike_num < spot_num THEN 'OTM' WHEN cp_norm='PUT' AND strike_num >= spot_num THEN 'ITM' END)='ITM' THEN premium_num ELSE 0 END)
                OVER (PARTITION BY symbol_norm ORDER BY flow_ts_local, rid) AS prem_pb_itm,
              SUM(CASE WHEN cp_norm='CALL' AND side_norm='SELL' AND (CASE WHEN cp_norm='CALL' AND strike_num > spot_num THEN 'OTM' WHEN cp_norm='CALL' AND strike_num <= spot_num THEN 'ITM' WHEN cp_norm='PUT' AND strike_num < spot_num THEN 'OTM' WHEN cp_norm='PUT' AND strike_num >= spot_num THEN 'ITM' END)='ITM' THEN premium_num ELSE 0 END)
                OVER (PARTITION BY symbol_norm ORDER BY flow_ts_local, rid) AS prem_cs_itm,
              SUM(CASE WHEN cp_norm='CALL' AND side_norm='BUY' AND (CASE WHEN cp_norm='CALL' AND strike_num > spot_num THEN 'OTM' WHEN cp_norm='CALL' AND strike_num <= spot_num THEN 'ITM' WHEN cp_norm='PUT' AND strike_num < spot_num THEN 'OTM' WHEN cp_norm='PUT' AND strike_num >= spot_num THEN 'ITM' END)='OTM' THEN premium_num ELSE 0 END)
                OVER (PARTITION BY symbol_norm ORDER BY flow_ts_local, rid) AS prem_cb_otm,
              SUM(CASE WHEN cp_norm='PUT' AND side_norm='SELL' AND (CASE WHEN cp_norm='CALL' AND strike_num > spot_num THEN 'OTM' WHEN cp_norm='CALL' AND strike_num <= spot_num THEN 'ITM' WHEN cp_norm='PUT' AND strike_num < spot_num THEN 'OTM' WHEN cp_norm='PUT' AND strike_num >= spot_num THEN 'ITM' END)='OTM' THEN premium_num ELSE 0 END)
                OVER (PARTITION BY symbol_norm ORDER BY flow_ts_local, rid) AS prem_ps_otm,
              SUM(CASE WHEN cp_norm='PUT' AND side_norm='BUY' AND (CASE WHEN cp_norm='CALL' AND strike_num > spot_num THEN 'OTM' WHEN cp_norm='CALL' AND strike_num <= spot_num THEN 'ITM' WHEN cp_norm='PUT' AND strike_num < spot_num THEN 'OTM' WHEN cp_norm='PUT' AND strike_num >= spot_num THEN 'ITM' END)='OTM' THEN premium_num ELSE 0 END)
                OVER (PARTITION BY symbol_norm ORDER BY flow_ts_local, rid) AS prem_pb_otm,
              SUM(CASE WHEN cp_norm='CALL' AND side_norm='SELL' AND (CASE WHEN cp_norm='CALL' AND strike_num > spot_num THEN 'OTM' WHEN cp_norm='CALL' AND strike_num <= spot_num THEN 'ITM' WHEN cp_norm='PUT' AND strike_num < spot_num THEN 'OTM' WHEN cp_norm='PUT' AND strike_num >= spot_num THEN 'ITM' END)='OTM' THEN premium_num ELSE 0 END)
                OVER (PARTITION BY symbol_norm ORDER BY flow_ts_local, rid) AS prem_cs_otm,
              SUM(CASE WHEN cp_norm='CALL' AND side_norm='BUY' AND (CASE WHEN cp_norm='CALL' AND strike_num > spot_num THEN 'OTM' WHEN cp_norm='CALL' AND strike_num <= spot_num THEN 'ITM' WHEN cp_norm='PUT' AND strike_num < spot_num THEN 'OTM' WHEN cp_norm='PUT' AND strike_num >= spot_num THEN 'ITM' END)='OTM' THEN oi_num ELSE 0 END)
                OVER (PARTITION BY symbol_norm ORDER BY flow_ts_local, rid) AS oi_cb_otm,
              SUM(CASE WHEN cp_norm='PUT' AND side_norm='BUY' AND (CASE WHEN cp_norm='CALL' AND strike_num > spot_num THEN 'OTM' WHEN cp_norm='CALL' AND strike_num <= spot_num THEN 'ITM' WHEN cp_norm='PUT' AND strike_num < spot_num THEN 'OTM' WHEN cp_norm='PUT' AND strike_num >= spot_num THEN 'ITM' END)='OTM' THEN oi_num ELSE 0 END)
                OVER (PARTITION BY symbol_norm ORDER BY flow_ts_local, rid) AS oi_pb_otm
            FROM mny m
            WHERE m.direction IS NOT NULL
          ),
          final1 AS (
            SELECT
              a.*,
              (a.prem_cb_itm + a.prem_ps_itm) AS bull_prem_itm,
              (a.prem_cs_itm + a.prem_pb_itm) AS bear_prem_itm,
              (a.prem_cb_otm + a.prem_ps_otm) AS otm_bull_raw,
              (a.prem_pb_otm + a.prem_cs_otm) AS otm_bear_raw
            FROM agg a
          ),
          deco AS (
            SELECT
              f.*,
              CASE
                WHEN (f.bull_prem_itm) > (f.bear_prem_itm) THEN '🟢'
                WHEN (f.bull_prem_itm) < (f.bear_prem_itm) THEN '🔴'
                ELSE ''
              END AS badge_round,
              (
                CASE WHEN (f.bull_prem_itm) > (f.bear_prem_itm) AND f.prem_cb_itm > f.prem_ps_itm THEN '💎' ELSE '' END
                || CASE WHEN (f.bull_prem_itm) < (f.bear_prem_itm) AND f.prem_pb_itm > f.prem_cs_itm THEN '💎' ELSE '' END
                || CASE WHEN (f.bull_prem_itm) > (f.bear_prem_itm) AND (f.otm_bull_raw - f.otm_bear_raw) > 10000 THEN '⭐' ELSE '' END
                || CASE WHEN (f.bull_prem_itm) < (f.bear_prem_itm) AND (f.otm_bear_raw - f.otm_bull_raw) > 10000 THEN '⭐' ELSE '' END
                || CASE WHEN (f.bull_prem_itm) > (f.bear_prem_itm) AND f.oi_cb_otm > 100000 THEN '💰' ELSE '' END
                || CASE WHEN (f.bull_prem_itm) < (f.bear_prem_itm) AND f.oi_pb_otm > 100000 THEN '💰' ELSE '' END
              ) AS badge_more,
              (f.prem_cb_itm + f.prem_ps_itm + f.prem_cb_otm + f.prem_ps_otm) AS bull_total,
              (f.prem_cs_itm + f.prem_pb_itm + f.prem_cs_otm + f.prem_pb_otm) AS bear_total
            FROM final1 f
          )
          SELECT
            COUNT(*) FILTER (WHERE premium_num > 80000) AS count_premium_gt_80k,
            COUNT(*) FILTER (WHERE badge_round IN ('🟢','🔴')) AS count_has_round_badge,
            COUNT(*) FILTER (WHERE badge_round IN ('🟢','🔴') AND POSITION('💎' IN badge_more) > 0) AS count_has_diamond,
            COUNT(*) FILTER (WHERE badge_round IN ('🟢','🔴') AND POSITION('💎' IN badge_more) > 0 AND POSITION('⭐' IN badge_more) > 0) AS count_has_diamond_star,
            COUNT(*) FILTER (WHERE premium_num > 80000 
                             AND badge_round IN ('🟢','🔴')
                             AND POSITION('💎' IN badge_more) > 0
                             AND POSITION('⭐' IN badge_more) > 0) AS count_meets_badge_requirements,
            COUNT(*) FILTER (WHERE premium_num > 80000 
                             AND badge_round IN ('🟢','🔴')
                             AND POSITION('💎' IN badge_more) > 0
                             AND POSITION('⭐' IN badge_more) > 0
                             AND ((direction = 'BULL' AND badge_round = '🟢' AND (bull_total - bear_total) > 0)
                                  OR (direction = 'BEAR' AND badge_round = '🔴' AND (bull_total - bear_total) < 0))) AS count_meets_all_requirements,
            COUNT(*) AS total_processed
          FROM deco
        `;
        const diagnosticResult = await rawQuery(diagnosticQuery, [normalizedStartDate || startDate, normalizedEndDate || endDate]);
        if (diagnosticResult && diagnosticResult.length > 0) {
          const diag = diagnosticResult[0];
          console.log(`\n🔍 Filter Diagnostic Breakdown:`);
          console.log(`   Total processed: ${diag.total_processed}`);
          console.log(`   Premium > 80K: ${diag.count_premium_gt_80k}`);
          console.log(`   Has round badge (🟢/🔴): ${diag.count_has_round_badge}`);
          console.log(`   Has 💎 badge: ${diag.count_has_diamond}`);
          console.log(`   Has 💎 + ⭐ badges: ${diag.count_has_diamond_star}`);
          console.log(`   Meets badge requirements: ${diag.count_meets_badge_requirements}`);
          console.log(`   Meets ALL requirements (including direction): ${diag.count_meets_all_requirements}`);
        }
      } catch (diagError) {
        console.warn('⚠️ Could not run diagnostic query:', diagError.message);
      }
      
      try {
        // Use SQL query as source of truth - it has restrictive filters
        // Profile the query execution
        // Pass normalized dates to ensure full 24-hour day coverage (00:00:00 to 23:59:59)
        const queryStartDate = normalizedStartDate || startDate;
        const queryEndDate = normalizedEndDate || endDate;
        const { result, metrics } = await QueryProfiler.profile(
          () => rawQuery(optionsFlowQuery, [queryStartDate, queryEndDate]),
          'optionsFlowQuery',
          { logSlowThreshold: 500 } // Log queries > 500ms
        );
        rawData = result;
        console.log(`✅ SQL query (source of truth) returned ${rawData.length} rows (${metrics.duration.toFixed(2)}ms)`);
        
        if (rawData.length === 0) {
          console.warn('⚠️ SQL query returned 0 rows. The SQL query (source of truth) has restrictive filters:');
          console.warn('   - premium_num > 80000');
          console.warn('   - badge_round IN (🟢,🔴)');
          console.warn('   - Must have 💎 (diamond) badge');
          console.warn('   - Must have ⭐ (star) badge');
          console.warn('   - Direction alignment (BULL/🟢/positive net OR BEAR/🔴/negative net)');
          console.warn('   These filters are defined in the SQL query and are not modified.');
        }
        
        // Add performance metrics to response metadata
        res.locals.queryMetrics = metrics;
      } catch (sqlError) {
        console.error('SQL query error:', sqlError.message);
        // If it's a date parsing error, provide helpful message
        if (sqlError.code === '22007' && (sqlError.message.includes('AM') || sqlError.message.includes('PM'))) {
          console.error('⚠️ Date/time parsing error. Check your CreatedTime format in OptionsFlow_monthly table.');
          console.error('   Expected formats: "9:00 AM", "09:00 AM", "9 AM", etc.');
          console.error('   The query has been updated to handle leading zeros, but some formats may still fail.');
        }
        throw sqlError;
      }
      
      // Mark source for logging
      res.locals.dataSource = 'sql';
    }
    
    // Enrich each row with badges, scores, tape alignment, and trade signals
    const enrichedData = rawData.map(row => {
      const badges = calculateBadges(row);
      const rocketScore = calculateRocketScore(row);
      const tapeAligned = checkTapeAlignment(row);
      const tapeArrow = getTapeAlignmentArrow(tapeAligned, row.direction);
      
      // Convert badge object to array for frontend display
      const badgesArray = [];
      if (badges.round) badgesArray.push(badges.round);
      if (badges.more) {
        // Split the more string into individual emojis
        // Match emojis including: 💎⭐💰✔ and other common emojis
        const emojiRegex = /[\u{1F300}-\u{1F9FF}\u{2600}-\u{26FF}\u{2700}-\u{27BF}\u{1F600}-\u{1F64F}\u{1F680}-\u{1F6FF}\u{2700}-\u{27BF}\u{1F900}-\u{1F9FF}\u{2600}-\u{26FF}\u{2700}-\u{27BF}✔]/gu;
        const moreBadges = badges.more.match(emojiRegex) || [];
        badgesArray.push(...moreBadges);
      }
      if (badges.flash) badgesArray.push(badges.flash);
      if (badges.rocket) {
        // Handle multi-emoji rockets (🚀🚀🚀) - count occurrences
        const rocketCount = (badges.rocket.match(/🚀/g) || []).length;
        for (let i = 0; i < rocketCount; i++) {
          badgesArray.push('🚀');
        }
      }
      
      // Calculate noise score and signal quality
      const noiseScore = calculateNoiseScore(row);
      const signalQuality = getSignalQuality(noiseScore);
      const warnings = getFlowWarnings(row);
      
      // Create enriched row object
      const enrichedRow = {
        ...row,
        badges: badgesArray, // Array format for frontend
        badgesRaw: badges,    // Keep raw object for reference
        rocketScore,
        tapeAligned,
        tapeArrow,
        // Noise detection
        noiseScore,
        signalQuality,
        warnings,
        isNoise: noiseScore >= 70,
        // Format display values
        symbolDisplay: formatSymbolDisplay(row, badges),
        rocketDisplay: formatRocketDisplay(row, rocketScore, badges),
        netPremiumDisplay: formatPremium(row.bull_total - row.bear_total),
        premiumDisplay: formatPremium(row.premium_num)
      };
      
      // Generate initial trade signal (will be regenerated later with flow trend data)
      const tradeSignal = generateTradeSignal(enrichedRow);
      enrichedRow.tradeSignal = tradeSignal;
      enrichedRow.tradeSignalDisplay = formatTradeSignal(tradeSignal);
      
      // Trade plan will be generated later after Yahoo Finance data is fetched
      // (see dataWithFlowInfo.map below)
      
      return enrichedRow;
    });

    // Filter and sort
    console.log(`📊 Raw data from SQL (source of truth): ${rawData.length} rows`);
    console.log(`📊 After enrichment: ${enrichedData.length} rows`);
    console.log(`📊 SQL query already applied filters: premium > 80000, badges (🟢/🔴 + 💎 + ⭐), direction alignment`);
    
    // Debug: show sample of enriched data
    if (enrichedData.length > 0) {
      const sample = enrichedData[0];
      console.log('📋 Sample enriched row:', {
        symbol: sample.Symbol || sample.symbol_norm,
        premium_num: sample.premium_num,
        badgesRaw: sample.badgesRaw,
        direction: sample.direction,
        bull_total: sample.bull_total,
        bear_total: sample.bear_total,
        // Check for Phase 1 fields
        hasPhase1Fields: !!(sample.signal_tier || sample.checklist_passed !== undefined),
        signal_tier: sample.signal_tier,
        checklist_passed: sample.checklist_passed
      });
    }
    
    // Note: SQL query is the source of truth and already applies all restrictive filters:
    // - premium_num > 80000
    // - badge_round IN ('🟢','🔴')
    // - Must have 💎 (diamond) badge
    // - Must have ⭐ (star) badge
    // - Direction alignment (BULL/🟢/positive net OR BEAR/🔴/negative net)
    // So we don't need to re-apply these filters here - SQL query is authoritative
    
    // Only apply additional JavaScript-side filters if minPremium differs from SQL's hardcoded 80000
    let filtered = enrichedData;
    if (minPremium !== 80000) {
      filtered = filtered.filter(row => {
        const premium = parseFloat(row.premium_num) || 0;
        return premium > minPremium;
      });
      console.log(`📊 Applied additional premium filter (>${minPremium}): ${filtered.length} rows`);
    } else {
      console.log(`📊 Using SQL query filters as-is (no additional filtering needed)`);
    }
    
    // Apply minScore filter if provided
    const beforeScoreFilter = filtered.length;
    if (minScore !== null && minScore !== undefined) {
      filtered = filtered.filter(row => {
        const rocketScore = row.rocketScore || row.rocket_score || 0;
        return rocketScore >= minScore;
      });
      console.log(`📊 Applied minScore filter (>=${minScore}): ${filtered.length} rows (removed ${beforeScoreFilter - filtered.length})`);
    }
    
    // Apply noise filter if requested
    const shouldFilterNoise = filterNoise === 'true' || filterNoise === true;
    const beforeNoiseFilter = filtered.length;
    if (shouldFilterNoise) {
      filtered = filtered.filter(row => !row.isNoise);
      console.log(`🚫 Filtered out ${beforeNoiseFilter - filtered.length} noise signals (noise score >= 70)`);
    }
    
    // Sort by timestamp descending
    const sorted = filtered.sort((a, b) => {
      const aTime = new Date(a.flow_ts_utc || a.CreatedDate || 0);
      const bTime = new Date(b.flow_ts_utc || b.CreatedDate || 0);
      return bTime - aTime;
    });

    // Group flows by ticker for flow decay, reversal, and trend detection
    const tickerFlows = {};
    sorted.forEach(row => {
      const symbol = row.symbol_norm || row.Symbol;
      if (!tickerFlows[symbol]) {
        tickerFlows[symbol] = [];
      }
      tickerFlows[symbol].push(row);
    });

    // Detect flow decay, reversals, and trends FIRST (before trade signal generation)
    const flowDecay = batchDetectFlowDecay(tickerFlows);
    const flowReversals = batchDetectFlowReversals(tickerFlows, 30);
    const flowTrends = batchDetectFlowTrends(tickerFlows);

    // Fetch stock price data for all unique symbols (includes volume history)
    // Skip if skipPrices=true for faster initial response
    const uniqueSymbols = [...new Set(sorted.map(row => (row.symbol_norm || row.Symbol || '').toUpperCase()).filter(Boolean))];
    const shouldSkipPrices = skipPrices === 'true' || skipPrices === true;
    
    let stockPrices = {};
    let volumeHistory = {};
    
    if (!shouldSkipPrices) {
      console.log(`📈 Fetching stock prices and volume history for ${uniqueSymbols.length} symbols from Yahoo Finance...`);
    try {
      stockPrices = await batchFetchYahooFinanceData(uniqueSymbols, 5);
      console.log(`✅ Successfully fetched stock prices for ${Object.keys(stockPrices).length} symbols`);
        
        // Extract volume history from stock price data
        Object.keys(stockPrices).forEach(symbol => {
          const priceData = stockPrices[symbol];
          if (priceData && priceData.volumeHistory && priceData.volumeHistory.length > 0) {
            volumeHistory[symbol] = priceData.volumeHistory;
          }
        });
        console.log(`✅ Extracted volume history for ${Object.keys(volumeHistory).length} symbols`);
    } catch (error) {
      console.warn('⚠️ Error fetching stock prices from Yahoo Finance:', error.message);
      // Continue without stock prices if fetch fails
      }

      // Fallback: Fetch volume history from database for symbols that didn't get data from Yahoo Finance
      const missingSymbols = uniqueSymbols.filter(s => !volumeHistory[s]);
      if (missingSymbols.length > 0) {
        console.log(`📊 Fetching volume history from database for ${missingSymbols.length} symbols...`);
        try {
          const dbVolumeHistory = await fetchVolumeHistoryBulk(missingSymbols);
          // Merge database results
          Object.keys(dbVolumeHistory).forEach(symbol => {
            if (!volumeHistory[symbol] && dbVolumeHistory[symbol].length > 0) {
              volumeHistory[symbol] = dbVolumeHistory[symbol];
            }
          });
          console.log(`✅ Fetched volume history from database for ${Object.keys(dbVolumeHistory).length} additional symbols`);
        } catch (error) {
          console.warn('⚠️ Error fetching volume history from database:', error.message);
        }
      }
    } else {
      console.log(`⏩ Skipping stock price/volume fetch for faster response (skipPrices=true)`);
    }

    // Add flow decay, reversal, and trend info to rows, then regenerate trade signals with trend data
    const dataWithFlowInfo = sorted.map(row => {
      const symbol = row.symbol_norm || row.Symbol;
      const symbolUpper = (symbol || '').toUpperCase();
      const decay = flowDecay[symbol];
      const reversal = flowReversals[symbol];
      const trend = flowTrends[symbol];
      
      // Calculate moneyness context
      const moneyness = getMoneynessContext(row);
      const isGamma = isGammaSqueezeSetup(row);
      
      // Calculate momentum score
      const momentumScore = calculateFlowMomentum(row, trend);
      const momentumLabel = getMomentumLabel(momentumScore);
      
      // Get stock price data for this symbol
      const stockPriceData = stockPrices[symbolUpper] || null;
      
      // Get volume history for this symbol
      const volumeHistoryData = volumeHistory[symbolUpper] || [];
      
      // Explicitly preserve Phase 1 fields from Python service
      const phase1Fields = {
        signal_tier: row.signal_tier,
        is_tradeable: row.is_tradeable,
        checklist_score: row.checklist_score,
        checklist_passed: row.checklist_passed,
        checklist_details: row.checklist_details,
        price_reaction_5m_pct: row.price_reaction_5m_pct,
        price_reaction_15m_pct: row.price_reaction_15m_pct,
        price_reaction_30m_pct: row.price_reaction_30m_pct,
        high_break_5m: row.high_break_5m,
        low_break_5m: row.low_break_5m,
        flow_led_to_move: row.flow_led_to_move,
        vwap_at_signal: row.vwap_at_signal,
        price_vs_vwap_pct: row.price_vs_vwap_pct
      };
      
      const rowWithFlowInfo = {
        ...row, // Preserve all original fields (including rocketScore from enrichedData)
        ...phase1Fields, // Explicitly ensure Phase 1 fields are included
        flowDecayRaw: decay,
        flowReversalRaw: reversal,
        flowTrendRaw: trend,
        // Add moneyness context
        moneynessContext: moneyness,
        moneynessLabel: getMoneynessLabel(moneyness),
        isGammaSqueezeSetup: isGamma,
        // Add momentum score
        momentumScore: momentumScore,
        momentumLabel: momentumLabel.label,
        momentumColor: momentumLabel.color,
        momentumIcon: momentumLabel.icon,
        // Add stock price data
        stockPrice: stockPriceData,
        // Add volume history (last 5 days)
        volumeHistory: volumeHistoryData,
        // Explicitly preserve rocketScore (in case it got lost)
        rocketScore: row.rocketScore || row.rocket_score || 0,
        rocket_score: row.rocket_score || row.rocketScore || 0
      };
      
      // Regenerate trade signal with flow trend data and moneyness context
      const tradeSignal = generateTradeSignal(rowWithFlowInfo);
      rowWithFlowInfo.tradeSignal = tradeSignal;
      rowWithFlowInfo.tradeSignalDisplay = formatTradeSignal(tradeSignal);
      
      // Add Yahoo Finance data for trade plan generation (if available)
      if (stockPriceData) {
        rowWithFlowInfo.yahooPriceData = stockPriceData;
        rowWithFlowInfo.stockPriceData = stockPriceData; // Also set as stockPriceData for compatibility
      }
      
      // Generate trade plan for high-score signals (>=2) with Yahoo Finance data
      const rocketScore = rowWithFlowInfo.rocketScore || rowWithFlowInfo.rocket_score || row.rocketScore || row.rocket_score || 0;
      
      // Debug logging for trade plan generation
      if (rocketScore >= 2 && !rowWithFlowInfo.tradePlan) {
        console.log(`🔍 Trade plan check for ${symbolUpper}: rocketScore=${rocketScore}, hasYahooData=${!!stockPriceData}`);
      }
      
      if (rocketScore >= 2) {
        try {
          const tradePlan = generateTradePlan(rowWithFlowInfo);
          rowWithFlowInfo.tradePlan = tradePlan;
          rowWithFlowInfo.tradePlanDisplay = formatTradePlanDisplay(tradePlan);
        } catch (error) {
          console.error(`❌ Error generating trade plan for ${symbolUpper} (rocketScore=${rocketScore}):`, error);
          rowWithFlowInfo.tradePlan = null;
        }
      } else {
        rowWithFlowInfo.tradePlan = null;
      }
      
      return {
        ...rowWithFlowInfo,
        flowDecay: decay ? formatFlowDecay(decay) : null,
        flowReversal: reversal && reversal.hasReversal ? formatFlowReversal(reversal) : null,
        flowTrend: trend ? formatFlowTrend(trend) : null
      };
    });
    
    const finalData = dataWithFlowInfo;

    res.json({
      success: true,
      data: finalData,
      count: finalData.length,
      timestamp: new Date().toISOString(),
      filtered: shouldFilterNoise ? beforeNoiseFilter - finalData.length : 0,
      metadata: {
        source: res.locals.dataSource || 'unknown',
        queryMetrics: res.locals.queryMetrics || null,
        flowDecaySummary: Object.keys(flowDecay).reduce((acc, ticker) => {
          if (flowDecay[ticker].status !== 'ACTIVE') {
            acc[ticker] = flowDecay[ticker];
          }
          return acc;
        }, {}),
        flowReversalsSummary: Object.keys(flowReversals).reduce((acc, ticker) => {
          if (flowReversals[ticker].hasReversal) {
            acc[ticker] = flowReversals[ticker];
          }
          return acc;
        }, {})
      }
    });

  } catch (error) {
    console.error('Options flow error:', error);
    res.status(500).json({ success: false, error: error.message });
  }
});

// Helper functions
function formatSymbolDisplay(row, badges) {
  // Use symbol_norm (raw symbol) instead of Symbol (which may already be formatted by SQL)
  const rawSymbol = row.symbol_norm || (row.Symbol && !row.Symbol.includes('·') ? row.Symbol : null) || 'UNKNOWN';
  
  const dirCount = row.direction === 'BULL' 
    ? `(${row.dir_count}🟩)` 
    : row.direction === 'BEAR'
    ? `(${row.dir_count}🟥)`
    : '';
  
  const catalyst = row.near_alert_type ? ' ⚡' : '';
  const badgeStr = (badges.round + badges.more + badges.flash).trim();
  const fire = row.premium_num > 1000000 ? ' 🔥' : 
               row.premium_num > 500000 ? ' 💵' : '';
  
  return `${dirCount}${dirCount ? '  ' : ''}${rawSymbol} · ${row.session_bucket || '?'}${catalyst}${badgeStr ? ' ' + badgeStr : ''}${fire}`;
}

function formatRocketDisplay(row, score, badges) {
  let rocket = '';
  if (score >= 5.0) rocket = '🚀🚀🚀';
  else if (score >= 3.5) rocket = '🚀🚀';
  else if (score >= 2.0) rocket = '🚀';
  else rocket = badges.rocket;
  
  if (row.mny_pct != null) {
    const mnyLabel = row.mny_pct >= 0 ? 'ITM' : 'OTM';
    rocket += ` [${mnyLabel} ${Math.abs(row.mny_pct).toFixed(1)}%]`;
  }
  
  return rocket;
}

function formatPremium(value) {
  if (!value) return '0';
  const absVal = Math.abs(value);
  const sign = value < 0 ? '-' : '';
  
  if (absVal >= 1e6) return `${sign}${(absVal / 1e6).toFixed(2)} M`;
  if (absVal >= 1e3) return `${sign}${Math.round(absVal / 1e3)} K`;
  return `${sign}${Math.round(absVal)}`;
}

function formatTradePlanDisplay(tradePlan) {
  if (!tradePlan || !tradePlan.signal) return null;
  
  const signal = tradePlan.signal;
  const confidence = tradePlan.confidence || 0;
  
  if (signal === 'BUY' || signal === 'SELL') {
    const signalEmoji = signal === 'BUY' ? '🚀' : '🔴';
    // For BUY/SELL: Show confidence as "readiness to trade"
    return `${signalEmoji} ${signal} ${confidence}% Ready`;
  }
  
  // For WAIT: Use readiness and waitStatusMessage from plan object (no hardcoding)
  const readiness = tradePlan.readiness !== undefined ? tradePlan.readiness : (tradePlan.confidence || 0);
  const statusMessage = tradePlan.waitStatusMessage || 'Low Conviction';
  
  return `⏸ WAIT: ${statusMessage} (${readiness}% ready)`;
}

/**
 * GET /api/options/phase-classifier
 * Options Flow Phase Classifier - Classifies stocks into institutional phases
 * (SQUEEZE, UNWINDING, HEDGED SHORTING, ACCUMULATION)
 */
router.get('/phase-classifier', async (req, res) => {
  try {
    // Read SQL query from file
    const sqlPath = path.join(__dirname, '../../database/intradayflow.sql');
    const query = fs.readFileSync(sqlPath, 'utf8');
    
    // Execute query
    const data = await rawQuery(query);
    
    // Format and enrich data
    const enrichedData = data.map(row => {
      // Format premium values
      const callPrem = row.call_prem || 0;
      const putPrem = row.put_prem || 0;
      const totalPrem = callPrem + putPrem;
      
      // Determine phase priority for sorting
      const phasePriority = {
        'SQUEEZE': 1,
        'UNWINDING': 2,
        'HEDGED SHORTING': 3,
        'ACCUMULATION': 4,
        'NEUTRAL': 9
      };
      
      return {
        ...row,
        call_prem_formatted: formatPremium(callPrem),
        put_prem_formatted: formatPremium(putPrem),
        total_prem_formatted: formatPremium(totalPrem),
        phase_priority: phasePriority[row.phase] || 9,
        // Add interpretation
        phase_interpretation: getPhaseInterpretation(row.phase),
        // Add trade implications
        trade_implication: getTradeImplication(row.phase)
      };
    });
    
    res.json({
      success: true,
      data: enrichedData,
      count: enrichedData.length,
      timestamp: new Date().toISOString(),
      phases: {
        SQUEEZE: enrichedData.filter(r => r.phase === 'SQUEEZE').length,
        UNWINDING: enrichedData.filter(r => r.phase === 'UNWINDING').length,
        HEDGED_SHORTING: enrichedData.filter(r => r.phase === 'HEDGED SHORTING').length,
        ACCUMULATION: enrichedData.filter(r => r.phase === 'ACCUMULATION').length,
        NEUTRAL: enrichedData.filter(r => r.phase === 'NEUTRAL').length
      }
    });
  } catch (error) {
    console.error('Phase classifier error:', error);
    res.status(500).json({ success: false, error: error.message });
  }
});

/**
 * Get phase interpretation for display
 */
function getPhaseInterpretation(phase) {
  const interpretations = {
    'SQUEEZE': 'Gamma squeeze setup. Market makers short gamma, forced to buy as price rises. Call flow overwhelms puts. Opening range break = institutional momentum ignition.',
    'UNWINDING': 'Short unwind in progress. Low short volume means sellers are NOT selling into the rally. Call dominance + lack of resistance = sustained bid.',
    'HEDGED SHORTING': 'Bearish trap disguised as bullish flow. Calls are being bought as downside protection. High short volume = aggressive selling into dark pools.',
    'ACCUMULATION': 'Quiet institutional accumulation. Heavy call buying without price movement = large players building positions without chasing.',
    'NEUTRAL': 'No clear institutional phase detected.'
  };
  return interpretations[phase] || interpretations['NEUTRAL'];
}

/**
 * Get trade implication for each phase
 */
function getTradeImplication(phase) {
  const implications = {
    'SQUEEZE': {
      action: 'BUY',
      entry: 'Enter on VWAP pullback or opening range retest',
      stop: 'Below opening range low or -1.5%',
      target: 'Next gamma strike or +3-5%',
      risk: 'HIGH - Momentum play, watch for reversal'
    },
    'UNWINDING': {
      action: 'BUY',
      entry: 'Enter on pullbacks to VWAP',
      stop: 'Below VWAP or -1%',
      target: '+2-4% or prior day high',
      risk: 'MEDIUM - Continuation play'
    },
    'HEDGED SHORTING': {
      action: 'SHORT or AVOID',
      entry: 'Short on rallies or after absorption',
      stop: 'Above VWAP or +1.5%',
      target: 'Prior day low or -3-5%',
      risk: 'HIGH - Distribution phase, expect continuation lower'
    },
    'ACCUMULATION': {
      action: 'BUY (SWING)',
      entry: 'Build position over 2-3 days on dips',
      stop: 'Below accumulation zone or -2%',
      target: '+5-10% over 1-5 days',
      risk: 'LOW - Stealth positioning, wait for catalyst'
    },
    'NEUTRAL': {
      action: 'WAIT',
      entry: 'No clear signal',
      stop: 'N/A',
      target: 'N/A',
      risk: 'N/A'
    }
  };
  return implications[phase] || implications['NEUTRAL'];
}

/**
 * AI Analysis endpoint
 * GET /api/options/ai-analysis
 * Query params: symbol, or pass full flow data in body
 */
router.get('/ai-analysis', async (req, res) => {
  try {
    const { symbol } = req.query;
    
    if (!symbol) {
      return res.status(400).json({ 
        success: false, 
        error: 'Symbol parameter is required' 
      });
    }

    // Get the latest flow data for this symbol
    // Normalize dates to PostgreSQL-safe format
    const startDate = toPgDate(new Date());
    const endDate = startDate;
    
    let rawData = [];
    try {
      rawData = await rawQuery(optionsFlowQuery, [startDate, endDate]);
    } catch (error) {
      console.error('Error fetching flow data for AI analysis:', error);
    }

    // Find the most recent flow for this symbol
    const symbolFlows = rawData.filter(row => 
      (row.symbol_norm || row.Symbol || '').toUpperCase() === symbol.toUpperCase()
    );
    
    if (symbolFlows.length === 0) {
      return res.status(404).json({ 
        success: false, 
        error: `No flow data found for symbol ${symbol}` 
      });
    }

    // Get the most recent flow
    const latestFlow = symbolFlows[0];
    
    // Enrich with badges and scores
    const badges = calculateBadges(latestFlow);
    const rocketScore = calculateRocketScore(latestFlow);
    const enrichedFlow = {
      ...latestFlow,
      badgesRaw: badges,
      rocketScore
    };

    // Generate AI analysis
    const analysisResult = await generateAIAnalysis(enrichedFlow);

    res.json({
      success: analysisResult.success,
      symbol,
      analysis: analysisResult.analysis,
      yahooData: analysisResult.yahooData,
      timestamp: analysisResult.timestamp,
      error: analysisResult.error
    });

  } catch (error) {
    console.error('AI Analysis endpoint error:', error);
    res.status(500).json({ 
      success: false, 
      error: error.message 
    });
  }
});

/**
 * AI Analysis endpoint (POST - with full flow data)
 * POST /api/options/ai-analysis
 * Body: { flowData: {...} }
 */
router.post('/ai-analysis', async (req, res) => {
  try {
    const { flowData } = req.body;
    
    if (!flowData) {
      return res.status(400).json({ 
        success: false, 
        error: 'flowData in request body is required' 
      });
    }

    // Generate AI analysis
    const analysisResult = await generateAIAnalysis(flowData);

    res.json({
      success: analysisResult.success,
      analysis: analysisResult.analysis,
      yahooData: analysisResult.yahooData,
      timestamp: analysisResult.timestamp,
      error: analysisResult.error
    });

  } catch (error) {
    console.error('AI Analysis endpoint error:', error);
    res.status(500).json({ 
      success: false, 
      error: error.message 
    });
  }
});

// Phase 1 calculation endpoint
router.post('/phase1/calculate', async (req, res) => {
  try {
    const { symbol, flow_ts_utc, flow_date_cst, row_data } = req.body;
    
    if (!symbol || !flow_ts_utc) {
      return res.status(400).json({
        success: false,
        error: 'symbol and flow_ts_utc are required'
      });
    }

    console.log(`📊 Calculating Phase 1 for ${symbol} at ${flow_ts_utc}`);
    
    // Call Python service to calculate Phase 1
    const result = await calculatePhase1ForSignal({
      symbol,
      flow_ts_utc,
      flow_date_cst,
      row_data
    });

    res.json(result);
  } catch (error) {
    console.error('Error calculating Phase 1:', error);
    
    if (error.message === 'PYTHON_SERVICE_UNAVAILABLE') {
      return res.status(503).json({
        success: false,
        error: 'Python service unavailable or connection lost. Please ensure the Python service is running at http://localhost:8010 and try again.'
      });
    }
    
    if (error.message === 'PYTHON_SERVICE_TIMEOUT') {
      return res.status(504).json({
        success: false,
        error: 'Phase 1 calculation timed out. This may take up to 2 minutes. Please try again or check if the symbol has valid data.'
      });
    }
    
    res.status(500).json({
      success: false,
      error: error.message || 'Failed to calculate Phase 1 metrics'
    });
  }
});

export default router;
