import express from 'express';
import { DateTime } from 'luxon';
import { getDbConnection, getPoolStatus } from '../db/connection.js';
import {
  getTimeRangeConditions,
  formatBusinessDate,
  getBusinessDayBounds,
  customRange,
  previousRange,
  parseMySql,
  toMySqlBound,
  _internal as timeHelpers,
} from '../../../shared/business-time/index.js';

const router = express.Router();

const TIMEZONE = 'America/New_York';
const BUSINESS_DAY_START_HOUR = timeHelpers?.BUSINESS_DAY_START_HOUR ?? 1;

// Cherry Box order types to exclude when excludeCherryBox=true is passed
// 3 = cherrybox_subscription, 4 = cherrybox_sending, 5 = cherrybox_subscription_renew, 7 = cherrybox_refund
const EXCLUDED_ORDER_TYPES = [3, 4, 5, 7];
const getCherryBoxClause = (exclude) => exclude ? `order_type NOT IN (${EXCLUDED_ORDER_TYPES.join(', ')})` : '1=1';
const getCherryBoxClauseAliased = (alias, exclude) => exclude ? `${alias}.order_type NOT IN (${EXCLUDED_ORDER_TYPES.join(', ')})` : '1=1';
const parseBoolParam = (value) => value === 'true' || value === '1';

// Image URL generation utility
const getImageUrls = (pid, iid = 1) => {
  const imageUrlBase = 'https://sbing.com/i/products/0000/';
  const paddedPid = pid.toString().padStart(6, '0');
  const prefix = paddedPid.slice(0, 3);
  const basePath = `${imageUrlBase}${prefix}/${pid}`;
  return {
    image: `${basePath}-t-${iid}.jpg`,
    image_175: `${basePath}-175x175-${iid}.jpg`,
    image_full: `${basePath}-o-${iid}.jpg`,
    ImgThumb: `${basePath}-175x175-${iid}.jpg` // For ProductGrid component
  };
};

// Main stats endpoint - replaces /api/klaviyo/events/stats
router.get('/stats', async (req, res) => {
  const startTime = Date.now();
  console.log(`[STATS] Starting request for timeRange: ${req.query.timeRange}`);
  
  // Set a timeout for the entire operation
  const timeoutPromise = new Promise((_, reject) => {
    setTimeout(() => reject(new Error('Request timeout after 15 seconds')), 15000);
  });
  
  try {
    const mainOperation = async () => {
      const { timeRange, startDate, endDate, excludeCherryBox } = req.query;
      const excludeCB = parseBoolParam(excludeCherryBox);
      console.log(`[STATS] Getting DB connection... (excludeCherryBox: ${excludeCB})`);
      const { connection, release } = await getDbConnection();
      console.log(`[STATS] DB connection obtained in ${Date.now() - startTime}ms`);
      try {

    const { whereClause, params, dateRange } = getTimeRangeConditions(timeRange, startDate, endDate);
    
    // Main order stats query (optionally excludes Cherry Box orders)
    // Note: order_status > 15 excludes cancelled (15), so cancelled stats are queried separately
    const mainStatsQuery = `
      SELECT
        COUNT(*) as orderCount,
        SUM(summary_total) as revenue,
        SUM(stats_prod_pieces) as itemCount,
        AVG(summary_total) as averageOrderValue,
        AVG(stats_prod_pieces) as averageItemsPerOrder,
        SUM(CASE WHEN stats_waiting_preorder > 0 THEN 1 ELSE 0 END) as preOrderCount,
        SUM(CASE WHEN ship_method_selected = 'localpickup' THEN 1 ELSE 0 END) as localPickupCount,
        SUM(CASE WHEN ship_method_selected = 'holdit' THEN 1 ELSE 0 END) as onHoldCount
      FROM _order
      WHERE order_status > 15 AND ${getCherryBoxClause(excludeCB)} AND ${whereClause}
    `;

    const [mainStats] = await connection.execute(mainStatsQuery, params);
    const stats = mainStats[0];

    // Cancelled orders query - uses date_cancelled instead of date_placed
    // Shows orders cancelled during the selected period, regardless of when they were placed
    const cancelledQuery = `
      SELECT
        COUNT(*) as cancelledCount,
        SUM(summary_total) as cancelledTotal
      FROM _order
      WHERE order_status = 15
        AND ${getCherryBoxClause(excludeCB)}
        AND ${whereClause.replace(/date_placed/g, 'date_cancelled')}
    `;

    const [cancelledResult] = await connection.execute(cancelledQuery, params);
    const cancelledStats = cancelledResult[0] || { cancelledCount: 0, cancelledTotal: 0 };

    // Refunds query (optionally excludes Cherry Box orders)
    const refundsQuery = `
      SELECT 
        COUNT(*) as refundCount,
        ABS(SUM(payment_amount)) as refundTotal
      FROM order_payment op
      JOIN _order o ON op.order_id = o.order_id
      WHERE payment_amount < 0 AND o.order_status > 15 AND ${getCherryBoxClauseAliased('o', excludeCB)} AND ${whereClause.replace(/date_placed/g, 'o.date_placed')}
    `;
    
    const [refundStats] = await connection.execute(refundsQuery, params);

    // Shipped orders query - uses date_shipped instead of date_placed
    // This counts orders that were SHIPPED during the selected period, regardless of when they were placed
    const shippedQuery = `
      SELECT COUNT(*) as shippedCount
      FROM _order
      WHERE order_status IN (92, 95, 100)
        AND ${getCherryBoxClause(excludeCB)}
        AND ${whereClause.replace(/date_placed/g, 'date_shipped')}
    `;

    const [shippedResult] = await connection.execute(shippedQuery, params);
    const shippedCount = parseInt(shippedResult[0]?.shippedCount || 0);

    // Best revenue day query (optionally excludes Cherry Box orders)
    const bestDayQuery = `
      SELECT 
        DATE(date_placed) as date,
        SUM(summary_total) as revenue,
        COUNT(*) as orders
      FROM _order 
      WHERE order_status > 15 AND ${getCherryBoxClause(excludeCB)} AND ${whereClause}
      GROUP BY DATE(date_placed)
      ORDER BY revenue DESC
      LIMIT 1
    `;
    
    const [bestDayResult] = await connection.execute(bestDayQuery, params);
    
    // Peak hour query - uses selected time range for the card value.
    // date_placed stores Central wall-clock; the office reads the axis in
    // Eastern, and ET = CT + 1h year-round, so shift before taking HOUR().
    let peakHour = null;
    if (['today', 'yesterday'].includes(timeRange)) {
      const peakHourQuery = `
        SELECT
          HOUR(ADDTIME(date_placed, '01:00:00')) as hour,
          COUNT(*) as count
        FROM _order
        WHERE order_status > 15 AND ${getCherryBoxClause(excludeCB)} AND ${whereClause}
        GROUP BY HOUR(ADDTIME(date_placed, '01:00:00'))
        ORDER BY count DESC
        LIMIT 1
      `;

      const [peakHourResult] = await connection.execute(peakHourQuery, params);
      if (peakHourResult.length > 0) {
        const hour = parseInt(peakHourResult[0].hour);
        peakHour = {
          hour,
          count: parseInt(peakHourResult[0].count),
          displayHour: DateTime.fromObject({ hour }).toFormat('h a')
        };
      }
    }

    // Hourly breakdown for detail chart - always rolling 24 hours (like revenue/orders use 30 days)
    // Returns data ordered chronologically: [24hrs ago, 23hrs ago, ..., 1hr ago, current hour]
    let hourlyOrders = null;
    if (['today', 'yesterday'].includes(timeRange)) {
      // Hourly counts in EASTERN display hours (column stores Central
      // wall-clock; ET = CT + 1h year-round). currentHour comes from the same
      // expression so the rolling window stays aligned.
      const hourlyQuery = `
        SELECT
          HOUR(ADDTIME(date_placed, '01:00:00')) as hour,
          COUNT(*) as count,
          HOUR(ADDTIME(NOW(), '01:00:00')) as currentHour
        FROM _order
        WHERE order_status > 15
          AND ${getCherryBoxClause(excludeCB)}
          AND date_placed >= NOW() - INTERVAL 24 HOUR
        GROUP BY HOUR(ADDTIME(date_placed, '01:00:00'))
      `;

      const [hourlyResult] = await connection.execute(hourlyQuery);

      // Current hour in Eastern (fallback computes it directly)
      const currentHour = hourlyResult.length > 0
        ? parseInt(hourlyResult[0].currentHour)
        : DateTime.now().setZone(TIMEZONE).hour;

      // Build map of hour -> count
      const hourCounts = {};
      hourlyResult.forEach(row => {
        hourCounts[parseInt(row.hour)] = parseInt(row.count);
      });

      // Build array in chronological order starting from (currentHour + 1) which is 24 hours ago
      hourlyOrders = [];
      for (let i = 0; i < 24; i++) {
        const hour = (currentHour + 1 + i) % 24;  // Start from 24hrs ago, end at current hour
        hourlyOrders.push({
          hour,
          count: hourCounts[hour] || 0
        });
      }
    }
    
    // Brands query - products.company links to product_categories.cat_id for brand name
    // Only include products that have a brand assigned (INNER JOIN)
    const brandsQuery = `
      SELECT
        pc.cat_id as catId,
        pc.name as brandName,
        COUNT(DISTINCT oi.order_id) as orderCount,
        SUM(oi.qty_ordered) as itemCount,
        SUM(oi.qty_ordered * oi.prod_price) as revenue
      FROM order_items oi
      JOIN _order o ON oi.order_id = o.order_id
      JOIN products p ON oi.prod_pid = p.pid
      JOIN product_categories pc ON p.company = pc.cat_id
      WHERE o.order_status > 15 AND ${getCherryBoxClauseAliased('o', excludeCB)} AND ${whereClause.replace(/date_placed/g, 'o.date_placed')}
      GROUP BY pc.cat_id, pc.name
      HAVING revenue > 0
      ORDER BY revenue DESC
      LIMIT 100
    `;

    const [brandsResult] = await connection.execute(brandsQuery, params);

    // Categories query - uses product_category_index to get category assignments
    // Only include categories with valid types (no NULL/uncategorized)
    const categoriesQuery = `
      SELECT
        pc.cat_id as catId,
        pc.name as categoryName,
        COUNT(DISTINCT oi.order_id) as orderCount,
        SUM(oi.qty_ordered) as itemCount,
        SUM(oi.qty_ordered * oi.prod_price) as revenue
      FROM order_items oi
      JOIN _order o ON oi.order_id = o.order_id
      JOIN products p ON oi.prod_pid = p.pid
      JOIN product_category_index pci ON p.pid = pci.pid
      JOIN product_categories pc ON pci.cat_id = pc.cat_id
      WHERE o.order_status > 15
        AND ${getCherryBoxClauseAliased('o', excludeCB)}
        AND ${whereClause.replace(/date_placed/g, 'o.date_placed')}
        AND pc.type IN (10, 20, 11, 21, 12, 13)
      GROUP BY pc.cat_id, pc.name
      HAVING revenue > 0
      ORDER BY revenue DESC
      LIMIT 100
    `;

    const [categoriesResult] = await connection.execute(categoriesQuery, params);
    
    // Shipping locations query - uses date_shipped to match shippedCount
    const shippingQuery = `
      SELECT
        ship_country,
        ship_state,
        ship_method_selected,
        COUNT(*) as count
      FROM _order
      WHERE order_status IN (92, 95, 100)
        AND ${getCherryBoxClause(excludeCB)}
        AND ${whereClause.replace(/date_placed/g, 'date_shipped')}
      GROUP BY ship_country, ship_state, ship_method_selected
    `;
    
    const [shippingResult] = await connection.execute(shippingQuery, params);
    
    // Process shipping data
    const shippingStats = processShippingData(shippingResult, shippedCount);
    
    // Order value range query (optionally excludes Cherry Box orders)
    // Excludes $0 orders from min calculation
    const orderRangeQuery = `
      SELECT
        MIN(CASE WHEN summary_total > 0 THEN summary_total END) as smallest,
        MAX(summary_total) as largest
      FROM _order
      WHERE order_status > 15 AND ${getCherryBoxClause(excludeCB)} AND ${whereClause}
    `;
    
    const [orderRangeResult] = await connection.execute(orderRangeQuery, params);
    
    // Calculate period progress for incomplete periods
    let periodProgress = 100;
    if (['today', 'thisWeek', 'thisMonth'].includes(timeRange)) {
      periodProgress = calculatePeriodProgress(timeRange);
    }
    
    // Previous period comparison data
    const prevPeriodData = await getPreviousPeriodData(connection, timeRange, startDate, endDate, excludeCB);
    
    const response = {
      timeRange: dateRange,
      stats: {
        revenue: parseFloat(stats.revenue || 0),
        orderCount: parseInt(stats.orderCount || 0),
        itemCount: parseInt(stats.itemCount || 0),
        averageOrderValue: parseFloat(stats.averageOrderValue || 0),
        averageItemsPerOrder: parseFloat(stats.averageItemsPerOrder || 0),
        
        // Order types
        orderTypes: {
          preOrders: {
            count: parseInt(stats.preOrderCount || 0),
            percentage: stats.orderCount > 0 ? (stats.preOrderCount / stats.orderCount) * 100 : 0
          },
          localPickup: {
            count: parseInt(stats.localPickupCount || 0),
            percentage: stats.orderCount > 0 ? (stats.localPickupCount / stats.orderCount) * 100 : 0
          },
          heldItems: {
            count: parseInt(stats.onHoldCount || 0),
            percentage: stats.orderCount > 0 ? (stats.onHoldCount / stats.orderCount) * 100 : 0
          }
        },
        
        // Shipping
        shipping: {
          shippedCount: parseInt(shippedCount || 0),
          locations: shippingStats.locations,
          methodStats: shippingStats.methods
        },
        
        // Brands and categories
        brands: {
          total: brandsResult.length,
          list: brandsResult.slice(0, 50).map(brand => ({
            id: brand.catId,
            name: brand.brandName,
            count: parseInt(brand.itemCount),
            revenue: parseFloat(brand.revenue)
          }))
        },

        categories: {
          total: categoriesResult.length,
          list: categoriesResult.slice(0, 50).map(category => ({
            id: category.catId,
            name: category.categoryName,
            count: parseInt(category.itemCount),
            revenue: parseFloat(category.revenue)
          }))
        },
        
        // Refunds and cancellations
        refunds: {
          total: parseFloat(refundStats[0]?.refundTotal || 0),
          count: parseInt(refundStats[0]?.refundCount || 0)
        },
        
        canceledOrders: {
          total: parseFloat(cancelledStats.cancelledTotal || 0),
          count: parseInt(cancelledStats.cancelledCount || 0)
        },
        
        // Best day
        bestRevenueDay: bestDayResult.length > 0 ? {
          amount: parseFloat(bestDayResult[0].revenue),
          displayDate: bestDayResult[0].date,
          orders: parseInt(bestDayResult[0].orders)
        } : null,
        
        // Peak hour (for single days)
        peakOrderHour: peakHour,
        hourlyOrders: hourlyOrders,  // Array of 24 hourly order counts for the detail chart

        // Order value range
        orderValueRange: orderRangeResult.length > 0 ? {
          smallest: parseFloat(orderRangeResult[0].smallest || 0),
          largest: parseFloat(orderRangeResult[0].largest || 0)
        } : { smallest: 0, largest: 0 },
        
        // Period progress and projections
        periodProgress,
        projectedRevenue: periodProgress < 100 ? (stats.revenue / (periodProgress / 100)) : stats.revenue,
        
        // Previous period comparison
        prevPeriodRevenue: prevPeriodData.revenue,
        prevPeriodOrders: prevPeriodData.orderCount,
        prevPeriodAOV: prevPeriodData.averageOrderValue
      }
    };
    
    return response;
      } finally {
        // Always release the connection regardless of whether the outer Promise.race
        // used our result. If the timeout wins, this IIFE keeps running in the
        // background until MySQL responds, then this finally releases. Without it,
        // every timed-out request permanently leaks one pool slot.
        release();
      }
    };

    const response = await Promise.race([mainOperation(), timeoutPromise]);

    console.log(`[STATS] Request completed in ${Date.now() - startTime}ms`);
    res.json(response);

  } catch (error) {
    if (error.message.includes('timeout')) {
      console.log(`[STATS] Request timed out in ${Date.now() - startTime}ms`);
    } else {
      console.error('Error in /stats:', error);
    }
    console.log(`[STATS] Request failed in ${Date.now() - startTime}ms`);
    res.status(500).json({ error: error.message });
  }
});

// Daily details endpoint - replaces /api/klaviyo/events/stats/details
router.get('/stats/details', async (req, res) => {
  let release;
  try {
    const { timeRange, startDate, endDate, metric, daily, excludeCherryBox, orderType, eventType } = req.query;
    const excludeCB = parseBoolParam(excludeCherryBox);
    const { connection, release: releaseConn } = await getDbConnection();
    release = releaseConn;

    const { whereClause, params } = getTimeRangeConditions(timeRange, startDate, endDate);

    // Handle special event types (refunds, cancellations)
    if (eventType === 'PAYMENT_REFUNDED') {
      // Refunds query - from order_payment table
      const refundsQuery = `
        SELECT
          DATE(op.payment_date) as date,
          COUNT(*) as count,
          ABS(SUM(op.payment_amount)) as total
        FROM order_payment op
        JOIN _order o ON op.order_id = o.order_id
        WHERE op.payment_amount < 0
          AND o.order_status > 15
          AND ${getCherryBoxClauseAliased('o', excludeCB)}
          AND ${whereClause.replace(/date_placed/g, 'op.payment_date')}
        GROUP BY DATE(op.payment_date)
        ORDER BY DATE(op.payment_date)
      `;

      const [refundResults] = await connection.execute(refundsQuery, params);

      // Format matches what frontend expects: day.refunds.total, day.refunds.count
      const stats = refundResults.map(day => ({
        timestamp: day.date,
        date: day.date,
        refunds: {
          total: parseFloat(day.total || 0),
          count: parseInt(day.count || 0),
          reasons: {}
        }
      }));

      if (release) release();
      return res.json({ stats });
    }

    if (eventType === 'CANCELED_ORDER') {
      // Cancellations query - uses date_cancelled to show when orders were actually cancelled
      const cancelQuery = `
        SELECT
          DATE(date_cancelled) as date,
          COUNT(*) as count,
          SUM(summary_total) as total
        FROM _order
        WHERE order_status = 15
          AND ${getCherryBoxClause(excludeCB)}
          AND ${whereClause.replace(/date_placed/g, 'date_cancelled')}
        GROUP BY DATE(date_cancelled)
        ORDER BY DATE(date_cancelled)
      `;

      const [cancelResults] = await connection.execute(cancelQuery, params);

      // Format matches what frontend expects: day.canceledOrders.total, day.canceledOrders.count
      const stats = cancelResults.map(day => ({
        timestamp: day.date,
        date: day.date,
        canceledOrders: {
          total: parseFloat(day.total || 0),
          count: parseInt(day.count || 0),
          reasons: {}
        }
      }));

      if (release) release();
      return res.json({ stats });
    }

    if (eventType === 'PLACED_ORDER') {
      // Order range query - daily min/max/average order values
      const orderRangeQuery = `
        SELECT
          DATE(date_placed) as date,
          COUNT(*) as orders,
          MIN(CASE WHEN summary_total > 0 THEN summary_total END) as smallest,
          MAX(summary_total) as largest,
          AVG(summary_total) as averageOrderValue
        FROM _order
        WHERE order_status > 15
          AND ${getCherryBoxClause(excludeCB)}
          AND ${whereClause}
        GROUP BY DATE(date_placed)
        ORDER BY DATE(date_placed)
      `;

      const [orderRangeResults] = await connection.execute(orderRangeQuery, params);

      // Format matches what frontend OrderRangeDetails expects
      const stats = orderRangeResults.map(day => ({
        timestamp: day.date,
        date: day.date,
        orders: parseInt(day.orders || 0),
        orderValueRange: {
          smallest: parseFloat(day.smallest || 0),
          largest: parseFloat(day.largest || 0)
        },
        averageOrderValue: parseFloat(day.averageOrderValue || 0)
      }));

      if (release) release();
      return res.json({ stats });
    }

    // Build order type filter based on orderType parameter
    let orderTypeFilter = '';
    if (orderType === 'pre_orders') {
      orderTypeFilter = 'AND stats_waiting_preorder > 0';
    } else if (orderType === 'local_pickup') {
      orderTypeFilter = "AND ship_method_selected = 'localpickup'";
    } else if (orderType === 'on_hold') {
      orderTypeFilter = "AND ship_method_selected = 'holdit'";
    }

    // Daily breakdown query (optionally excludes Cherry Box orders)
    const dailyQuery = `
      SELECT
        DATE(date_placed) as date,
        COUNT(*) as orders,
        SUM(summary_total) as revenue,
        AVG(summary_total) as averageOrderValue,
        SUM(stats_prod_pieces) as itemCount
      FROM _order
      WHERE order_status > 15 AND ${getCherryBoxClause(excludeCB)} AND ${whereClause} ${orderTypeFilter}
      GROUP BY DATE(date_placed)
      ORDER BY DATE(date_placed)
    `;

    const [dailyResults] = await connection.execute(dailyQuery, params);
    
    // Get previous period data using the same logic as main stats endpoint
    let prevWhereClause, prevParams;
    
    if (timeRange && timeRange !== 'custom') {
      const prevTimeRange = getPreviousTimeRange(timeRange);
      const result = getTimeRangeConditions(prevTimeRange);
      prevWhereClause = result.whereClause;
      prevParams = result.params;
    } else {
      // Custom date range - go back by the same duration (business-day aware)
      const prev = previousRange(customRange(startDate, endDate));
      prevWhereClause = 'date_placed >= ? AND date_placed < ?';
      prevParams = [toMySqlBound(prev.start), toMySqlBound(prev.end)];
    }

    // Get previous period daily data (optionally excludes Cherry Box orders)
    const prevQuery = `
      SELECT
        DATE(date_placed) as date,
        COUNT(*) as prevOrders,
        SUM(summary_total) as prevRevenue,
        AVG(summary_total) as prevAvgOrderValue
      FROM _order
      WHERE order_status > 15 AND ${getCherryBoxClause(excludeCB)} AND ${prevWhereClause} ${orderTypeFilter}
      GROUP BY DATE(date_placed)
    `;

    const [prevResults] = await connection.execute(prevQuery, prevParams);

    // Create a map for quick lookup of previous period data
    // (dateStrings: true — DATE columns arrive as 'YYYY-MM-DD' strings)
    const prevMap = new Map();
    prevResults.forEach(prev => {
      prevMap.set(String(prev.date), prev);
    });
    
    // For period-to-period comparison, we need to map days by relative position
    // since dates won't match exactly (e.g., current week vs previous week)
    const dailyArray = dailyResults.map(day => ({
      timestamp: day.date,
      date: day.date,
      orders: parseInt(day.orders),
      revenue: parseFloat(day.revenue),
      averageOrderValue: parseFloat(day.averageOrderValue || 0),
      itemCount: parseInt(day.itemCount)
    }));

    const prevArray = prevResults.map(day => ({
      orders: parseInt(day.prevOrders),
      revenue: parseFloat(day.prevRevenue),
      averageOrderValue: parseFloat(day.prevAvgOrderValue || 0)
    }));

    // Combine current and previous period data by matching relative positions
    const statsWithComparison = dailyArray.map((day, index) => {
      const prev = prevArray[index] || { orders: 0, revenue: 0, averageOrderValue: 0 };
      
      return {
        ...day,
        prevOrders: prev.orders,
        prevRevenue: prev.revenue,
        prevAvgOrderValue: prev.averageOrderValue
      };
    });
    
    res.json({ stats: statsWithComparison });
    
  } catch (error) {
    console.error('Error in /stats/details:', error);
    res.status(500).json({ error: error.message });
  } finally {
    // Release connection back to pool
    if (release) release();
  }
});

// Financial performance endpoint
router.get('/financials', async (req, res) => {
  let release;
  try {
    const { timeRange, startDate, endDate, excludeCherryBox } = req.query;
    const excludeCB = parseBoolParam(excludeCherryBox);
    const { connection, release: releaseConn } = await getDbConnection();
    release = releaseConn;

    const { whereClause, params, dateRange } = getTimeRangeConditions(timeRange, startDate, endDate);
    const financialWhere = whereClause.replace(/date_placed/g, 'date_change');

    const formatDebugBound = (value) => {
      if (!value) return 'n/a';
      const parsed = parseMySql(value);
      if (!parsed) {
        return `invalid(${value})`;
      }
      return parsed.setZone(TIMEZONE).toISO();
    };

    console.log('[FINANCIALS] request params', {
      timeRange: timeRange || 'default',
      startDate,
      endDate,
      whereClause: financialWhere,
      params,
      boundsEastern: Array.isArray(params) ? params.map(formatDebugBound) : [],
    });

    const [totalsRows] = await connection.execute(
      buildFinancialTotalsQuery(financialWhere, excludeCB),
      params
    );

    const totals = normalizeFinancialTotals(totalsRows[0]);

    console.log('[FINANCIALS] totals query result', {
      rows: totalsRows.length,
      totals,
    });

    const [trendRows] = await connection.execute(
      buildFinancialTrendQuery(financialWhere, excludeCB),
      params
    );

    const trend = trendRows.map(normalizeFinancialTrendRow);

    console.log('[FINANCIALS] trend query result', {
      rows: trendRows.length,
      first: trend[0] || null,
      last: trend[trend.length - 1] || null,
    });

    let previousTotals = null;
    let comparison = null;

    const previousRange = getPreviousPeriodRange(timeRange, startDate, endDate);
    if (previousRange) {
      console.log('[FINANCIALS] previous range params', {
        timeRange: timeRange || 'default',
        prevWhere: previousRange.whereClause.replace(/date_placed/g, 'date_change'),
        params: previousRange.params,
        boundsEastern: Array.isArray(previousRange.params)
          ? previousRange.params.map(formatDebugBound)
          : [],
      });
      const prevWhere = previousRange.whereClause.replace(/date_placed/g, 'date_change');
      const [previousRows] = await connection.execute(
        buildFinancialTotalsQuery(prevWhere, excludeCB),
        previousRange.params
      );
      previousTotals = normalizeFinancialTotals(previousRows[0]);
      comparison = {
        grossSales: calculateComparison(totals.grossSales, previousTotals.grossSales),
        refunds: calculateComparison(totals.refunds, previousTotals.refunds),
        taxCollected: calculateComparison(totals.taxCollected, previousTotals.taxCollected),
        discounts: calculateComparison(totals.discounts, previousTotals.discounts),
        cogs: calculateComparison(totals.cogs, previousTotals.cogs),
        income: calculateComparison(totals.income, previousTotals.income),
        profit: calculateComparison(totals.profit, previousTotals.profit),
        margin: calculateComparison(totals.margin, previousTotals.margin),
      };
    }

    const trendDebugSample = trend.slice(-3).map((item) => ({
      date: item.date,
      timestamp: item.timestamp,
      income: item.income,
      grossSales: item.grossSales,
    }));

    const debugInfo = {
      serverTimeUtc: new Date().toISOString(),
      timeRange: timeRange || 'default',
      params,
      boundsEastern: Array.isArray(params) ? params.map(formatDebugBound) : [],
      trendCount: trend.length,
      trendSample: trendDebugSample,
      previousRange: previousRange
        ? {
            params: previousRange.params,
            boundsEastern: Array.isArray(previousRange.params)
              ? previousRange.params.map(formatDebugBound)
              : [],
          }
        : null,
    };

    res.json({
      dateRange,
      totals,
      previousTotals,
      comparison,
      trend,
      debug: debugInfo,
    });
  } catch (error) {
    console.error('Error in /financials:', error);
    res.status(500).json({ error: error.message });
  } finally {
    if (release) release();
  }
});

// Products endpoint - replaces /api/klaviyo/events/products
router.get('/products', async (req, res) => {
  let release;
  try {
    const { timeRange, startDate, endDate, excludeCherryBox } = req.query;
    const excludeCB = parseBoolParam(excludeCherryBox);
    const { connection, release: releaseConn } = await getDbConnection();
    release = releaseConn;
    
    const { whereClause, params } = getTimeRangeConditions(timeRange, startDate, endDate);
    
    // Products query (optionally excludes Cherry Box orders)
    const productsQuery = `
      SELECT 
        p.pid,
        p.description as name,
        SUM(oi.qty_ordered) as totalQuantity,
        SUM(oi.qty_ordered * oi.prod_price) as totalRevenue,
        COUNT(DISTINCT oi.order_id) as orderCount,
        (SELECT pi.iid FROM product_images pi WHERE pi.pid = p.pid AND pi.order = 255 LIMIT 1) as primary_iid
      FROM order_items oi
      JOIN _order o ON oi.order_id = o.order_id
      JOIN products p ON oi.prod_pid = p.pid
      WHERE o.order_status > 15 AND ${getCherryBoxClauseAliased('o', excludeCB)} AND ${whereClause.replace(/date_placed/g, 'o.date_placed')}
      GROUP BY p.pid, p.description
      ORDER BY totalRevenue DESC
      LIMIT 500
    `;
    
    const [productsResult] = await connection.execute(productsQuery, params);
    
    // Add image URLs to each product
    const productsWithImages = productsResult.map(product => {
      const imageUrls = getImageUrls(product.pid, product.primary_iid || 1);
      return {
        id: product.pid,
        name: product.name,
        totalQuantity: parseInt(product.totalQuantity),
        totalRevenue: parseFloat(product.totalRevenue),
        orderCount: parseInt(product.orderCount),
        ...imageUrls
      };
    });
    
    res.json({
      stats: {
        products: {
          total: productsWithImages.length,
          list: productsWithImages
        }
      }
    });
    
  } catch (error) {
    console.error('Error in /products:', error);
    res.status(500).json({ error: error.message });
  } finally {
    // Release connection back to pool
    if (release) release();
  }
});

// Projection endpoint - replaces /api/klaviyo/events/projection
router.get('/projection', async (req, res) => {
  const startTime = Date.now();
  let release;
  try {
    const { timeRange, startDate, endDate, excludeCherryBox } = req.query;
    const excludeCB = parseBoolParam(excludeCherryBox);
    console.log(`[PROJECTION] Starting request for timeRange: ${timeRange}`);

    // Only provide projections for incomplete periods
    if (!['today', 'thisWeek', 'thisMonth'].includes(timeRange)) {
      return res.json({ projectedRevenue: 0, confidence: 0, method: 'none' });
    }

    const { connection, release: releaseConn } = await getDbConnection();
    release = releaseConn;
    console.log(`[PROJECTION] DB connection obtained in ${Date.now() - startTime}ms`);

    const now = DateTime.now().setZone(TIMEZONE);

    // Get current period data
    const { whereClause, params } = getTimeRangeConditions(timeRange, startDate, endDate);

    // Current period query (optionally excludes Cherry Box orders)
    const currentQuery = `
      SELECT
        SUM(summary_total) as currentRevenue,
        COUNT(*) as currentOrders
      FROM _order
      WHERE order_status > 15 AND ${getCherryBoxClause(excludeCB)} AND ${whereClause}
    `;

    const [currentResult] = await connection.execute(currentQuery, params);
    const current = currentResult[0];
    console.log(`[PROJECTION] Current period data fetched in ${Date.now() - startTime}ms`);

    // Fetch pattern data in parallel for performance
    const patternStart = Date.now();
    const [hourlyPattern, dayOfWeekPattern, dailyStats] = await Promise.all([
      getHourlyRevenuePattern(connection, excludeCB),
      getDayOfWeekRevenuePattern(connection, excludeCB),
      getAverageDailyRevenue(connection, excludeCB)
    ]);
    console.log(`[PROJECTION] Pattern data fetched in ${Date.now() - patternStart}ms`);

    // Calculate period progress (for logging/debugging)
    const periodProgress = calculatePeriodProgress(timeRange);

    // Calculate pattern-based projection
    const projection = calculateSmartProjection(
      timeRange,
      parseFloat(current.currentRevenue || 0),
      parseInt(current.currentOrders || 0),
      periodProgress,
      hourlyPattern,
      dayOfWeekPattern,
      dailyStats,
      now
    );

    // Add some useful debug info
    projection.periodProgress = periodProgress;
    projection.currentRevenue = parseFloat(current.currentRevenue || 0);
    projection.currentOrders = parseInt(current.currentOrders || 0);

    console.log(`[PROJECTION] Request completed in ${Date.now() - startTime}ms - method: ${projection.method}, projected: $${projection.projectedRevenue?.toFixed(2)}`);
    res.json(projection);

  } catch (error) {
    console.error(`[PROJECTION] Error after ${Date.now() - startTime}ms:`, error);
    res.status(500).json({ error: error.message });
  } finally {
    // Release connection back to pool
    if (release) release();
  }
});

// Debug endpoint to check connection pool status
router.get('/debug/pool', (req, res) => {
  res.json(getPoolStatus());
});

// Health check endpoint
router.get('/health', async (req, res) => {
  try {
    const { connection, release } = await getDbConnection();
    
    // Simple query to test connection
    const [result] = await connection.execute('SELECT 1 as test');
    release();
    
    res.json({ 
      status: 'healthy', 
      timestamp: new Date().toISOString(),
      pool: getPoolStatus(),
      dbTest: result[0]
    });
  } catch (error) {
    res.status(500).json({ 
      status: 'unhealthy', 
      error: error.message,
      timestamp: new Date().toISOString(),
      pool: getPoolStatus()
    });
  }
});

// Helper functions
function processShippingData(shippingResult, totalShipped) {
  const countries = {};
  const states = {};
  const methods = {};
  
  shippingResult.forEach(row => {
    // Countries
    if (row.ship_country) {
      countries[row.ship_country] = (countries[row.ship_country] || 0) + row.count;
    }
    
    // States
    if (row.ship_state) {
      states[row.ship_state] = (states[row.ship_state] || 0) + row.count;
    }
    
    // Methods
    if (row.ship_method_selected) {
      methods[row.ship_method_selected] = (methods[row.ship_method_selected] || 0) + row.count;
    }
  });
  
  return {
    locations: {
      total: Object.keys(states).length,  // Count of unique states/regions shipped to
      byCountry: Object.entries(countries)
        .map(([country, count]) => ({
          country,
          count,
          percentage: (count / totalShipped) * 100
        }))
        .sort((a, b) => b.count - a.count),
      byState: Object.entries(states)
        .map(([state, count]) => ({
          state,
          count,
          percentage: (count / totalShipped) * 100
        }))
        .sort((a, b) => b.count - a.count)
    },
    methods: Object.entries(methods)
      .map(([name, value]) => ({ name, value }))
      .sort((a, b) => b.value - a.value)
  };
}

function calculatePeriodProgress(timeRange) {
  if (!['today', 'thisWeek', 'thisMonth'].includes(timeRange)) {
    return 100;
  }

  const now = DateTime.now().setZone(TIMEZONE);

  let range;
  try {
    range = timeHelpers.getRangeForTimeRange(timeRange, now);
  } catch (error) {
    console.error(`[STATS] Failed to derive range for ${timeRange}:`, error);
    return 100;
  }

  if (!range?.start || !range?.end) {
    return 100;
  }

  const total = range.end.toMillis() - range.start.toMillis();
  if (total <= 0) {
    return 100;
  }

  const elapsed = Math.min(
    Math.max(now.toMillis() - range.start.toMillis(), 0),
    total
  );

  return Math.min(100, Math.max(0, (elapsed / total) * 100));
}

function buildFinancialTotalsQuery(whereClause, excludeCherryBox = false) {
  // Optionally join to _order to exclude Cherry Box orders
  if (excludeCherryBox) {
    return `
      SELECT 
        COALESCE(SUM(r.sale_amount), 0) as grossSales,
        COALESCE(SUM(r.refund_amount), 0) as refunds,
        COALESCE(SUM(r.shipping_collected_amount + r.small_order_fee_amount + r.rush_fee_amount), 0) as shippingFees,
        COALESCE(SUM(r.tax_collected_amount), 0) as taxCollected,
        COALESCE(SUM(r.discount_total_amount), 0) as discounts,
        COALESCE(SUM(r.cogs_amount), 0) as cogs
      FROM report_sales_data r
      JOIN _order o ON r.order_id = o.order_id
      WHERE ${whereClause.replace(/date_change/g, 'r.date_change')}
        AND r.action IN (1, 2, 3)
        AND ${getCherryBoxClauseAliased('o', true)}
    `;
  }
  return `
    SELECT 
      COALESCE(SUM(sale_amount), 0) as grossSales,
      COALESCE(SUM(refund_amount), 0) as refunds,
      COALESCE(SUM(shipping_collected_amount + small_order_fee_amount + rush_fee_amount), 0) as shippingFees,
      COALESCE(SUM(tax_collected_amount), 0) as taxCollected,
      COALESCE(SUM(discount_total_amount), 0) as discounts,
      COALESCE(SUM(cogs_amount), 0) as cogs
    FROM report_sales_data
    WHERE ${whereClause}
      AND action IN (1, 2, 3)
  `;
}

function buildFinancialTrendQuery(whereClause, excludeCherryBox = false) {
  // date_change stores Central wall-clock literals; business date = the
  // Central calendar date, so DATE_FORMAT(col) needs no hour shift.
  // Optionally join to _order to exclude Cherry Box orders
  if (excludeCherryBox) {
    return `
      SELECT
        DATE_FORMAT(r.date_change, '%Y-%m-%d') as businessDate,
        SUM(r.sale_amount) as grossSales,
        SUM(r.refund_amount) as refunds,
        SUM(r.shipping_collected_amount + r.small_order_fee_amount + r.rush_fee_amount) as shippingFees,
        SUM(r.tax_collected_amount) as taxCollected,
        SUM(r.discount_total_amount) as discounts,
        SUM(r.cogs_amount) as cogs
      FROM report_sales_data r
      JOIN _order o ON r.order_id = o.order_id
      WHERE ${whereClause.replace(/date_change/g, 'r.date_change')}
        AND r.action IN (1, 2, 3)
        AND ${getCherryBoxClauseAliased('o', true)}
      GROUP BY businessDate
      ORDER BY businessDate ASC
    `;
  }
  return `
    SELECT
      DATE_FORMAT(date_change, '%Y-%m-%d') as businessDate,
      SUM(sale_amount) as grossSales,
      SUM(refund_amount) as refunds,
      SUM(shipping_collected_amount + small_order_fee_amount + rush_fee_amount) as shippingFees,
      SUM(tax_collected_amount) as taxCollected,
      SUM(discount_total_amount) as discounts,
      SUM(cogs_amount) as cogs
    FROM report_sales_data
    WHERE ${whereClause}
      AND action IN (1, 2, 3)
    GROUP BY businessDate
    ORDER BY businessDate ASC
  `;
}

function normalizeFinancialTotals(row = {}) {
  const grossSales = parseFloat(row.grossSales || 0);
  const refunds = parseFloat(row.refunds || 0);
  const shippingFees = parseFloat(row.shippingFees || 0);
  const taxCollected = parseFloat(row.taxCollected || 0);
  const discounts = parseFloat(row.discounts || 0);
  const cogs = parseFloat(row.cogs || 0);
  const productNet = grossSales - refunds - discounts;
  const income = productNet + shippingFees;
  const profit = income - cogs;
  const margin = income !== 0 ? (profit / income) * 100 : 0;

  return {
    grossSales,
    refunds,
    shippingFees,
    taxCollected,
    discounts,
    cogs,
    income,
    profit,
    margin,
  };
}

function normalizeFinancialTrendRow(row = {}) {
  const grossSales = parseFloat(row.grossSales || 0);
  const refunds = parseFloat(row.refunds || 0);
  const shippingFees = parseFloat(row.shippingFees || 0);
  const taxCollected = parseFloat(row.taxCollected || 0);
  const discounts = parseFloat(row.discounts || 0);
  const cogs = parseFloat(row.cogs || 0);
  const productNet = grossSales - refunds - discounts;
  const income = productNet + shippingFees;
  const profit = income - cogs;
  const margin = income !== 0 ? (profit / income) * 100 : 0;
  let timestamp = null;
  let dateValue = row.businessDate || row.date || null;

  const resolveBusinessDayStart = (value) => {
    if (!value) {
      return null;
    }

    let dt;
    if (value instanceof Date) {
      dt = DateTime.fromJSDate(value, { zone: TIMEZONE });
    } else if (typeof value === 'string') {
      dt = DateTime.fromISO(value, { zone: TIMEZONE });
      if (!dt.isValid) {
        dt = DateTime.fromSQL(value, { zone: TIMEZONE });
      }
    }

    if (!dt || !dt.isValid) {
      return null;
    }

    const hour = BUSINESS_DAY_START_HOUR;
    return dt.set({
      hour,
      minute: 0,
      second: 0,
      millisecond: 0,
    });
  };

  const businessDayStart = resolveBusinessDayStart(dateValue);
  if (businessDayStart) {
    timestamp = businessDayStart.toUTC().toISO();
    dateValue = businessDayStart.toISO();
  } else if (row.date instanceof Date) {
    timestamp = new Date(row.date.getTime()).toISOString();
  } else if (typeof row.date === 'string') {
    timestamp = new Date(`${row.date}T00:00:00Z`).toISOString();
  }

  return {
    date: dateValue,
    grossSales,
    refunds,
    shippingFees,
    taxCollected,
    discounts,
    cogs,
    income,
    profit,
    margin,
    timestamp,
  };
}

function calculateComparison(currentValue, previousValue) {
  if (typeof previousValue !== 'number') {
    return { absolute: null, percentage: null };
  }

  const absolute = typeof currentValue === 'number' ? currentValue - previousValue : null;
  const percentage =
    absolute !== null && previousValue !== 0
      ? (absolute / Math.abs(previousValue)) * 100
      : null;

  return { absolute, percentage };
}

function getPreviousPeriodRange(timeRange, startDate, endDate) {
  if (timeRange && timeRange !== 'custom') {
    const prevTimeRange = getPreviousTimeRange(timeRange);
    if (!prevTimeRange || prevTimeRange === timeRange) {
      return null;
    }
    return getTimeRangeConditions(prevTimeRange);
  }

  const hasCustomDates = (timeRange === 'custom' || !timeRange) && startDate && endDate;
  if (!hasCustomDates) {
    return null;
  }

  try {
    const prev = previousRange(customRange(startDate, endDate));
    if (!prev) return null;
    return getTimeRangeConditions('custom', prev.start.toISO(), prev.end.toISO());
  } catch {
    return null;
  }
}

async function getPreviousPeriodData(connection, timeRange, startDate, endDate, excludeCherryBox = false) {
  // Calculate previous period dates
  let prevWhereClause, prevParams;
  
  if (timeRange && timeRange !== 'custom') {
    const prevTimeRange = getPreviousTimeRange(timeRange);
    const result = getTimeRangeConditions(prevTimeRange);
    prevWhereClause = result.whereClause;
    prevParams = result.params;
  } else {
    // Custom date range - go back by the same duration (business-day aware)
    const prev = previousRange(customRange(startDate, endDate));
    prevWhereClause = 'date_placed >= ? AND date_placed < ?';
    prevParams = [toMySqlBound(prev.start), toMySqlBound(prev.end)];
  }

  // Previous period query (optionally excludes Cherry Box orders)
  const prevQuery = `
    SELECT 
      COUNT(*) as orderCount,
      SUM(summary_total) as revenue,
      AVG(summary_total) as averageOrderValue
    FROM _order 
    WHERE order_status > 15 AND ${getCherryBoxClause(excludeCherryBox)} AND ${prevWhereClause}
  `;
  
  const [prevResult] = await connection.execute(prevQuery, prevParams);
  const prev = prevResult[0] || { orderCount: 0, revenue: 0, averageOrderValue: 0 };
  
  return {
    orderCount: parseInt(prev.orderCount || 0),
    revenue: parseFloat(prev.revenue || 0),
    averageOrderValue: parseFloat(prev.averageOrderValue || 0)
  };
}

function getPreviousTimeRange(timeRange) {
  const map = {
    today: 'yesterday',
    thisWeek: 'lastWeek',
    thisMonth: 'lastMonth',
    last7days: 'previous7days',
    last30days: 'previous30days',
    last90days: 'previous90days',
    yesterday: 'twoDaysAgo'
  };
  return map[timeRange] || timeRange;
}

/**
 * Get hourly revenue distribution pattern from last 8 weeks (same day of week)
 * Returns array of 24 objects with hour and avgShare (0-1 representing % of daily revenue)
 * Optimized: Uses JOIN instead of correlated subquery for O(n) instead of O(n²)
 */
async function getHourlyRevenuePattern(connection, excludeCherryBox = false) {
  const now = DateTime.now().setZone(TIMEZONE);
  const dayOfWeek = now.weekday; // 1=Monday, 7=Sunday (Luxon)
  const mysqlDayOfWeek = dayOfWeek === 7 ? 1 : dayOfWeek + 1;

  // Step 1: Get daily totals and hourly breakdowns in one efficient query
  const query = `
    SELECT
      hourly.hour_of_day,
      AVG(hourly.hour_revenue / daily.daily_revenue) as avgShare
    FROM (
      SELECT
        DATE(date_placed) as order_date,
        HOUR(date_placed) as hour_of_day,
        SUM(summary_total) as hour_revenue
      FROM _order
      WHERE order_status > 15
        AND ${getCherryBoxClause(excludeCherryBox)}
        AND date_placed >= DATE_SUB(NOW(), INTERVAL 8 WEEK)
        AND date_placed < DATE(NOW())
        AND DAYOFWEEK(date_placed) = ?
      GROUP BY DATE(date_placed), HOUR(date_placed)
    ) hourly
    JOIN (
      SELECT
        DATE(date_placed) as order_date,
        SUM(summary_total) as daily_revenue
      FROM _order
      WHERE order_status > 15
        AND ${getCherryBoxClause(excludeCherryBox)}
        AND date_placed >= DATE_SUB(NOW(), INTERVAL 8 WEEK)
        AND date_placed < DATE(NOW())
        AND DAYOFWEEK(date_placed) = ?
      GROUP BY DATE(date_placed)
      HAVING daily_revenue > 0
    ) daily ON hourly.order_date = daily.order_date
    GROUP BY hourly.hour_of_day
    ORDER BY hourly.hour_of_day
  `;

  const [result] = await connection.execute(query, [mysqlDayOfWeek, mysqlDayOfWeek]);

  // Convert to a full 24-hour array, filling gaps with 0
  const hourlyPattern = Array(24).fill(0).map((_, i) => ({ hour: i, avgShare: 0 }));
  result.forEach(row => {
    hourlyPattern[row.hour_of_day] = {
      hour: row.hour_of_day,
      avgShare: parseFloat(row.avgShare) || 0
    };
  });

  // Normalize so shares sum to 1.0
  const totalShare = hourlyPattern.reduce((sum, h) => sum + h.avgShare, 0);
  if (totalShare > 0) {
    hourlyPattern.forEach(h => h.avgShare = h.avgShare / totalShare);
  }

  return hourlyPattern;
}

/**
 * Get day-of-week revenue distribution pattern from last 8 weeks
 * Returns array of 7 objects with dayOfWeek (1-7, Sunday=1) and avgShare
 * Optimized: Uses JOIN instead of correlated subquery
 */
async function getDayOfWeekRevenuePattern(connection, excludeCherryBox = false) {
  const query = `
    SELECT
      daily.day_of_week,
      AVG(daily.day_revenue / weekly.weekly_revenue) as avgShare
    FROM (
      SELECT
        YEARWEEK(date_placed, 0) as year_week,
        DAYOFWEEK(date_placed) as day_of_week,
        SUM(summary_total) as day_revenue
      FROM _order
      WHERE order_status > 15
        AND ${getCherryBoxClause(excludeCherryBox)}
        AND date_placed >= DATE_SUB(NOW(), INTERVAL 8 WEEK)
        AND date_placed < DATE(NOW())
      GROUP BY YEARWEEK(date_placed, 0), DAYOFWEEK(date_placed)
    ) daily
    JOIN (
      SELECT
        YEARWEEK(date_placed, 0) as year_week,
        SUM(summary_total) as weekly_revenue
      FROM _order
      WHERE order_status > 15
        AND ${getCherryBoxClause(excludeCherryBox)}
        AND date_placed >= DATE_SUB(NOW(), INTERVAL 8 WEEK)
        AND date_placed < DATE(NOW())
      GROUP BY YEARWEEK(date_placed, 0)
      HAVING weekly_revenue > 0
    ) weekly ON daily.year_week = weekly.year_week
    GROUP BY daily.day_of_week
    ORDER BY daily.day_of_week
  `;

  const [result] = await connection.execute(query);

  // Convert to array indexed by MySQL day of week (1=Sunday, 2=Monday, etc.)
  const weekPattern = Array(8).fill(0).map((_, i) => ({ dayOfWeek: i, avgShare: 0 }));
  result.forEach(row => {
    weekPattern[row.day_of_week] = {
      dayOfWeek: row.day_of_week,
      avgShare: parseFloat(row.avgShare) || 0
    };
  });

  // Normalize (indices 1-7 are used, 0 is unused)
  const totalShare = weekPattern.slice(1).reduce((sum, d) => sum + d.avgShare, 0);
  if (totalShare > 0) {
    weekPattern.forEach(d => { if (d.dayOfWeek > 0) d.avgShare = d.avgShare / totalShare; });
  }

  return weekPattern;
}

/**
 * Get average daily revenue for projection (last 30 days, excluding today)
 * Also gets same-day-of-week stats for more accurate confidence calculation
 */
async function getAverageDailyRevenue(connection, excludeCherryBox = false) {
  const now = DateTime.now().setZone(TIMEZONE);
  const mysqlDayOfWeek = now.weekday === 7 ? 1 : now.weekday + 1;

  // Get both overall 30-day stats AND same-day-of-week stats
  const query = `
    SELECT
      AVG(daily_revenue) as avgDailyRevenue,
      STDDEV(daily_revenue) as stdDev,
      COUNT(*) as dayCount,
      (
        SELECT AVG(day_rev) FROM (
          SELECT SUM(summary_total) as day_rev
          FROM _order
          WHERE order_status > 15
            AND ${getCherryBoxClause(excludeCherryBox)}
            AND date_placed >= DATE_SUB(NOW(), INTERVAL 8 WEEK)
            AND date_placed < DATE(NOW())
            AND DAYOFWEEK(date_placed) = ${mysqlDayOfWeek}
          GROUP BY DATE(date_placed)
        ) same_day
      ) as sameDayAvg,
      (
        SELECT STDDEV(day_rev) FROM (
          SELECT SUM(summary_total) as day_rev
          FROM _order
          WHERE order_status > 15
            AND ${getCherryBoxClause(excludeCherryBox)}
            AND date_placed >= DATE_SUB(NOW(), INTERVAL 8 WEEK)
            AND date_placed < DATE(NOW())
            AND DAYOFWEEK(date_placed) = ${mysqlDayOfWeek}
          GROUP BY DATE(date_placed)
        ) same_day_std
      ) as sameDayStdDev,
      (
        SELECT COUNT(*) FROM (
          SELECT DATE(date_placed) as d
          FROM _order
          WHERE order_status > 15
            AND ${getCherryBoxClause(excludeCherryBox)}
            AND date_placed >= DATE_SUB(NOW(), INTERVAL 8 WEEK)
            AND date_placed < DATE(NOW())
            AND DAYOFWEEK(date_placed) = ${mysqlDayOfWeek}
          GROUP BY DATE(date_placed)
        ) same_day_count
      ) as sameDayCount
    FROM (
      SELECT
        DATE(date_placed) as order_date,
        SUM(summary_total) as daily_revenue
      FROM _order
      WHERE order_status > 15
        AND ${getCherryBoxClause(excludeCherryBox)}
        AND date_placed >= DATE_SUB(NOW(), INTERVAL 30 DAY)
        AND date_placed < DATE(NOW())
      GROUP BY DATE(date_placed)
    ) daily_totals
  `;

  const [result] = await connection.execute(query);
  const row = result[0] || {};

  return {
    avgDailyRevenue: parseFloat(row.avgDailyRevenue) || 0,
    stdDev: parseFloat(row.stdDev) || 0,
    dayCount: parseInt(row.dayCount) || 0,
    sameDayAvg: parseFloat(row.sameDayAvg) || 0,
    sameDayStdDev: parseFloat(row.sameDayStdDev) || 0,
    sameDayCount: parseInt(row.sameDayCount) || 0
  };
}

/**
 * Calculate meaningful confidence score based on multiple factors
 * Returns score between 0-1 and breakdown of contributing factors
 */
function calculateConfidence({
  expectedProgress,
  currentRevenue,
  patternProjection,
  historicalDailyAvg,
  sameDayStdDev,
  sameDayCount,
  stdDev,
  dayCount
}) {
  const factors = {};

  // Factor 1: Time Progress (0-0.3)
  // More time elapsed = more data = higher confidence
  // Scales from 0 at 0% to 0.3 at 100%
  factors.timeProgress = Math.min(0.3, expectedProgress * 0.35);

  // Factor 2: Historical Predictability via Coefficient of Variation (0-0.35)
  // CV = stdDev / mean - lower is more predictable
  // Use same-day-of-week stats if available (more relevant)
  const relevantStdDev = sameDayStdDev || stdDev || 0;
  const relevantAvg = historicalDailyAvg || 1;
  const cv = relevantStdDev / relevantAvg;

  // CV of 0.1 (10% variation) = very predictable = full points
  // CV of 0.5 (50% variation) = unpredictable = minimal points
  // Scale: CV 0.1 -> 0.35, CV 0.3 -> 0.15, CV 0.5+ -> 0.05
  if (cv <= 0.1) {
    factors.predictability = 0.35;
  } else if (cv <= 0.5) {
    factors.predictability = Math.max(0.05, 0.35 - (cv - 0.1) * 0.75);
  } else {
    factors.predictability = 0.05;
  }

  // Factor 3: Tracking Accuracy (0-0.25)
  // How well is today tracking the expected pattern?
  // If we're at 40% progress with 38-42% of expected revenue, that's good
  if (expectedProgress > 0.05 && historicalDailyAvg > 0) {
    const expectedRevenueSoFar = historicalDailyAvg * expectedProgress;
    const trackingRatio = currentRevenue / expectedRevenueSoFar;

    // Perfect tracking (ratio = 1.0) = full points
    // 20% off (ratio 0.8 or 1.2) = partial points
    // 50%+ off = minimal points
    const deviation = Math.abs(1 - trackingRatio);
    if (deviation <= 0.1) {
      factors.tracking = 0.25;
    } else if (deviation <= 0.3) {
      factors.tracking = 0.25 - (deviation - 0.1) * 0.5;
    } else if (deviation <= 0.5) {
      factors.tracking = 0.15 - (deviation - 0.3) * 0.4;
    } else {
      factors.tracking = 0.05;
    }
  } else {
    // Not enough progress to judge tracking
    factors.tracking = 0.1;
  }

  // Factor 4: Data Quality (0-0.1)
  // More historical data points = more reliable pattern
  const dataPoints = sameDayCount || Math.floor(dayCount / 7) || 0;
  // 8 weeks of same-day data = full points, less = proportionally less
  factors.dataQuality = Math.min(0.1, (dataPoints / 8) * 0.1);

  // Calculate total confidence score
  const score = Math.min(0.95, Math.max(0.1,
    factors.timeProgress +
    factors.predictability +
    factors.tracking +
    factors.dataQuality
  ));

  return { score, factors };
}

/**
 * Calculate pattern-based projection for different time ranges
 */
function calculateSmartProjection(
  timeRange,
  currentRevenue,
  currentOrders,
  periodProgress,
  hourlyPattern,
  dayOfWeekPattern,
  dailyStats,
  now
) {
  if (periodProgress >= 100) {
    return { projectedRevenue: currentRevenue, projectedOrders: currentOrders, confidence: 1.0 };
  }

  const currentHour = now.hour;
  const currentDayOfWeek = now.weekday === 7 ? 1 : now.weekday + 1; // Convert to MySQL day (1=Sunday)

  if (timeRange === 'today') {
    // Calculate expected progress based on hourly pattern
    // Sum up shares for all hours up to and including current hour
    let expectedProgress = 0;
    for (let h = 0; h <= currentHour; h++) {
      expectedProgress += hourlyPattern[h]?.avgShare || 0;
    }

    // Adjust for partial hour (how far through current hour we are)
    const minuteProgress = now.minute / 60;
    const currentHourShare = hourlyPattern[currentHour]?.avgShare || 0;
    expectedProgress = expectedProgress - currentHourShare + (currentHourShare * minuteProgress);

    // Avoid division by zero and handle edge cases
    if (expectedProgress <= 0.01) {
      // Very early in day, use linear projection with low confidence
      const linearProjection = currentRevenue / Math.max(periodProgress / 100, 0.01);
      return {
        projectedRevenue: linearProjection,
        projectedOrders: Math.round(currentOrders / Math.max(periodProgress / 100, 0.01)),
        confidence: 0.1,
        method: 'linear_fallback'
      };
    }

    const patternProjection = currentRevenue / expectedProgress;

    // Blend with historical average for stability early in day
    // Use same-day-of-week average if available, otherwise fall back to overall average
    const historicalDailyAvg = dailyStats.sameDayAvg || dailyStats.avgDailyRevenue || patternProjection;
    const actualWeight = Math.pow(expectedProgress, 0.8); // More weight to actual as day progresses
    const projectedRevenue = (patternProjection * actualWeight) + (historicalDailyAvg * (1 - actualWeight));

    // Calculate meaningful confidence based on multiple factors
    const confidence = calculateConfidence({
      expectedProgress,
      currentRevenue,
      patternProjection,
      historicalDailyAvg,
      sameDayStdDev: dailyStats.sameDayStdDev,
      sameDayCount: dailyStats.sameDayCount,
      stdDev: dailyStats.stdDev,
      dayCount: dailyStats.dayCount
    });

    return {
      projectedRevenue,
      projectedOrders: Math.round(currentOrders / expectedProgress),
      confidence: confidence.score,
      confidenceFactors: confidence.factors,
      method: 'hourly_pattern',
      debug: { expectedProgress, actualWeight, patternProjection, historicalDailyAvg }
    };
  }

  if (timeRange === 'thisWeek') {
    // Calculate revenue expected so far this week based on day-of-week pattern
    // And project remaining days

    // Days completed so far (Sunday = day 1 in MySQL)
    // If today is Tuesday (MySQL day 3), completed days are Sunday(1) and Monday(2)
    let expectedProgressSoFar = 0;
    for (let d = 1; d < currentDayOfWeek; d++) {
      expectedProgressSoFar += dayOfWeekPattern[d]?.avgShare || 0;
    }

    // Add partial progress through today using hourly pattern
    let todayExpectedProgress = 0;
    for (let h = 0; h <= currentHour; h++) {
      todayExpectedProgress += hourlyPattern[h]?.avgShare || 0;
    }
    const minuteProgress = now.minute / 60;
    const currentHourShare = hourlyPattern[currentHour]?.avgShare || 0;
    todayExpectedProgress = todayExpectedProgress - currentHourShare + (currentHourShare * minuteProgress);

    // Add today's partial contribution
    const todayFullShare = dayOfWeekPattern[currentDayOfWeek]?.avgShare || (1/7);
    expectedProgressSoFar += todayFullShare * todayExpectedProgress;

    // Avoid division by zero
    if (expectedProgressSoFar <= 0.01) {
      return {
        projectedRevenue: currentRevenue / Math.max(periodProgress / 100, 0.01),
        projectedOrders: Math.round(currentOrders / Math.max(periodProgress / 100, 0.01)),
        confidence: 0.1,
        method: 'linear_fallback'
      };
    }

    const projectedWeekRevenue = currentRevenue / expectedProgressSoFar;
    const projectedWeekOrders = Math.round(currentOrders / expectedProgressSoFar);

    // Calculate meaningful confidence
    const historicalWeeklyAvg = dailyStats.avgDailyRevenue * 7;
    const confidence = calculateConfidence({
      expectedProgress: expectedProgressSoFar,
      currentRevenue,
      patternProjection: projectedWeekRevenue,
      historicalDailyAvg: historicalWeeklyAvg,
      sameDayStdDev: dailyStats.stdDev * Math.sqrt(7), // Approximate weekly stdDev
      sameDayCount: Math.floor(dailyStats.dayCount / 7),
      stdDev: dailyStats.stdDev * Math.sqrt(7),
      dayCount: dailyStats.dayCount
    });

    return {
      projectedRevenue: projectedWeekRevenue,
      projectedOrders: projectedWeekOrders,
      confidence: confidence.score,
      confidenceFactors: confidence.factors,
      method: 'weekly_pattern',
      debug: { expectedProgressSoFar, currentDayOfWeek, todayExpectedProgress }
    };
  }

  if (timeRange === 'thisMonth') {
    // For month projection, use days elapsed and average daily revenue
    const currentDay = now.day;
    const daysInMonth = now.daysInMonth;

    // Calculate average daily revenue so far this month
    const daysElapsed = currentDay - 1; // Full days completed

    // Add partial progress through today
    let todayExpectedProgress = 0;
    for (let h = 0; h <= currentHour; h++) {
      todayExpectedProgress += hourlyPattern[h]?.avgShare || 0;
    }
    const minuteProgress = now.minute / 60;
    const currentHourShare = hourlyPattern[currentHour]?.avgShare || 0;
    todayExpectedProgress = todayExpectedProgress - currentHourShare + (currentHourShare * minuteProgress);

    const effectiveDaysElapsed = daysElapsed + todayExpectedProgress;

    if (effectiveDaysElapsed <= 0.1) {
      // Very early in month, use historical average
      const projectedRevenue = dailyStats.avgDailyRevenue * daysInMonth;
      return {
        projectedRevenue,
        projectedOrders: Math.round(currentOrders / Math.max(periodProgress / 100, 0.01)),
        confidence: 0.15,
        method: 'historical_monthly'
      };
    }

    // Calculate implied daily rate from current data
    const impliedDailyRate = currentRevenue / effectiveDaysElapsed;

    // Blend with historical average (more weight to actual data as month progresses)
    const actualWeight = Math.min(0.9, effectiveDaysElapsed / 10); // Full weight after ~10 days
    const blendedDailyRate = (impliedDailyRate * actualWeight) + (dailyStats.avgDailyRevenue * (1 - actualWeight));

    const projectedMonthRevenue = blendedDailyRate * daysInMonth;
    const projectedMonthOrders = Math.round((currentOrders / effectiveDaysElapsed) * daysInMonth);

    // Calculate meaningful confidence
    const historicalMonthlyAvg = dailyStats.avgDailyRevenue * daysInMonth;
    const confidence = calculateConfidence({
      expectedProgress: effectiveDaysElapsed / daysInMonth,
      currentRevenue,
      patternProjection: projectedMonthRevenue,
      historicalDailyAvg: historicalMonthlyAvg,
      sameDayStdDev: dailyStats.stdDev * Math.sqrt(daysInMonth),
      sameDayCount: 1, // Only ~1 month of same-month data typically
      stdDev: dailyStats.stdDev * Math.sqrt(daysInMonth),
      dayCount: dailyStats.dayCount
    });

    return {
      projectedRevenue: projectedMonthRevenue,
      projectedOrders: projectedMonthOrders,
      confidence: confidence.score,
      confidenceFactors: confidence.factors,
      method: 'monthly_blend',
      debug: { effectiveDaysElapsed, daysInMonth, impliedDailyRate, blendedDailyRate }
    };
  }

  // Fallback for any other case
  const linearProjection = currentRevenue / (periodProgress / 100);
  return {
    projectedRevenue: linearProjection,
    projectedOrders: Math.round(currentOrders / (periodProgress / 100)),
    confidence: Math.min(0.95, Math.max(0.1, periodProgress / 100)),
    method: 'linear_fallback'
  };
}

// Health check endpoint
router.get('/health', async (req, res) => {
  try {
    const poolStatus = getPoolStatus();
    
    // Test database connectivity
    const { connection, release } = await getDbConnection();
    await connection.execute('SELECT 1 as test');
    release();
    
    res.json({
      status: 'healthy',
      timestamp: new Date().toISOString(),
      pool: poolStatus,
      database: 'connected'
    });
  } catch (error) {
    console.error('Health check failed:', error);
    res.status(500).json({
      status: 'unhealthy',
      timestamp: new Date().toISOString(),
      error: error.message,
      pool: getPoolStatus()
    });
  }
});

// Debug endpoint for pool status
router.get('/debug/pool', (req, res) => {
  res.json({
    timestamp: new Date().toISOString(),
    pool: getPoolStatus()
  });
});

export default router;

