import fetch from 'node-fetch';
import { TimeManager } from '../../../shared/business-time/index.js';
import { RedisService } from './redis.service.js';
import _ from 'lodash';

const METRIC_IDS = {
  PLACED_ORDER: 'Y8cqcF',
  SHIPPED_ORDER: 'VExpdL',
  ACCOUNT_CREATED: 'TeeypV',
  CANCELED_ORDER: 'YjVMNg',
  NEW_BLOG_POST: 'YcxeDr',
  PAYMENT_REFUNDED: 'R7XUYh'
};

export class EventsService {
  constructor(apiKey, apiRevision, redis) {
    this.apiKey = apiKey;
    this.apiRevision = apiRevision;
    this.baseUrl = 'https://a.klaviyo.com/api';
    this.timeManager = new TimeManager();
    this.redisService = new RedisService(redis);
  }

  async getEvents(params = {}) {
    try {
      // Add request debouncing
      const requestKey = JSON.stringify(params);
      if (this._pendingRequests && this._pendingRequests[requestKey]) {
        return this._pendingRequests[requestKey];
      }

      // Try to get from cache first
      const cacheKey = this.redisService._getCacheKey('events', params);
      let cachedData = null;
      try {
        cachedData = await this.redisService.get(`${cacheKey}:raw`);
        if (cachedData) {
          cachedData.data = this._transformEvents(cachedData.data);
          return cachedData;
        }
      } catch (cacheError) {
        console.warn('[EventsService] Cache error:', cacheError);
      }

      this._pendingRequests = this._pendingRequests || {};
      this._pendingRequests[requestKey] = (async () => {
        let allEvents = [];
        let nextCursor = params.pageCursor;
        let pageCount = 0;
        
        const filter = params.filter || this._buildFilter(params);
        
        do {
          const queryParams = new URLSearchParams();
          if (filter) {
            queryParams.append('filter', filter);
          }
          queryParams.append('sort', params.sort || '-datetime');

          if (nextCursor) {
            queryParams.append('page[cursor]', nextCursor);
          }

          const url = `${this.baseUrl}/events?${queryParams.toString()}`;

          try {
            const response = await fetch(url, {
              method: 'GET',
              headers: {
                'Accept': 'application/json',
                'Content-Type': 'application/json',
                'Authorization': `Klaviyo-API-Key ${this.apiKey}`,
                'revision': this.apiRevision
              }
            });

            if (!response.ok) {
              const errorData = await response.json();
              console.error('[EventsService] API Error:', errorData);
              throw new Error(`Klaviyo API error: ${response.status} ${response.statusText}`);
            }

            const responseData = await response.json();
            allEvents = allEvents.concat(responseData.data || []);
            pageCount++;
            
            nextCursor = responseData.links?.next ? 
              new URL(responseData.links.next).searchParams.get('page[cursor]') : null;

            if (nextCursor) {
              await new Promise(resolve => setTimeout(resolve, 50));
            }
          } catch (fetchError) {
            console.error('[EventsService] Fetch error:', fetchError);
            throw fetchError;
          }
          
        } while (nextCursor);

        const transformedEvents = this._transformEvents(allEvents);

        const result = {
          data: transformedEvents,
          meta: {
            total_count: transformedEvents.length,
            page_count: pageCount
          }
        };

        try {
          const ttl = this.redisService._getTTL(params.timeRange);
          await this.redisService.set(`${cacheKey}:raw`, result, ttl);
        } catch (cacheError) {
          console.warn('[EventsService] Cache set error:', cacheError);
        }

        delete this._pendingRequests[requestKey];
        return result;
      })();

      return await this._pendingRequests[requestKey];
    } catch (error) {
      console.error('[EventsService] Error fetching events:', error);
      throw error;
    }
  }

  _buildFilter(params) {
    const filters = [];
    
    if (params.metricId) {
      filters.push(`equals(metric_id,"${params.metricId}")`);
    }

    if (params.startDate && params.endDate) {
      const startUtc = this.timeManager.formatForAPI(params.startDate);
      const endUtc = this.timeManager.formatForAPI(params.endDate);
      
      filters.push(`greater-or-equal(datetime,${startUtc})`);
      filters.push(`less-than(datetime,${endUtc})`);
    }

    if (params.profileId) {
      filters.push(`equals(profile_id,"${params.profileId}")`);
    }

    if (params.customFilters) {
      filters.push(...params.customFilters);
    }

    return filters.length > 0 ? (filters.length > 1 ? `and(${filters.join(',')})` : filters[0]) : null;
  }

  async getEventsByTimeRange(timeRange, options = {}) {
    const range = this.timeManager.getDateRange(timeRange);
    if (!range) {
      throw new Error('Invalid time range specified');
    }

    const params = {
      timeRange,
      startDate: range.start.toISO(),
      endDate: range.end.toISO(),
      metricId: options.metricId
    };

    // Try to get from cache first
    const cacheKey = this.redisService._getCacheKey('events', params);
    let cachedData = null;
    try {
      cachedData = await this.redisService.get(`${cacheKey}:raw`);
      if (cachedData) {
        // Transform cached events
        cachedData.data = this._transformEvents(cachedData.data);
        return cachedData;
      }
    } catch (cacheError) {
      console.warn('[EventsService] Cache error:', cacheError);
      // Continue with API request if cache fails
    }

    return this.getEvents(params);
  }

  async calculatePeriodStats(params = {}) {
    try {
      // Add request debouncing
      const requestKey = JSON.stringify(params);
      if (this._pendingStatRequests && this._pendingStatRequests[requestKey]) {
        return this._pendingStatRequests[requestKey];
      }

      // Get period dates
      let periodStart, periodEnd, prevPeriodStart, prevPeriodEnd;
      if (params.startDate && params.endDate) {
        periodStart = this.timeManager.getDayStart(this.timeManager.toDateTime(params.startDate));
        periodEnd = this.timeManager.getDayEnd(this.timeManager.toDateTime(params.endDate));
        const duration = periodEnd.diff(periodStart);
        prevPeriodStart = this.timeManager.getDayStart(periodStart.minus(duration));
        prevPeriodEnd = this.timeManager.getDayEnd(periodStart.minus({ milliseconds: 1 }));
      } else if (params.timeRange) {
        const range = this.timeManager.getDateRange(params.timeRange);
        periodStart = range.start;
        periodEnd = range.end;
        const prevRange = this.timeManager.getPreviousPeriod(params.timeRange);
        prevPeriodStart = prevRange.start;
        prevPeriodEnd = prevRange.end;
      }

      // Load both current and previous period data
      const [orderData, shippedData, refundData, canceledData, prevPeriodData] = await Promise.all([
        this.getEvents({
          ...params,
          metricId: METRIC_IDS.PLACED_ORDER
        }),
        this.getEvents({
          ...params,
          metricId: METRIC_IDS.SHIPPED_ORDER
        }),
        this.getEvents({
          ...params,
          metricId: METRIC_IDS.PAYMENT_REFUNDED
        }),
        this.getEvents({
          ...params,
          metricId: METRIC_IDS.CANCELED_ORDER
        }),
        this.getEvents({
          // Only pass through non-date related params for previous period
          ..._.omit(params, ['timeRange', 'startDate', 'endDate']),
          metricId: METRIC_IDS.PLACED_ORDER,
          startDate: prevPeriodStart.toISO(),
          endDate: prevPeriodEnd.toISO()
        })
      ]);

      // Add debug logging
      console.log('[EventsService] Previous period request:', {
        params: _.omit(params, ['timeRange', 'startDate', 'endDate']),
        dates: {
          start: prevPeriodStart.toISO(),
          end: prevPeriodEnd.toISO()
        },
        responseLength: prevPeriodData?.data?.length
      });

      // Transform all data
      const transformedOrders = this._transformEvents(orderData.data);
      const transformedShipped = this._transformEvents(shippedData.data);
      const transformedRefunds = this._transformEvents(refundData.data);
      const transformedCanceled = this._transformEvents(canceledData.data);
      const transformedPrevPeriod = this._transformEvents(prevPeriodData.data);

      // Calculate previous period stats
      const prevPeriodRevenue = transformedPrevPeriod.reduce((sum, order) => {
        const props = order.event_properties || {};
        return sum + Number(props.TotalAmount || 0);
      }, 0);

      const prevPeriodOrders = transformedPrevPeriod.length;
      const prevPeriodAOV = prevPeriodOrders > 0 ? prevPeriodRevenue / prevPeriodOrders : 0;

      // Calculate stats with all data available
      const stats = {
        orderCount: 0,
        revenue: 0,
        averageOrderValue: 0,
        itemCount: 0,
        prevPeriodRevenue,
        projectedRevenue: 0,
        periodProgress: 0,
        dailyData: [],
        products: {
          list: [],
          total: 0,
          status: {
            backordered: 0,
            inStock: 0,
            outOfStock: 0,
            preorder: 0
          }
        },
        shipping: {
          shippedCount: 0,
          locations: {
            byState: [],
            byCountry: []
          },
          methods: {},
          methodPercentages: {},
          totalRevenue: 0,
          averageShipTime: 0,
          totalShipTime: 0
        },
        refunds: {
          total: 0,
          count: 0,
          reasons: {},
          items: [],
          averageAmount: 0
        },
        canceledOrders: {
          total: 0,
          count: 0,
          reasons: {},
          items: [],
          averageAmount: 0
        },
        orderTypes: {
          preOrders: { count: 0, percentage: 0, value: 0, items: [], averageValue: 0 },
          localPickup: { count: 0, percentage: 0, value: 0, items: [], averageValue: 0 },
          heldItems: { count: 0, percentage: 0, value: 0, items: [], averageValue: 0 },
          digital: { count: 0, percentage: 0, value: 0, items: [], averageValue: 0 },
          giftCard: { count: 0, percentage: 0, value: 0, items: [], averageValue: 0 }
        },
        brands: {
          total: 0,
          list: [],
          topBrands: [],
          totalRevenue: 0,
          averageOrderValue: 0
        },
        categories: {
          total: 0,
          list: [],
          topCategories: [],
          totalRevenue: 0,
          averageOrderValue: 0
        },
        hourlyOrders: Array(24).fill(0),
        peakOrderHour: null,
        bestRevenueDay: null,
        orderValueRange: {
          largest: 0,
          smallest: 0,
          largestOrderId: null,
          smallestOrderId: null,
          distribution: {
            under25: { count: 0, total: 0 },
            under50: { count: 0, total: 0 },
            under100: { count: 0, total: 0 },
            under200: { count: 0, total: 0 },
            over200: { count: 0, total: 0 }
          }
        }
      };

      // Calculate period progress
      if (periodStart && periodEnd) {
        const totalDuration = periodEnd.diff(periodStart);
        const elapsedDuration = this.timeManager.getNow().diff(periodStart);
        stats.periodProgress = Math.min(100, Math.max(0, (elapsedDuration.milliseconds / totalDuration.milliseconds) * 100));
      }

      // Process orders
      const brandMap = new Map();
      const categoryMap = new Map();
      const dailyOrderCounts = {};
      const dailyStats = new Map();

      // Track best day stats
      let bestDay = {
        date: null,
        displayDate: null,
        amount: 0,
        orderCount: 0
      };

      // Initialize daily stats for the entire date range
      if (periodStart && periodEnd) {
        let currentDate = periodStart;
        while (currentDate <= periodEnd) {
          const dateKey = currentDate.toFormat('yyyy-MM-dd');
          dailyStats.set(dateKey, {
            date: currentDate.toISO(), // ISO format for chart library
            timestamp: dateKey,
            revenue: 0,
            orders: 0,
            itemCount: 0,
            averageOrderValue: 0,
            averageItemsPerOrder: 0,
            hourlyOrders: Array(24).fill(0),
            refunds: { total: 0, count: 0, reasons: {}, items: [] },
            canceledOrders: { total: 0, count: 0, reasons: {}, items: [] },
            orderTypes: {
              preOrders: { count: 0, percentage: 0, value: 0, items: [], averageValue: 0 },
              localPickup: { count: 0, percentage: 0, value: 0, items: [], averageValue: 0 },
              heldItems: { count: 0, percentage: 0, value: 0, items: [], averageValue: 0 },
              digital: { count: 0, percentage: 0, value: 0, items: [], averageValue: 0 },
              giftCard: { count: 0, percentage: 0, value: 0, items: [], averageValue: 0 }
            },
            orderValueRange: {
              largest: 0,
              smallest: 0,
              largestOrderId: null,
              smallestOrderId: null,
              distribution: {
                under25: { count: 0, total: 0 },
                under50: { count: 0, total: 0 },
                under100: { count: 0, total: 0 },
                under200: { count: 0, total: 0 },
                over200: { count: 0, total: 0 }
              }
            }
          });
          currentDate = this.timeManager.getDayStart(currentDate.plus({ days: 1 }));
        }
      }

      // Track peak hour stats
      let maxHourCount = 0;
      let peakHour = 0;

      for (const order of transformedOrders) {
        const props = order.event_properties || {};
        const items = props.Items || [];
        const totalAmount = Number(props.TotalAmount || 0);
        const datetime = this.timeManager.toDateTime(order.attributes?.datetime);
        const orderId = props.OrderId;

        // Update order counts and revenue
        stats.orderCount++;
        stats.revenue += totalAmount;
        stats.itemCount += items.length;

        // Calculate running AOV
        stats.averageOrderValue = stats.revenue / stats.orderCount;

        // Track daily stats
        if (datetime) {
          const dayStart = this.timeManager.getDayStart(datetime);
          const dateKey = dayStart.toFormat('yyyy-MM-dd');
          const hourOfDay = datetime.hour;
          
          // Initialize stats for this date if it doesn't exist
          if (!dailyStats.has(dateKey)) {
            dailyStats.set(dateKey, {
              date: datetime.toISO(), // ISO format for chart library
              timestamp: dateKey,
              revenue: 0,
              orders: 0,
              itemCount: 0,
              averageOrderValue: 0,
              averageItemsPerOrder: 0,
              hourlyOrders: Array(24).fill(0),
              refunds: { total: 0, count: 0, reasons: {}, items: [] },
              canceledOrders: { total: 0, count: 0, reasons: {}, items: [] },
              orderTypes: {
                preOrders: { count: 0, percentage: 0, value: 0, items: [], averageValue: 0 },
                localPickup: { count: 0, percentage: 0, value: 0, items: [], averageValue: 0 },
                heldItems: { count: 0, percentage: 0, value: 0, items: [], averageValue: 0 },
                digital: { count: 0, percentage: 0, value: 0, items: [], averageValue: 0 },
                giftCard: { count: 0, percentage: 0, value: 0, items: [], averageValue: 0 }
              },
              orderValueRange: {
                largest: 0,
                smallest: 0,
                largestOrderId: null,
                smallestOrderId: null,
                distribution: {
                  under25: { count: 0, total: 0 },
                  under50: { count: 0, total: 0 },
                  under100: { count: 0, total: 0 },
                  under200: { count: 0, total: 0 },
                  over200: { count: 0, total: 0 }
                }
              }
            });
          }

          const dayStats = dailyStats.get(dateKey);

          // Update daily stats
          dayStats.revenue += totalAmount;
          dayStats.orders++;
          dayStats.itemCount += items.length;
          dayStats.hourlyOrders[hourOfDay]++;
          dayStats.averageOrderValue = dayStats.revenue / dayStats.orders;
          dayStats.averageItemsPerOrder = dayStats.itemCount / dayStats.orders;

          // Track best day
          if (dayStats.revenue > bestDay.amount) {
            bestDay = {
              date: dateKey,
              displayDate: dayStart.toFormat('LLL d, yyyy'),
              amount: dayStats.revenue,
              orderCount: dayStats.orders
            };
          }

          // Track daily order value range
          if (totalAmount > dayStats.orderValueRange.largest) {
            dayStats.orderValueRange.largest = totalAmount;
            dayStats.orderValueRange.largestOrderId = orderId;
          }
          if (totalAmount > 0) {
            if (dayStats.orderValueRange.smallest === 0 || totalAmount < dayStats.orderValueRange.smallest) {
              dayStats.orderValueRange.smallest = totalAmount;
              dayStats.orderValueRange.smallestOrderId = orderId;
            }

            // Track order value distribution
            if (totalAmount < 25) {
              dayStats.orderValueRange.distribution.under25.count++;
              dayStats.orderValueRange.distribution.under25.total += totalAmount;
            } else if (totalAmount < 50) {
              dayStats.orderValueRange.distribution.under50.count++;
              dayStats.orderValueRange.distribution.under50.total += totalAmount;
            } else if (totalAmount < 100) {
              dayStats.orderValueRange.distribution.under100.count++;
              dayStats.orderValueRange.distribution.under100.total += totalAmount;
            } else if (totalAmount < 200) {
              dayStats.orderValueRange.distribution.under200.count++;
              dayStats.orderValueRange.distribution.under200.total += totalAmount;
            } else {
              dayStats.orderValueRange.distribution.over200.count++;
              dayStats.orderValueRange.distribution.over200.total += totalAmount;
            }
          }

          // Track order types
          if (props.HasPreorder) {
            dayStats.orderTypes.preOrders.count++;
            dayStats.orderTypes.preOrders.value += totalAmount;
            dayStats.orderTypes.preOrders.percentage = (dayStats.orderTypes.preOrders.count / dayStats.orders) * 100;
            dayStats.orderTypes.preOrders.averageValue = dayStats.orderTypes.preOrders.value / dayStats.orderTypes.preOrders.count;
          }
          if (props.LocalPickup) {
            dayStats.orderTypes.localPickup.count++;
            dayStats.orderTypes.localPickup.value += totalAmount;
            dayStats.orderTypes.localPickup.percentage = (dayStats.orderTypes.localPickup.count / dayStats.orders) * 100;
            dayStats.orderTypes.localPickup.averageValue = dayStats.orderTypes.localPickup.value / dayStats.orderTypes.localPickup.count;
          }
          if (props.IsOnHold) {
            dayStats.orderTypes.heldItems.count++;
            dayStats.orderTypes.heldItems.value += totalAmount;
            dayStats.orderTypes.heldItems.percentage = (dayStats.orderTypes.heldItems.count / dayStats.orders) * 100;
            dayStats.orderTypes.heldItems.averageValue = dayStats.orderTypes.heldItems.value / dayStats.orderTypes.heldItems.count;
          }
          if (props.HasDigiItem) {
            dayStats.orderTypes.digital.count++;
            dayStats.orderTypes.digital.value += totalAmount;
            dayStats.orderTypes.digital.percentage = (dayStats.orderTypes.digital.count / dayStats.orders) * 100;
            dayStats.orderTypes.digital.averageValue = dayStats.orderTypes.digital.value / dayStats.orderTypes.digital.count;
          }
          if (props.HasDigitalGC) {
            dayStats.orderTypes.giftCard.count++;
            dayStats.orderTypes.giftCard.value += totalAmount;
            dayStats.orderTypes.giftCard.percentage = (dayStats.orderTypes.giftCard.count / dayStats.orders) * 100;
            dayStats.orderTypes.giftCard.averageValue = dayStats.orderTypes.giftCard.value / dayStats.orderTypes.giftCard.count;
          }

          // Update hourly stats for peak hour calculation
          if (dayStats.hourlyOrders[hourOfDay] > maxHourCount) {
            maxHourCount = dayStats.hourlyOrders[hourOfDay];
            peakHour = hourOfDay;
          }

          dailyStats.set(dateKey, dayStats);
        }

        // Track order value range
        if (totalAmount > stats.orderValueRange.largest) {
          stats.orderValueRange.largest = totalAmount;
          stats.orderValueRange.largestOrderId = orderId;
        }
        if (totalAmount > 0) {
          if (stats.orderValueRange.smallest === 0 || totalAmount < stats.orderValueRange.smallest) {
            stats.orderValueRange.smallest = totalAmount;
            stats.orderValueRange.smallestOrderId = orderId;
          }

          // Track order value distribution
          if (totalAmount < 25) {
            stats.orderValueRange.distribution.under25.count++;
            stats.orderValueRange.distribution.under25.total += totalAmount;
          } else if (totalAmount < 50) {
            stats.orderValueRange.distribution.under50.count++;
            stats.orderValueRange.distribution.under50.total += totalAmount;
          } else if (totalAmount < 100) {
            stats.orderValueRange.distribution.under100.count++;
            stats.orderValueRange.distribution.under100.total += totalAmount;
          } else if (totalAmount < 200) {
            stats.orderValueRange.distribution.under200.count++;
            stats.orderValueRange.distribution.under200.total += totalAmount;
          } else {
            stats.orderValueRange.distribution.over200.count++;
            stats.orderValueRange.distribution.over200.total += totalAmount;
          }
        }

        // Track order types with values
        if (props.HasPreorder) {
          stats.orderTypes.preOrders.count++;
          stats.orderTypes.preOrders.value += totalAmount;
          stats.orderTypes.preOrders.items.push({ orderId, amount: totalAmount, items });
          stats.orderTypes.preOrders.percentage = (stats.orderTypes.preOrders.count / stats.orderCount) * 100;
          stats.orderTypes.preOrders.averageValue = stats.orderTypes.preOrders.value / stats.orderTypes.preOrders.count;
        }
        if (props.LocalPickup) {
          stats.orderTypes.localPickup.count++;
          stats.orderTypes.localPickup.value += totalAmount;
          stats.orderTypes.localPickup.items.push({ orderId, amount: totalAmount, items });
          stats.orderTypes.localPickup.percentage = (stats.orderTypes.localPickup.count / stats.orderCount) * 100;
          stats.orderTypes.localPickup.averageValue = stats.orderTypes.localPickup.value / stats.orderTypes.localPickup.count;
        }
        if (props.IsOnHold) {
          stats.orderTypes.heldItems.count++;
          stats.orderTypes.heldItems.value += totalAmount;
          stats.orderTypes.heldItems.items.push({ orderId, amount: totalAmount, items });
          stats.orderTypes.heldItems.percentage = (stats.orderTypes.heldItems.count / stats.orderCount) * 100;
          stats.orderTypes.heldItems.averageValue = stats.orderTypes.heldItems.value / stats.orderTypes.heldItems.count;
        }
        if (props.HasDigiItem) {
          stats.orderTypes.digital.count++;
          stats.orderTypes.digital.value += totalAmount;
          stats.orderTypes.digital.items.push({ orderId, amount: totalAmount, items });
          stats.orderTypes.digital.percentage = (stats.orderTypes.digital.count / stats.orderCount) * 100;
          stats.orderTypes.digital.averageValue = stats.orderTypes.digital.value / stats.orderTypes.digital.count;
        }
        if (props.HasDigitalGC) {
          stats.orderTypes.giftCard.count++;
          stats.orderTypes.giftCard.value += totalAmount;
          stats.orderTypes.giftCard.items.push({ orderId, amount: totalAmount, items });
          stats.orderTypes.giftCard.percentage = (stats.orderTypes.giftCard.count / stats.orderCount) * 100;
          stats.orderTypes.giftCard.averageValue = stats.orderTypes.giftCard.value / stats.orderTypes.giftCard.count;
        }

        // Track hourly and daily stats
        if (datetime) {
          const hour = datetime.hour;
          stats.hourlyOrders[hour]++;
        }

        // Process products and related data
        for (const item of items) {
          const productId = item.ProductID;
          const quantity = Number(item.Quantity || item.QuantityOrdered || 1);
          const revenue = Number(item.RowTotal || (item.ItemPrice * quantity) || 0);
          
          // Track item status
          switch(item.ItemStatus?.toLowerCase()) {
            case 'backordered':
              stats.products.status.backordered++;
              break;
            case 'out of stock':
              stats.products.status.outOfStock++;
              break;
            case 'preorder':
              stats.products.status.preorder++;
              break;
            default:
              stats.products.status.inStock++;
          }

          // Update product stats
          const existingProduct = stats.products.list.find(p => p.id === productId);
          if (existingProduct) {
            existingProduct.totalQuantity += quantity;
            existingProduct.totalRevenue += revenue;
            existingProduct.orderCount++;
            existingProduct.orders.add(orderId);
          } else {
            stats.products.list.push({
              id: productId,
              sku: item.SKU,
              name: item.ProductName,
              brand: item.Brand,
              price: Number(item.ItemPrice || 0),
              ImgThumb: item.ImgThumb,
              totalQuantity: quantity,
              totalRevenue: revenue,
              orderCount: 1,
              orders: new Set([orderId]),
              categories: item.Categories || [],
              status: item.ItemStatus || 'In Stock'
            });
          }

          // Update brand stats
          if (item.Brand) {
            const brand = brandMap.get(item.Brand) || {
              name: item.Brand,
              quantity: 0,
              revenue: 0,
              products: new Set()
            };
            brand.quantity += quantity;
            brand.revenue += revenue;
            brand.products.add(productId);
            brandMap.set(item.Brand, brand);
          }

          // Update category stats
          if (item.Categories) {
            for (const category of item.Categories) {
              const categoryStats = categoryMap.get(category) || {
                name: category,
                quantity: 0,
                revenue: 0,
                products: new Set()
              };
              categoryStats.quantity += quantity;
              categoryStats.revenue += revenue;
              categoryStats.products.add(productId);
              categoryMap.set(category, categoryStats);
            }
          }
        }
      }

      // After processing all orders
      if (stats.orderCount > 0) {
        stats.averageOrderValue = stats.revenue / stats.orderCount;
        stats.averageItemsPerOrder = stats.itemCount / stats.orderCount;
      }

      // Calculate projected revenue for incomplete periods
      if (periodStart && periodEnd) {
        const totalDuration = periodEnd.diff(periodStart);
        const elapsedDuration = this.timeManager.getNow().diff(periodStart);
        const periodProgress = Math.min(100, Math.max(0, (elapsedDuration.milliseconds / totalDuration.milliseconds) * 100));
        
        if (periodProgress > 0 && periodProgress < 100) {
          stats.projectedRevenue = (stats.revenue / (periodProgress / 100));
        } else {
          stats.projectedRevenue = stats.revenue;
        }
        stats.periodProgress = periodProgress;
      }

      // Calculate trend data only for revenue
      if (prevPeriodRevenue > 0) {
        stats.trend = {
          revenue: ((stats.revenue - prevPeriodRevenue) / prevPeriodRevenue) * 100
        };
      }

      // Process shipped orders with better formatting
      const shippingMethodMap = new Map();
      const locationMap = new Map();
      const stateMap = new Map();
      const countryMap = new Map();
      
      for (const shipped of transformedShipped) {
        const props = shipped.event_properties || {};
        stats.shipping.shippedCount++;
        
        // Track shipping methods
        const method = props.ShipMethod || props.ShippingMethod || 'Unknown';
        const currentMethodCount = shippingMethodMap.get(method) || 0;
        shippingMethodMap.set(method, currentMethodCount + 1);

        // Track locations by state and country
        const state = props.ShippingState?.trim() || 'Unknown State';
        const country = props.ShippingCountry?.trim() || 'Unknown Country';

        // Track unique locations
        const locationKey = `${state}-${country}`;
        locationMap.set(locationKey, true);

        // Track by state
        const stateStats = stateMap.get(state) || { count: 0, country };
        stateStats.count++;
        stateMap.set(state, stateStats);

        // Track by country
        const countryStats = countryMap.get(country) || { count: 0, states: new Set() };
        countryStats.count++;
        countryStats.states.add(state);
        countryMap.set(country, countryStats);
      }

      // Format shipping methods
      stats.shipping.methods = Object.fromEntries(shippingMethodMap);
      stats.shipping.methodPercentages = {};
      stats.shipping.methodStats = [];
      shippingMethodMap.forEach((count, method) => {
        const percentage = (count / stats.shipping.shippedCount) * 100;
        stats.shipping.methodPercentages[method] = percentage;
        stats.shipping.methodStats.push({
          name: method,
          value: count,
          percentage
        });
      });
      stats.shipping.methodStats.sort((a, b) => b.value - a.value);

      // Format locations by state and country
      stats.shipping.locations = {
        total: locationMap.size,
        byState: Array.from(stateMap.entries())
          .map(([state, data]) => ({
            state,
            country: data.country,
            count: data.count,
            percentage: (data.count / stats.shipping.shippedCount) * 100
          }))
          .sort((a, b) => b.count - a.count),
        byCountry: Array.from(countryMap.entries())
          .map(([country, data]) => ({
            country,
            count: data.count,
            states: Array.from(data.states),
            percentage: (data.count / stats.shipping.shippedCount) * 100
          }))
          .sort((a, b) => b.count - a.count)
      };

      // Process refunds with more detail
      for (const refund of transformedRefunds) {
        const props = refund.event_properties || {};
        const amount = Number(props.PaymentAmount || 0);
        const reason = props.CancelReason || props.OrderMessage || 'No reason provided';
        const datetime = this.timeManager.toDateTime(refund.attributes?.datetime);
        const orderId = props.OrderId || props.FromOrder;
        
        stats.refunds.total += amount;
        stats.refunds.count++;
        stats.refunds.reasons[reason] = (stats.refunds.reasons[reason] || 0) + 1;
        stats.refunds.items.push({
          orderId,
          amount,
          reason,
          datetime: datetime?.toISO()
        });
        stats.refunds.averageAmount = stats.refunds.total / stats.refunds.count;

        // Track daily refunds
        if (datetime) {
          const dayStart = this.timeManager.getDayStart(datetime);
          const dateKey = dayStart.toFormat('yyyy-MM-dd');
          // Initialize stats for this date if it doesn't exist
          if (!dailyStats.has(dateKey)) {
            dailyStats.set(dateKey, {
              date: datetime.toISO(), // ISO format for chart library
              timestamp: dateKey,
              revenue: 0,
              orders: 0,
              itemCount: 0,
              averageOrderValue: 0,
              averageItemsPerOrder: 0,
              hourlyOrders: Array(24).fill(0),
              refunds: { total: 0, count: 0, reasons: {}, items: [] },
              canceledOrders: { total: 0, count: 0, reasons: {}, items: [] },
              orderTypes: {
                preOrders: { count: 0, percentage: 0, value: 0, items: [], averageValue: 0 },
                localPickup: { count: 0, percentage: 0, value: 0, items: [], averageValue: 0 },
                heldItems: { count: 0, percentage: 0, value: 0, items: [], averageValue: 0 },
                digital: { count: 0, percentage: 0, value: 0, items: [], averageValue: 0 },
                giftCard: { count: 0, percentage: 0, value: 0, items: [], averageValue: 0 }
              },
              orderValueRange: {
                largest: 0,
                smallest: 0,
                largestOrderId: null,
                smallestOrderId: null,
                distribution: {
                  under25: { count: 0, total: 0 },
                  under50: { count: 0, total: 0 },
                  under100: { count: 0, total: 0 },
                  under200: { count: 0, total: 0 },
                  over200: { count: 0, total: 0 }
                }
              }
            });
          }
          const dayStats = dailyStats.get(dateKey);
          dayStats.refunds.total += amount;
          dayStats.refunds.count++;
          dayStats.refunds.reasons[reason] = (dayStats.refunds.reasons[reason] || 0) + 1;
          dailyStats.set(dateKey, dayStats);
        }
      }

      // Process canceled orders with more detail
      for (const canceled of transformedCanceled) {
        const props = canceled.event_properties || {};
        const amount = Number(props.TotalAmount || 0);
        const reason = props.CancelReason || props.OrderMessage || 'No reason provided';
        const datetime = this.timeManager.toDateTime(canceled.attributes?.datetime);
        const orderId = props.OrderId || props.FromOrder;
        
        stats.canceledOrders = stats.canceledOrders || { total: 0, count: 0, reasons: {}, items: [], averageAmount: 0 };
        stats.canceledOrders.total += amount;
        stats.canceledOrders.count++;
        stats.canceledOrders.reasons[reason] = (stats.canceledOrders.reasons[reason] || 0) + 1;
        stats.canceledOrders.items.push({
          orderId,
          amount,
          reason,
          datetime: datetime?.toISO()
        });
        stats.canceledOrders.averageAmount = stats.canceledOrders.total / stats.canceledOrders.count;

        // Track daily cancellations
        if (datetime) {
          const dayStart = this.timeManager.getDayStart(datetime);
          const dateKey = dayStart.toFormat('yyyy-MM-dd');
          // Initialize stats for this date if it doesn't exist
          if (!dailyStats.has(dateKey)) {
            dailyStats.set(dateKey, {
              date: datetime.toISO(), // ISO format for chart library
              timestamp: dateKey,
              revenue: 0,
              orders: 0,
              itemCount: 0,
              averageOrderValue: 0,
              averageItemsPerOrder: 0,
              hourlyOrders: Array(24).fill(0),
              refunds: { total: 0, count: 0, reasons: {}, items: [] },
              canceledOrders: { total: 0, count: 0, reasons: {}, items: [] },
              orderTypes: {
                preOrders: { count: 0, percentage: 0, value: 0, items: [], averageValue: 0 },
                localPickup: { count: 0, percentage: 0, value: 0, items: [], averageValue: 0 },
                heldItems: { count: 0, percentage: 0, value: 0, items: [], averageValue: 0 },
                digital: { count: 0, percentage: 0, value: 0, items: [], averageValue: 0 },
                giftCard: { count: 0, percentage: 0, value: 0, items: [], averageValue: 0 }
              },
              orderValueRange: {
                largest: 0,
                smallest: 0,
                largestOrderId: null,
                smallestOrderId: null,
                distribution: {
                  under25: { count: 0, total: 0 },
                  under50: { count: 0, total: 0 },
                  under100: { count: 0, total: 0 },
                  under200: { count: 0, total: 0 },
                  over200: { count: 0, total: 0 }
                }
              }
            });
          }
          const dayStats = dailyStats.get(dateKey);
          dayStats.canceledOrders.total += amount;
          dayStats.canceledOrders.count++;
          dayStats.canceledOrders.reasons[reason] = (dayStats.canceledOrders.reasons[reason] || 0) + 1;
          dailyStats.set(dateKey, dayStats);
        }
      }

      // Set peak hour stats
      if (maxHourCount > 0) {
        stats.peakOrderHour = {
          hour: peakHour,
          count: maxHourCount,
          displayHour: this._formatHour(peakHour),
          hourData: stats.hourlyOrders.map((count, hour) => ({
            hour,
            displayHour: this._formatHour(hour),
            count,
            percentage: (count / stats.orderCount) * 100
          }))
        };
      }

      // Set best day stats
      if (bestDay.date) {
        stats.bestRevenueDay = {
          ...bestDay,
          dialogType: 'revenue', // Add dialog type for frontend
          dialogTitle: 'Revenue Details' // Add dialog title for frontend
        };
      }

      // Process products with status tracking
      stats.products.list = stats.products.list.map(product => ({
        ...product,
        averageOrderValue: product.totalRevenue / product.orderCount,
        orders: Array.from(product.orders)
      }));
      stats.products.list.sort((a, b) => b.totalRevenue - a.totalRevenue);
      stats.products.total = stats.products.list.length;

      // Format brands with more detail and period-specific data
      stats.brands.list = Array.from(brandMap.values())
        .map(brand => {
          const brandStats = {
            name: brand.name,
            count: brand.quantity,
            revenue: brand.revenue,
            productCount: brand.products.size,
            percentage: (brand.revenue / stats.revenue) * 100, // Calculate percentage based on period revenue
            averageOrderValue: brand.revenue / brand.quantity,
            products: Array.from(brand.products)
          };
          // Add display formatting for charts
          brandStats.tooltipLabel = `${brand.name}\nRevenue: $${brand.revenue.toFixed(2)}\nItems: ${brand.quantity}`;
          return brandStats;
        })
        .sort((a, b) => b.revenue - a.revenue);

      stats.brands.topBrands = stats.brands.list.slice(0, 10);
      stats.brands.totalRevenue = stats.revenue; // Use period revenue
      stats.brands.averageOrderValue = stats.revenue / stats.itemCount;
      stats.brands.total = stats.brands.list.length;

      // Format categories with more detail and period-specific data
      stats.categories.list = Array.from(categoryMap.values())
        .map(category => {
          const categoryStats = {
            name: category.name,
            count: category.quantity,
            revenue: category.revenue,
            productCount: category.products.size,
            percentage: (category.revenue / stats.revenue) * 100, // Calculate percentage based on period revenue
            averageOrderValue: category.revenue / category.quantity,
            products: Array.from(category.products)
          };
          // Add display formatting for charts
          categoryStats.tooltipLabel = `${category.name}\nRevenue: $${category.revenue.toFixed(2)}\nItems: ${category.quantity}`;
          return categoryStats;
        })
        .sort((a, b) => b.revenue - a.revenue);

      stats.categories.topCategories = stats.categories.list.slice(0, 10);
      stats.categories.totalRevenue = stats.revenue; // Use period revenue
      stats.categories.averageOrderValue = stats.revenue / stats.itemCount;
      stats.categories.total = stats.categories.list.length;

      // Remove pie chart labels and create a key
      stats.brands.key = stats.brands.list.map(brand => ({
        name: brand.name,
        color: this._getColorForBrand(brand.name)
      }));
      stats.categories.key = stats.categories.list.map(category => ({
        name: category.name,
        color: this._getColorForCategory(category.name)
      }));

      // Set peak hour stats with proper formatting
      if (maxHourCount > 0) {
        stats.peakOrderHour = {
          hour: peakHour,
          count: maxHourCount,
          displayHour: this._formatHour(peakHour),
          hourData: stats.hourlyOrders.map((count, hour) => ({
            hour,
            displayHour: this._formatHour(hour),
            count,
            percentage: (count / stats.orderCount) * 100
          }))
        };
      }

      // Set best day stats with link to revenue dialog
      if (bestDay.date) {
        stats.bestRevenueDay = {
          ...bestDay,
          dialogType: 'revenue', // Add dialog type for frontend
          dialogTitle: 'Revenue Details' // Add dialog title for frontend
        };
      }

      // Add daily stats for order types
      const orderTypeStats = {
        preOrders: { dailyData: [] },
        localPickup: { dailyData: [] },
        heldItems: { dailyData: [] }
      };

      // Process daily stats for order types
      stats.dailyData.forEach(day => {
        // Pre-orders daily data
        orderTypeStats.preOrders.dailyData.push({
          date: day.date,
          count: day.orderTypes.preOrders.count,
          value: day.orderTypes.preOrders.value,
          percentage: day.orderTypes.preOrders.percentage,
          totalOrders: day.orders
        });

        // Local pickup daily data
        orderTypeStats.localPickup.dailyData.push({
          date: day.date,
          count: day.orderTypes.localPickup.count,
          value: day.orderTypes.localPickup.value,
          percentage: day.orderTypes.localPickup.percentage,
          totalOrders: day.orders
        });

        // Held items daily data
        orderTypeStats.heldItems.dailyData.push({
          date: day.date,
          count: day.orderTypes.heldItems.count,
          value: day.orderTypes.heldItems.value,
          percentage: day.orderTypes.heldItems.percentage,
          totalOrders: day.orders
        });
      });

      // Add order type stats to main stats object
      stats.orderTypes.preOrders.dailyData = orderTypeStats.preOrders.dailyData;
      stats.orderTypes.localPickup.dailyData = orderTypeStats.localPickup.dailyData;
      stats.orderTypes.heldItems.dailyData = orderTypeStats.heldItems.dailyData;

      // Convert daily stats to array and sort
      stats.dailyData = Array.from(dailyStats.values())
        .sort((a, b) => a.date.localeCompare(b.date))
        .map(day => ({
          date: day.date,
          timestamp: day.timestamp,
          revenue: Number(day.revenue || 0),
          orders: Number(day.orders || 0),
          itemCount: Number(day.itemCount || 0),
          averageOrderValue: Number(day.averageOrderValue || 0),
          averageItemsPerOrder: Number(day.averageItemsPerOrder || 0),
          hourlyOrders: day.hourlyOrders || Array(24).fill(0),
          refunds: {
            total: Number(day.refunds?.total || 0),
            count: Number(day.refunds?.count || 0),
            reasons: day.refunds?.reasons || {}
          },
          canceledOrders: {
            total: Number(day.canceledOrders?.total || 0),
            count: Number(day.canceledOrders?.count || 0),
            reasons: day.canceledOrders?.reasons || {}
          },
          orderTypes: {
            preOrders: { 
              count: Number(day.orderTypes?.preOrders?.count || 0),
              value: Number(day.orderTypes?.preOrders?.value || 0),
              percentage: Number(day.orderTypes?.preOrders?.percentage || 0),
              averageValue: Number(day.orderTypes?.preOrders?.averageValue || 0)
            },
            localPickup: {
              count: Number(day.orderTypes?.localPickup?.count || 0),
              value: Number(day.orderTypes?.localPickup?.value || 0),
              percentage: Number(day.orderTypes?.localPickup?.percentage || 0),
              averageValue: Number(day.orderTypes?.localPickup?.averageValue || 0)
            },
            heldItems: {
              count: Number(day.orderTypes?.heldItems?.count || 0),
              value: Number(day.orderTypes?.heldItems?.value || 0),
              percentage: Number(day.orderTypes?.heldItems?.percentage || 0),
              averageValue: Number(day.orderTypes?.heldItems?.averageValue || 0)
            },
            digital: {
              count: Number(day.orderTypes?.digital?.count || 0),
              value: Number(day.orderTypes?.digital?.value || 0),
              percentage: Number(day.orderTypes?.digital?.percentage || 0),
              averageValue: Number(day.orderTypes?.digital?.averageValue || 0)
            },
            giftCard: {
              count: Number(day.orderTypes?.giftCard?.count || 0),
              value: Number(day.orderTypes?.giftCard?.value || 0),
              percentage: Number(day.orderTypes?.giftCard?.percentage || 0),
              averageValue: Number(day.orderTypes?.giftCard?.averageValue || 0)
            }
          }
        }));

      // Set totals
      stats.brands.total = stats.brands.list.length;
      stats.categories.total = stats.categories.list.length;

      return stats;
    } catch (error) {
      console.error('[EventsService] Error calculating period stats:', error);
      throw error;
    }
  }

  _getEmptyStats() {
    return {
      orderCount: 0,
      revenue: 0,
      averageOrderValue: 0,
      itemCount: 0,
      prevPeriodRevenue: 0,
      projectedRevenue: 0,
      periodProgress: 0,
      dailyData: [],
      products: {
        list: [],
        total: 0,
        status: {
          backordered: 0,
          inStock: 0,
          outOfStock: 0,
          preorder: 0
        }
      },
      shipping: {
        shippedCount: 0,
        locations: {
          total: 0,
          byState: [],
          byCountry: []
        },
        methods: {},
        methodStats: [],
        methodPercentages: {}
      },
      refunds: {
        total: 0,
        count: 0,
        reasons: {},
        items: [],
        averageAmount: 0
      },
      canceledOrders: {
        total: 0,
        count: 0,
        reasons: {},
        items: [],
        averageAmount: 0
      },
      orderTypes: {
        preOrders: { count: 0, percentage: 0, value: 0, items: [], averageValue: 0 },
        localPickup: { count: 0, percentage: 0, value: 0, items: [], averageValue: 0 },
        heldItems: { count: 0, percentage: 0, value: 0, items: [], averageValue: 0 },
        digital: { count: 0, percentage: 0, value: 0, items: [], averageValue: 0 },
        giftCard: { count: 0, percentage: 0, value: 0, items: [], averageValue: 0 }
      },
      brands: {
        total: 0,
        list: [],
        topBrands: [],
        totalRevenue: 0,
        averageOrderValue: 0
      },
      categories: {
        total: 0,
        list: [],
        topCategories: [],
        totalRevenue: 0,
        averageOrderValue: 0
      },
      hourlyOrders: Array(24).fill(0),
      peakOrderHour: null,
      bestRevenueDay: null,
      orderValueRange: {
        largest: 0,
        smallest: 0,
        largestOrderId: null,
        smallestOrderId: null,
        distribution: {
          under25: { count: 0, total: 0 },
          under50: { count: 0, total: 0 },
          under100: { count: 0, total: 0 },
          under200: { count: 0, total: 0 },
          over200: { count: 0, total: 0 }
        }
      }
    };
  }

  async getMultiMetricEvents(params = {}) {
    try {
      const { timeRange, startDate, endDate, metricIds } = params;
      const metrics = metricIds || Object.values(METRIC_IDS);

      // Get period dates using TimeManager to respect 1 AM day start
      let periodStart, periodEnd;
      if (startDate && endDate) {
        periodStart = this.timeManager.getDayStart(this.timeManager.toDateTime(startDate));
        periodEnd = this.timeManager.getDayEnd(this.timeManager.toDateTime(endDate));
      } else if (timeRange) {
        const range = this.timeManager.getDateRange(timeRange);
        periodStart = range.start;
        periodEnd = range.end;
      }

      // Try to get from cache first
      const cacheKey = this.redisService._getCacheKey('events', params);
      let cachedData = null;
      try {
        cachedData = await this.redisService.get(`${cacheKey}:feed`);
        if (cachedData) {
          return cachedData;
        }
      } catch (cacheError) {
        console.warn('[EventsService] Cache error:', cacheError);
      }

      // Fetch events for all specified metrics
      const eventPromises = metrics.map(metricId => 
        this.getEvents({
          startDate: periodStart.toISO(),
          endDate: periodEnd.toISO(),
          timeRange,
          metricId,
          sort: '-datetime'
        })
      );

      const results = await Promise.all(eventPromises);
      
      // Transform and flatten the events into a single array
      const allEvents = [];
      results.forEach((result) => {
        if (result && Array.isArray(result.data)) {
          allEvents.push(...result.data);
        }
      });

      // Sort all events by datetime in descending order
      allEvents.sort((a, b) => {
        const dateA = new Date(a.attributes?.datetime || 0);
        const dateB = new Date(b.attributes?.datetime || 0);
        return dateB - dateA;
      });

      const result = {
        data: allEvents,
        meta: {
          total_count: allEvents.length
        }
      };

      // Cache the result
      try {
        const ttl = this.redisService._getTTL(timeRange);
        await this.redisService.set(`${cacheKey}:feed`, result, ttl);
      } catch (cacheError) {
        console.warn('[EventsService] Cache set error:', cacheError);
      }

      return result;
    } catch (error) {
      console.error('[EventsService] Error in batch metrics:', error);
      throw error;
    }
  }

  _transformEvents(events) {
    if (!Array.isArray(events)) {
      console.warn('[EventsService] Events is not an array:', events);
      return [];
    }

    return events.map(event => {
      try {
        // Extract metric ID from all possible locations
        const metricId = event.relationships?.metric?.data?.id || 
                        event.attributes?.metric?.id || 
                        event.attributes?.metric_id;

        // Extract properties from all possible locations
        const rawProps = event.attributes?.event_properties || {};
        
        // Only log for shipped orders and only show relevant fields
        if (event.relationships?.metric?.data?.id === METRIC_IDS.SHIPPED_ORDER) {
          console.log('[EventsService] Shipped Order:', {
            orderId: rawProps.OrderId,
            shippedBy: rawProps.ShippedBy,
            datetime: event.attributes?.datetime
          });
        }
        
        // Normalize shipping data
        const shippingData = {
          ShippingName: rawProps.ShippingName,
          ShippingStreet1: rawProps.ShippingStreet1,
          ShippingStreet2: rawProps.ShippingStreet2,
          ShippingCity: rawProps.ShippingCity,
          ShippingState: rawProps.ShippingState,
          ShippingZip: rawProps.ShippingZip,
          ShippingCountry: rawProps.ShippingCountry,
          ShipMethod: rawProps.ShipMethod,
          TrackingNumber: rawProps.TrackingNumber,
          ShippedBy: rawProps.ShippedBy
        };

        // Normalize payment data
        const paymentData = {
          method: rawProps.PaymentMethod,
          name: rawProps.PaymentName,
          amount: Number(rawProps.PaymentAmount || 0)
        };

        // Normalize order flags
        const orderFlags = {
          type: rawProps.OrderType || 'standard',
          hasPreorder: Boolean(rawProps.HasPreorder),
          localPickup: Boolean(rawProps.LocalPickup),
          isOnHold: Boolean(rawProps.IsOnHold),
          hasDigiItem: Boolean(rawProps.HasDigiItem),
          hasNotions: Boolean(rawProps.HasNotions),
          hasDigitalGC: Boolean(rawProps.HasDigitalGC),
          stillOwes: Boolean(rawProps.StillOwes)
        };

        // Normalize refund/cancel data
        const refundData = {
          reason: rawProps.CancelReason,
          message: rawProps.CancelMessage,
          orderMessage: rawProps.OrderMessage
        };

        // Transform items
        const items = this._transformItems(rawProps.Items || []);

        // Calculate totals
        const totalAmount = Number(rawProps.TotalAmount || rawProps.PaymentAmount || rawProps.value || 0);
        const itemCount = items.reduce((sum, item) => sum + Number(item.Quantity || item.QuantityOrdered || 1), 0);

        const transformed = {
          id: event.id,
          type: event.type,
          metric_id: metricId,
          attributes: {
            ...event.attributes,
            datetime: event.attributes?.datetime,
            value: event.attributes?.value,
            metric: {
              ...event.attributes?.metric,
              id: metricId
            }
          },
          relationships: event.relationships,
          event_properties: {
            ...rawProps,  // Include all original properties
            Items: items,  // Override with transformed items
            TotalAmount: totalAmount,
            ItemCount: itemCount
          }
        };

        return transformed;
      } catch (error) {
        console.error('[EventsService] Error transforming event:', error, event);
        // Return a minimal valid event structure
        return {
          id: event.id || 'unknown',
          type: event.type || 'unknown',
          metric_id: event.relationships?.metric?.data?.id || 'unknown',
          attributes: event.attributes || {},
          event_properties: {}
        };
      }
    }).filter(Boolean); // Remove any null/undefined events
  }

  _transformItems(items) {
    if (!Array.isArray(items)) {
      console.warn('[EventsService] Items is not an array:', items);
      return [];
    }

    return items.map(item => {
      try {
        const quantity = Number(item.Quantity || item.QuantityOrdered || item.quantity || item.quantity_ordered || 1);
        const price = Number(item.ItemPrice || item.item_price || item.price || 0);
        const rowTotal = Number(item.RowTotal || item.row_total || (price * quantity) || 0);

        const transformed = {
          // Basic item information
          ProductID: item.ProductID || item.product_id || item.id,
          ProductName: item.ProductName || item.product_name || item.name,
          SKU: item.SKU || item.sku,
          Brand: item.Brand || item.brand,
          Categories: Array.isArray(item.Categories) ? item.Categories : 
                     Array.isArray(item.categories) ? item.categories : [],

          // Pricing
          ItemPrice: price,
          RowTotal: rowTotal,

          // Quantities
          Quantity: quantity,
          QuantityOrdered: quantity,
          QuantitySent: Number(item.QuantitySent || item.quantity_sent || 0),
          QuantityBackordered: Number(item.QuantityBackordered || item.quantity_backordered || 0),

          // Status and images
          ItemStatus: item.ItemStatus || item.item_status || item.status || 'In Stock',
          ImgThumb: item.ImgThumb || item.img_thumb || item.thumbnail,

          // Additional properties
          IsPreorder: Boolean(item.IsPreorder || item.is_preorder || item.preorder),
          IsDigital: Boolean(item.IsDigital || item.is_digital || item.digital),
          IsGiftCard: Boolean(item.IsGiftCard || item.is_gift_card || item.gift_card),

          // Original properties (for backward compatibility)
          ...item
        };

        return transformed;
      } catch (error) {
        console.error('[EventsService] Error transforming item:', error, item);
        // Return a minimal valid item structure
        return {
          ProductID: item.ProductID || item.product_id || 'unknown',
          ProductName: item.ProductName || item.product_name || 'Unknown Product',
          Quantity: 1,
          ItemPrice: 0,
          RowTotal: 0
        };
      }
    });
  }

  _formatHour(hour) {
    if (hour === 0) return "12:00 AM";
    if (hour === 12) return "12:00 PM";
    if (hour > 12) return `${hour - 12}:00 PM`;
    return `${hour}:00 AM`;
  }

  async calculateDetailedStats(params = {}) {
    try {
      const { metric, daily = false } = params;
      console.log('[EventsService] Request params:', params);

      // Get period dates using TimeManager to respect 1 AM day start
      let periodStart, periodEnd, prevPeriodStart, prevPeriodEnd;
      if (params.startDate && params.endDate) {
        periodStart = this.timeManager.getDayStart(this.timeManager.toDateTime(params.startDate));
        periodEnd = this.timeManager.getDayEnd(this.timeManager.toDateTime(params.endDate));
        const duration = periodEnd.diff(periodStart);
        prevPeriodStart = this.timeManager.getDayStart(periodStart.minus(duration));
        prevPeriodEnd = this.timeManager.getDayEnd(periodStart.minus({ milliseconds: 1 }));
      } else if (params.timeRange) {
        const range = this.timeManager.getDateRange(params.timeRange);
        periodStart = range.start;
        periodEnd = range.end;
        const prevRange = this.timeManager.getPreviousPeriod(params.timeRange);
        prevPeriodStart = prevRange.start;
        prevPeriodEnd = prevRange.end;
      }

      // For order range, we need to process all orders with their value distribution
      if (metric === 'order_range') {
        const [currentEvents] = await Promise.all([
          this.getEvents({
            ...params,
            startDate: periodStart.toISO(),
            endDate: periodEnd.toISO(),
            metricId: METRIC_IDS.PLACED_ORDER
          })
        ]);

        // Transform events
        const transformedEvents = this._transformEvents(currentEvents.data || []);
        console.log(`[EventsService] Processing ${transformedEvents.length} orders for order range`);

        // Initialize daily stats map with all dates in range
        const dailyStats = new Map();
        let currentDate = periodStart;
        while (currentDate <= periodEnd) {
          const dateKey = currentDate.toFormat('yyyy-MM-dd');
          dailyStats.set(dateKey, {
            date: currentDate.toISO(),
            timestamp: dateKey,
            orders: 0,
            averageOrderValue: 0,
            orderValueRange: {
              largest: 0,
              smallest: 0,
              largestOrderId: null,
              smallestOrderId: null
            },
            orderValueDistribution: [
              { min: 0, max: 25, count: 0, total: 0 },
              { min: 25, max: 50, count: 0, total: 0 },
              { min: 50, max: 100, count: 0, total: 0 },
              { min: 100, max: 200, count: 0, total: 0 },
              { min: 200, max: 'Infinity', count: 0, total: 0 }
            ]
          });
          currentDate = currentDate.plus({ days: 1 });
        }

        // Process events
        for (const event of transformedEvents) {
          const datetime = this.timeManager.toDateTime(event.attributes?.datetime);
          if (!datetime) continue;

          const props = event.event_properties || {};
          const totalAmount = Number(props.TotalAmount || 0);
          const orderId = props.OrderId;

          const dayStart = this.timeManager.getDayStart(datetime);
          const dateKey = dayStart.toFormat('yyyy-MM-dd');
          
          if (dailyStats.has(dateKey)) {
            const dayStats = dailyStats.get(dateKey);
            dayStats.orders++;

            // Update order value range
            if (totalAmount > dayStats.orderValueRange.largest) {
              dayStats.orderValueRange.largest = totalAmount;
              dayStats.orderValueRange.largestOrderId = orderId;
            }
            if (totalAmount > 0 && (dayStats.orderValueRange.smallest === 0 || totalAmount < dayStats.orderValueRange.smallest)) {
              dayStats.orderValueRange.smallest = totalAmount;
              dayStats.orderValueRange.smallestOrderId = orderId;
            }

            // Update distribution
            if (totalAmount < 25) {
              dayStats.orderValueDistribution[0].count++;
              dayStats.orderValueDistribution[0].total += totalAmount;
            } else if (totalAmount < 50) {
              dayStats.orderValueDistribution[1].count++;
              dayStats.orderValueDistribution[1].total += totalAmount;
            } else if (totalAmount < 100) {
              dayStats.orderValueDistribution[2].count++;
              dayStats.orderValueDistribution[2].total += totalAmount;
            } else if (totalAmount < 200) {
              dayStats.orderValueDistribution[3].count++;
              dayStats.orderValueDistribution[3].total += totalAmount;
            } else {
              dayStats.orderValueDistribution[4].count++;
              dayStats.orderValueDistribution[4].total += totalAmount;
            }

            dayStats.averageOrderValue = dayStats.orderValueDistribution.reduce((sum, range) => sum + range.total, 0) / dayStats.orders;
            dailyStats.set(dateKey, dayStats);
          }
        }

        // Convert to array and sort by date
        const stats = Array.from(dailyStats.values())
          .sort((a, b) => a.date.localeCompare(b.date));

        return stats;
      }

      // For refunds and cancellations, we need to fetch those specific events
      if (metric === 'refunds' || metric === 'cancellations') {
        const [currentEvents] = await Promise.all([
          this.getEvents({
            ...params,
            startDate: periodStart.toISO(),
            endDate: periodEnd.toISO(),
            metricId: metric === 'refunds' ? METRIC_IDS.PAYMENT_REFUNDED : METRIC_IDS.CANCELED_ORDER
          })
        ]);

        // Transform events
        const transformedEvents = this._transformEvents(currentEvents.data || []);
        console.log(`[EventsService] Processing ${transformedEvents.length} ${metric}`);

        // Initialize daily stats map with all dates in range using TimeManager's day start
        const dailyStats = new Map();
        let currentDate = periodStart;
        while (currentDate <= periodEnd) {
          const dateKey = currentDate.toFormat('yyyy-MM-dd');
          dailyStats.set(dateKey, {
            date: currentDate.toISO(),
            timestamp: dateKey,
            total: 0,
            count: 0,
            reasons: {},
            items: []
          });
          currentDate = this.timeManager.getDayStart(currentDate.plus({ days: 1 }));
        }

        // Aggregate all reasons and items for the entire period
        const periodStats = {
          total: 0,
          count: 0,
          reasons: {},
          items: []
        };

        // Process current period events
        for (const event of transformedEvents) {
          const datetime = this.timeManager.toDateTime(event.attributes?.datetime);
          if (!datetime) continue;

          const props = event.event_properties || {};
          const amount = Number(metric === 'refunds' ? props.PaymentAmount : props.TotalAmount || 0);
          const reason = props.CancelReason || props.OrderMessage || 'No reason provided';
          const orderId = props.OrderId || props.FromOrder;

          const item = {
            orderId,
            amount,
            reason,
            datetime: datetime.toISO()
          };

          // Always update period totals for events within the period
          periodStats.total += amount;
          periodStats.count++;
          periodStats.reasons[reason] = (periodStats.reasons[reason] || 0) + 1;
          periodStats.items.push(item);

          // Get the day start for this event's time
          const dayStart = this.timeManager.getDayStart(datetime);
          const dateKey = dayStart.toFormat('yyyy-MM-dd');

          // Update daily stats if we have this day in our map
          if (dailyStats.has(dateKey)) {
            const dayStats = dailyStats.get(dateKey);
            dayStats.total += amount;
            dayStats.count++;
            dayStats.reasons[reason] = (dayStats.reasons[reason] || 0) + 1;
            dayStats.items.push(item);
            dailyStats.set(dateKey, dayStats);
          }
        }

        console.log(`[EventsService] Period stats for ${metric}:`, {
          total: periodStats.total,
          count: periodStats.count,
          reasonCount: Object.keys(periodStats.reasons).length,
          itemCount: periodStats.items.length
        });

        // Convert to array and sort by date
        const stats = Array.from(dailyStats.values())
          .sort((a, b) => a.date.localeCompare(b.date))
          .map(day => ({
            ...day,
            [metric === 'refunds' ? 'refunds' : 'canceledOrders']: {
              total: day.total,
              count: day.count,
              reasons: periodStats.reasons, // Use period-wide reasons for each day
              items: day.items,
              periodTotal: periodStats.total,
              periodCount: periodStats.count,
              periodReasons: periodStats.reasons,
              periodItems: periodStats.items
            }
          }));

        return stats;
      }

      // For other metrics, continue with existing logic
      const [currentResponse, prevResponse] = await Promise.all([
        this.getEvents({
          ...params,
          startDate: periodStart.toISO(),
          endDate: periodEnd.toISO(),
          metricId: METRIC_IDS.PLACED_ORDER
        }),
        this.getEvents({
          ..._.omit(params, ['timeRange', 'startDate', 'endDate']),
          startDate: prevPeriodStart.toISO(),
          endDate: prevPeriodEnd.toISO(),
          metricId: METRIC_IDS.PLACED_ORDER
        })
      ]);

      // Transform events
      const currentEvents = this._transformEvents(currentResponse.data || []);
      const prevEvents = this._transformEvents(prevResponse.data || []);

      // Initialize daily stats map with all dates in range using TimeManager's day start
      const dailyStats = new Map();
      let currentDate = periodStart;
      while (currentDate <= periodEnd) {
        const dateKey = currentDate.toFormat('yyyy-MM-dd');
        dailyStats.set(dateKey, {
          date: currentDate.toISO(),
          timestamp: dateKey,
          revenue: 0,
          orders: 0,
          itemCount: 0,
          count: 0,
          value: 0,
          percentage: 0,
          totalOrders: 0,
          prevValue: 0,
          prevOrders: 0,
          prevItemCount: 0,
          prevCount: 0,
          prevPercentage: 0,
          averageOrderValue: 0,
          averageItemsPerOrder: 0,
          prevAvgOrderValue: 0
        });
        currentDate = this.timeManager.getDayStart(currentDate.plus({ days: 1 }));
      }

      // First pass: Count total orders per day using TimeManager's day boundaries
      for (const event of currentEvents) {
        const datetime = this.timeManager.toDateTime(event.attributes?.datetime);
        if (!datetime) continue;

        // Get the day start for this event's time to ensure proper day assignment
        const dayStart = this.timeManager.getDayStart(datetime);
        const dateKey = dayStart.toFormat('yyyy-MM-dd');
        if (!dailyStats.has(dateKey)) continue;

        const dayStats = dailyStats.get(dateKey);
        dayStats.orders++;
        dailyStats.set(dateKey, dayStats);
      }

      // Second pass: Process filtered orders
      const filterEvents = (events) => {
        switch (metric) {
          case 'pre_orders':
            return events.filter(event => Boolean(event.event_properties?.HasPreorder));
          case 'local_pickup':
            return events.filter(event => Boolean(event.event_properties?.LocalPickup));
          case 'on_hold':
            return events.filter(event => Boolean(event.event_properties?.IsOnHold));
          default:
            return events;
        }
      };

      const filteredCurrentEvents = filterEvents(currentEvents);

      // Process current period filtered events using TimeManager's day boundaries
      for (const event of filteredCurrentEvents) {
        const datetime = this.timeManager.toDateTime(event.attributes?.datetime);
        if (!datetime) continue;

        // Get the day start for this event's time
        const dayStart = this.timeManager.getDayStart(datetime);
        const dateKey = dayStart.toFormat('yyyy-MM-dd');
        if (!dailyStats.has(dateKey)) continue;

        const dayStats = dailyStats.get(dateKey);
        const props = event.event_properties || {};
        const totalAmount = Number(props.TotalAmount || 0);
        const items = props.Items || [];

        dayStats.count++;
        dayStats.value += totalAmount;
        dayStats.revenue = dayStats.value;
        dayStats.itemCount += items.length;
        dayStats.percentage = (dayStats.count / dayStats.orders) * 100;
        dayStats.averageOrderValue = dayStats.value / dayStats.count;
        dayStats.averageItemsPerOrder = dayStats.itemCount / dayStats.count;

        dailyStats.set(dateKey, dayStats);
      }

      // Initialize and process previous period stats using TimeManager's day boundaries
      const prevDailyStats = new Map();
      let prevDate = prevPeriodStart;
      while (prevDate <= prevPeriodEnd) {
        const dateKey = prevDate.toFormat('yyyy-MM-dd');
        prevDailyStats.set(dateKey, {
          date: prevDate.toISO(),
          timestamp: dateKey,
          orders: 0,
          count: 0,
          value: 0,
          percentage: 0
        });
        prevDate = this.timeManager.getDayStart(prevDate.plus({ days: 1 }));
      }

      // First pass for previous period: Count total orders
      for (const event of prevEvents) {
        const datetime = this.timeManager.toDateTime(event.attributes?.datetime);
        if (!datetime) continue;

        const dayStart = this.timeManager.getDayStart(datetime);
        const dateKey = dayStart.toFormat('yyyy-MM-dd');
        if (!prevDailyStats.has(dateKey)) continue;

        const dayStats = prevDailyStats.get(dateKey);
        dayStats.orders++;
        prevDailyStats.set(dateKey, dayStats);
      }

      // Second pass for previous period: Process filtered orders
      const filteredPrevEvents = filterEvents(prevEvents);
      for (const event of filteredPrevEvents) {
        const datetime = this.timeManager.toDateTime(event.attributes?.datetime);
        if (!datetime) continue;

        const dayStart = this.timeManager.getDayStart(datetime);
        const dateKey = dayStart.toFormat('yyyy-MM-dd');
        if (!prevDailyStats.has(dateKey)) continue;

        const dayStats = prevDailyStats.get(dateKey);
        const props = event.event_properties || {};
        const totalAmount = Number(props.TotalAmount || 0);

        dayStats.count++;
        dayStats.value += totalAmount;
        dayStats.percentage = (dayStats.count / dayStats.orders) * 100;

        prevDailyStats.set(dateKey, dayStats);
      }

      // Map previous period data to current period days
      const prevPeriodDays = Array.from(prevDailyStats.values()).sort((a, b) => a.date.localeCompare(b.date));
      const currentPeriodDays = Array.from(dailyStats.values()).sort((a, b) => a.date.localeCompare(b.date));

      // Map the data using array indices
      for (let i = 0; i < currentPeriodDays.length && i < prevPeriodDays.length; i++) {
        const currentDayStats = currentPeriodDays[i];
        const prevDayStats = prevPeriodDays[i];
        
        if (prevDayStats && currentDayStats) {
          const dayStats = dailyStats.get(currentDayStats.timestamp);
          if (dayStats) {
            dayStats.prevValue = prevDayStats.value;
            dayStats.prevRevenue = prevDayStats.value;
            dayStats.prevCount = prevDayStats.count;
            dayStats.prevOrders = prevDayStats.orders;
            dayStats.prevPercentage = prevDayStats.percentage;
            dayStats.prevAvgOrderValue = prevDayStats.count > 0 ? prevDayStats.value / prevDayStats.count : 0;
            dailyStats.set(currentDayStats.timestamp, dayStats);
          }
        }
      }

      // Convert to array and sort by date
      const stats = Array.from(dailyStats.values())
        .sort((a, b) => a.date.localeCompare(b.date))
        .map(day => ({
          ...day,
          revenue: Number(day.revenue || day.value || 0),
          orders: Number(day.orders || 0),
          itemCount: Number(day.itemCount || 0),
          count: Number(day.count || 0),
          value: Number(day.value || 0),
          percentage: Number(day.percentage || 0),
          averageOrderValue: Number(day.averageOrderValue || 0),
          averageItemsPerOrder: Number(day.averageItemsPerOrder || 0),
          prevRevenue: Number(day.prevRevenue || day.prevValue || 0),
          prevValue: Number(day.prevValue || 0),
          prevCount: Number(day.prevCount || 0),
          prevOrders: Number(day.prevOrders || 0),
          prevPercentage: Number(day.prevPercentage || 0),
          prevAvgOrderValue: Number(day.prevAvgOrderValue || 0)
        }));

      return stats;
    } catch (error) {
      console.error('[EventsService] Error calculating detailed stats:', error);
      throw error;
    }
  }

  _getColorForBrand(brandName) {
    // Generate a consistent color based on the brand name
    let hash = 0;
    for (let i = 0; i < brandName.length; i++) {
      hash = brandName.charCodeAt(i) + ((hash << 5) - hash);
    }
    
    // Use HSL to ensure colors are visually distinct and pleasing
    const hue = Math.abs(hash % 360);
    return `hsl(${hue}, 70%, 50%)`;
  }

  _getColorForCategory(categoryName) {
    // Generate a consistent color based on the category name
    let hash = 0;
    for (let i = 0; i < categoryName.length; i++) {
      hash = categoryName.charCodeAt(i) + ((hash << 5) - hash);
    }
    
    // Use HSL with different saturation/lightness than brands
    const hue = Math.abs(hash % 360);
    return `hsl(${hue}, 60%, 60%)`;
  }

  async getBatchMetrics(params = {}) {
    try {
      const { timeRange, startDate, endDate, metrics = [] } = params;
      
      // Create a map of all metric requests
      const metricPromises = metrics.map(metric => {
        switch(metric) {
          case 'orders':
            return this.getEvents({
              ...params,
              metricId: METRIC_IDS.PLACED_ORDER
            });
          case 'revenue':
            return this.getEvents({
              ...params,
              metricId: METRIC_IDS.PLACED_ORDER,
              property: 'TotalAmount'
            });
          case 'refunds':
            return this.getEvents({
              ...params,
              metricId: METRIC_IDS.PAYMENT_REFUNDED
            });
          case 'cancellations':
            return this.getEvents({
              ...params,
              metricId: METRIC_IDS.CANCELED_ORDER
            });
          case 'shipping':
            return this.getEvents({
              ...params,
              metricId: METRIC_IDS.SHIPPED_ORDER
            });
          default:
            return Promise.resolve(null);
        }
      });

      // Execute all promises in parallel
      const results = await Promise.all(metricPromises);
      
      // Transform results into a keyed object
      const batchResults = {};
      metrics.forEach((metric, index) => {
        batchResults[metric] = results[index];
      });

      return batchResults;
    } catch (error) {
      console.error('[EventsService] Error in batch metrics:', error);
      throw error;
    }
  }

  async calculateSmartProjection(params = {}) {
    try {
      const { timeRange, startDate, endDate } = params;
      
      // Get current period dates
      let periodStart, periodEnd;
      if (startDate && endDate) {
        periodStart = this.timeManager.getDayStart(this.timeManager.toDateTime(startDate));
        periodEnd = this.timeManager.getDayEnd(this.timeManager.toDateTime(endDate));
      } else if (timeRange) {
        const range = this.timeManager.getDateRange(timeRange);
        periodStart = range.start;
        periodEnd = range.end;
      }

      // Get the same day of week from the last 4 weeks for pattern matching
      const historicalPeriods = [];
      let historicalStart = periodStart.minus({ weeks: 4 });
      
      for (let i = 0; i < 4; i++) {
        historicalPeriods.push({
          start: historicalStart.plus({ weeks: i }),
          end: historicalStart.plus({ weeks: i + 1 }).minus({ milliseconds: 1 })
        });
      }

      // Fetch current period data
      const currentEvents = await this.getEvents({
        startDate: periodStart.toISO(),
        endDate: periodEnd.toISO(),
        metricId: METRIC_IDS.PLACED_ORDER
      });

      // Fetch historical data for pattern matching
      const historicalPromises = historicalPeriods.map(period => 
        this.getEvents({
          startDate: period.start.toISO(),
          endDate: period.end.toISO(),
          metricId: METRIC_IDS.PLACED_ORDER
        })
      );

      const historicalResults = await Promise.all(historicalPromises);

      // Process current period data
      const currentData = this._transformEvents(currentEvents.data || []);
      const currentRevenue = currentData.reduce((sum, event) => {
        const props = event.event_properties || {};
        return sum + Number(props.TotalAmount || 0);
      }, 0);

      // Build hourly patterns from historical data
      const hourlyPatterns = Array(24).fill(0).map(() => ({
        count: 0,
        revenue: 0,
        percentage: 0
      }));

      let totalHistoricalRevenue = 0;
      let totalHistoricalOrders = 0;

      historicalResults.forEach(result => {
        const events = this._transformEvents(result.data || []);
        events.forEach(event => {
          const datetime = this.timeManager.toDateTime(event.attributes?.datetime);
          if (!datetime) return;

          const hour = datetime.hour;
          const props = event.event_properties || {};
          const amount = Number(props.TotalAmount || 0);

          hourlyPatterns[hour].count++;
          hourlyPatterns[hour].revenue += amount;
          totalHistoricalRevenue += amount;
          totalHistoricalOrders++;
        });
      });

      // Calculate percentages
      hourlyPatterns.forEach(pattern => {
        pattern.percentage = totalHistoricalRevenue > 0 ? 
          (pattern.revenue / totalHistoricalRevenue) * 100 : 0;
      });

      // Get current hour in the period's timezone
      const now = this.timeManager.getNow();
      const currentHour = now.hour;
      const currentMinute = now.minute;

      // Handle the 12-1 AM edge case
      const isInEdgeCase = currentHour < this.timeManager.dayStartHour;
      const adjustedCurrentHour = isInEdgeCase ? currentHour + 24 : currentHour;
      const adjustedDayStartHour = this.timeManager.dayStartHour;

      // Calculate how much of the current hour has passed (0-1)
      const hourProgress = currentMinute / 60;

      // Calculate how much of the expected daily revenue we've seen so far
      let expectedPercentageSeen = 0;
      let totalDayPercentage = 0;

      // First, calculate total percentage for a full day
      for (let i = 0; i < 24; i++) {
        totalDayPercentage += hourlyPatterns[i].percentage;
      }

      if (isInEdgeCase) {
        // If we're between 12-1 AM, we want to use almost the full day's percentage
        // since we're at the end of the previous day
        expectedPercentageSeen = totalDayPercentage;
        // Subtract the remaining portion of the current hour
        expectedPercentageSeen -= hourlyPatterns[currentHour].percentage * (1 - hourProgress);
      } else {
        // Normal case - add up percentages from day start to current hour
        for (let i = adjustedDayStartHour; i < adjustedCurrentHour; i++) {
          const hourIndex = i % 24;
          expectedPercentageSeen += hourlyPatterns[hourIndex].percentage;
        }
        // Add partial current hour
        expectedPercentageSeen += hourlyPatterns[currentHour].percentage * hourProgress;
      }

      // Calculate projection based on patterns
      let projectedRevenue = 0;
      if (expectedPercentageSeen > 0) {
        projectedRevenue = (currentRevenue / (expectedPercentageSeen / totalDayPercentage));
      }

      // Calculate confidence score (0-1) based on:
      // 1. How much historical data we have
      // 2. How consistent the patterns are
      // 3. How far through the period we are
      const patternConsistency = this._calculatePatternConsistency(hourlyPatterns);
      
      // Calculate period progress considering the 1 AM day start
      const totalDuration = periodEnd.diff(periodStart);
      const elapsedDuration = now.diff(periodStart);
      let periodProgress = Math.min(100, Math.max(0, (elapsedDuration.milliseconds / totalDuration.milliseconds) * 100));

      // Adjust period progress for the 12-1 AM edge case
      if (isInEdgeCase) {
        // If we're between 12-1 AM, we're actually at the end of the previous day
        periodProgress = Math.min(100, Math.max(0, ((24 - adjustedDayStartHour + currentHour) / 24) * 100));
      }

      const historicalDataAmount = Math.min(totalHistoricalOrders / 1000, 1);

      const confidence = (
        (patternConsistency * 0.4) + 
        (periodProgress / 100 * 0.4) + 
        (historicalDataAmount * 0.2)
      );

      // Return both the simple and pattern-based projections with metadata
      return {
        currentRevenue,
        projectedRevenue,
        confidence,
        metadata: {
          periodProgress,
          patternConsistency,
          historicalOrders: totalHistoricalOrders,
          hourlyPatterns,
          expectedPercentageSeen,
          totalDayPercentage,
          currentHour,
          currentMinute,
          isInEdgeCase,
          adjustedCurrentHour
        }
      };
    } catch (error) {
      console.error('[EventsService] Error calculating smart projection:', error);
      throw error;
    }
  }

  _calculatePatternConsistency(hourlyPatterns) {
    // Calculate the standard deviation of the percentage distribution
    const mean = hourlyPatterns.reduce((sum, pattern) => sum + pattern.percentage, 0) / 24;
    const variance = hourlyPatterns.reduce((sum, pattern) => {
      const diff = pattern.percentage - mean;
      return sum + (diff * diff);
    }, 0) / 24;
    const stdDev = Math.sqrt(variance);

    // Normalize to a 0-1 scale where lower standard deviation means higher consistency
    // Using a sigmoid function to normalize
    const normalizedConsistency = 1 / (1 + Math.exp(stdDev / 10));
    return normalizedConsistency;
  }
} 
