import { DateTime } from 'luxon';

export class TimeManager {
  constructor(dayStartHour = 1) {
    this.timezone = 'America/New_York';
    this.dayStartHour = dayStartHour; // Hour (0-23) when the business day starts
    this.weekStartDay = 7; // 7 = Sunday in Luxon
  }

  /**
   * Get the start of the current business day
   * If current time is before dayStartHour, return previous day at dayStartHour
   */
  getDayStart(dt = this.getNow()) {
    if (!dt.isValid) {
      console.error("[TimeManager] Invalid datetime provided to getDayStart");
      return this.getNow();
    }
    const dayStart = dt.set({ hour: this.dayStartHour, minute: 0, second: 0, millisecond: 0 });
    return dt.hour < this.dayStartHour ? dayStart.minus({ days: 1 }) : dayStart;
  }

  /**
   * Get the end of the current business day
   * End is defined as dayStartHour - 1 minute on the next day
   */
  getDayEnd(dt = this.getNow()) {
    if (!dt.isValid) {
      console.error("[TimeManager] Invalid datetime provided to getDayEnd");
      return this.getNow();
    }
    const nextDay = this.getDayStart(dt).plus({ days: 1 });
    return nextDay.minus({ minutes: 1 });
  }

  /**
   * Get the start of the week containing the given date
   * Aligns with custom day start time and starts on Sunday
   */
  getWeekStart(dt = this.getNow()) {
    if (!dt.isValid) {
      console.error("[TimeManager] Invalid datetime provided to getWeekStart");
      return this.getNow();
    }
    // Set to start of week (Sunday) and adjust hour
    const weekStart = dt.set({ weekday: this.weekStartDay }).startOf('day');
    // If the week start time would be after the given time, go back a week
    if (weekStart > dt) {
      return weekStart.minus({ weeks: 1 }).set({ hour: this.dayStartHour });
    }
    return weekStart.set({ hour: this.dayStartHour });
  }

  /**
   * Convert any date input to a Luxon DateTime in Eastern time
   */
  toDateTime(date) {
    if (!date) return null;

    if (date instanceof DateTime) {
      return date.setZone(this.timezone);
    }

    // If it's an ISO string or Date object, parse it
    const dt = DateTime.fromISO(date instanceof Date ? date.toISOString() : date);
    if (!dt.isValid) {
      console.error("[TimeManager] Invalid date input:", date);
      return null;
    }

    return dt.setZone(this.timezone);
  }

  /**
   * Format a date for API requests (UTC ISO string)
   */
  formatForAPI(date) {
    if (!date) return null;

    // Parse the input date
    const dt = this.toDateTime(date);
    if (!dt || !dt.isValid) {
      console.error("[TimeManager] Invalid date for API:", date);
      return null;
    }

    // Convert to UTC for API request
    const utc = dt.toUTC();

    console.log("[TimeManager] API date conversion:", {
      input: date,
      eastern: dt.toISO(),
      utc: utc.toISO(),
      offset: dt.offset
    });

    return utc.toISO();
  }

  /**
   * Format a date for display (in Eastern time)
   */
  formatForDisplay(date) {
    const dt = this.toDateTime(date);
    if (!dt || !dt.isValid) return '';
    return dt.toFormat('LLL d, yyyy h:mm a');
  }

  /**
   * Validate if a date range is valid
   */
  isValidDateRange(start, end) {
    const startDt = this.toDateTime(start);
    const endDt = this.toDateTime(end);
    return startDt && endDt && endDt > startDt;
  }

  /**
   * Get the current time in Eastern timezone
   */
  getNow() {
    return DateTime.now().setZone(this.timezone);
  }

  /**
   * Get a date range for the last N hours
   */
  getLastNHours(hours) {
    const now = this.getNow();
    return {
      start: now.minus({ hours }),
      end: now
    };
  }

  /**
   * Get a date range for the last N days
   * Aligns with custom day start time
   */
  getLastNDays(days) {
    const now = this.getNow();
    const dayStart = this.getDayStart(now);
    return {
      start: dayStart.minus({ days }),
      end: this.getDayEnd(now)
    };
  }

  /**
   * Get a date range for a specific time period
   * All ranges align with custom day start time
   */
  getDateRange(period) {
    const now = this.getNow();
    
    // Normalize period to handle both 'last' and 'previous' prefixes
    const normalizedPeriod = period.startsWith('previous') ? period.replace('previous', 'last') : period;
    
    switch (normalizedPeriod) {
      case 'custom': {
        // Custom ranges are handled separately via getCustomRange
        console.warn('[TimeManager] Custom ranges should use getCustomRange method');
        return null;
      }
      case 'today': {
        return {
          start: this.getDayStart(now),
          end: this.getDayEnd(now)
        };
      }
      case 'yesterday': {
        const yesterday = now.minus({ days: 1 });
        return {
          start: this.getDayStart(yesterday),
          end: this.getDayEnd(yesterday)
        };
      }
      case 'last7days': {
        // For last 7 days, we want to include today and the previous 6 days
        // So if today is 12/17, we want 12/11 1am to 12/17 12:59am
        const dayStart = this.getDayStart(now);
        return {
          start: dayStart.minus({ days: 6 }), // 6 days ago from start of today
          end: this.getDayEnd(now) // end of today
        };
      }
      case 'last30days': {
        // Include today and previous 29 days
        const dayStart = this.getDayStart(now);
        return {
          start: dayStart.minus({ days: 29 }), // 29 days ago from start of today
          end: this.getDayEnd(now) // end of today
        };
      }
      case 'last90days': {
        // Include today and previous 89 days
        const dayStart = this.getDayStart(now);
        return {
          start: dayStart.minus({ days: 89 }), // 89 days ago from start of today
          end: this.getDayEnd(now) // end of today
        };
      }
      case 'thisWeek': {
        // Get the start of the week (Sunday) with custom hour
        const weekStart = this.getWeekStart(now);
        return {
          start: weekStart,
          end: this.getDayEnd(now)
        };
      }
      case 'lastWeek': {
        const lastWeek = now.minus({ weeks: 1 });
        const weekStart = this.getWeekStart(lastWeek);
        const weekEnd = weekStart.plus({ days: 6 }); // 6 days after start = Saturday
        
        return {
          start: weekStart,
          end: this.getDayEnd(weekEnd)
        };
      }
      case 'thisMonth': {
        const dayStart = this.getDayStart(now);
        const monthStart = dayStart.startOf('month').set({ hour: this.dayStartHour });
        
        return {
          start: monthStart,
          end: this.getDayEnd(now)
        };
      }
      case 'lastMonth': {
        const lastMonth = now.minus({ months: 1 });
        const monthStart = lastMonth.startOf('month').set({ hour: this.dayStartHour });
        const monthEnd = monthStart.plus({ months: 1 }).minus({ days: 1 });
        
        return {
          start: monthStart,
          end: this.getDayEnd(monthEnd)
        };
      }
      default:
        console.warn(`[TimeManager] Unknown period: ${period}`);
        return null;
    }
  }

  /**
   * Format a duration in milliseconds to a human-readable string
   */
  formatDuration(ms) {
    return DateTime.fromMillis(ms).toFormat("hh'h' mm'm' ss's'");
  }

  /**
   * Get relative time string (e.g., "2 hours ago")
   */
  getRelativeTime(date) {
    const dt = this.toDateTime(date);
    if (!dt) return '';
    return dt.toRelative();
  }

  /**
   * Get a custom date range using exact dates and times provided
   * @param {string} startDate - ISO string or Date for range start
   * @param {string} endDate - ISO string or Date for range end
   * @returns {Object} Object with start and end DateTime objects
   */
  getCustomRange(startDate, endDate) {
    if (!startDate || !endDate) {
      console.error("[TimeManager] Custom range requires both start and end dates");
      return null;
    }

    const start = this.toDateTime(startDate);
    const end = this.toDateTime(endDate);

    if (!start || !end || !start.isValid || !end.isValid) {
      console.error("[TimeManager] Invalid dates provided for custom range");
      return null;
    }

    // Validate the range
    if (end < start) {
      console.error("[TimeManager] End date must be after start date");
      return null;
    }

    return {
      start,
      end
    };
  }

  /**
   * Get the previous period's date range based on the current period
   * @param {string} period - The current period
   * @param {DateTime} now - The current datetime (optional)
   * @returns {Object} Object with start and end DateTime objects
   */
  getPreviousPeriod(period, now = this.getNow()) {
    switch (period) {
      case 'today': {
        const yesterday = now.minus({ days: 1 });
        return {
          start: this.getDayStart(yesterday),
          end: this.getDayEnd(yesterday)
        };
      }
      case 'yesterday': {
        const twoDaysAgo = now.minus({ days: 2 });
        return {
          start: this.getDayStart(twoDaysAgo),
          end: this.getDayEnd(twoDaysAgo)
        };
      }
      case 'last7days':
      case 'previous7days': {
        const dayStart = this.getDayStart(now);
        const currentStart = dayStart.minus({ days: 6 });
        const prevEnd = currentStart.minus({ milliseconds: 1 });
        const prevStart = prevEnd.minus({ days: 6 });
        return {
          start: prevStart,
          end: prevEnd
        };
      }
      case 'last30days':
      case 'previous30days': {
        const dayStart = this.getDayStart(now);
        const currentStart = dayStart.minus({ days: 29 });
        const prevEnd = currentStart.minus({ milliseconds: 1 });
        const prevStart = prevEnd.minus({ days: 29 });
        return {
          start: prevStart,
          end: prevEnd
        };
      }
      case 'last90days':
      case 'previous90days': {
        const dayStart = this.getDayStart(now);
        const currentStart = dayStart.minus({ days: 89 });
        const prevEnd = currentStart.minus({ milliseconds: 1 });
        const prevStart = prevEnd.minus({ days: 89 });
        return {
          start: prevStart,
          end: prevEnd
        };
      }
      case 'thisWeek': {
        const weekStart = this.getWeekStart(now);
        const prevEnd = weekStart.minus({ milliseconds: 1 });
        const prevStart = this.getWeekStart(prevEnd);
        return {
          start: prevStart,
          end: prevEnd
        };
      }
      case 'lastWeek': {
        const lastWeekStart = this.getWeekStart(now.minus({ weeks: 1 }));
        const prevEnd = lastWeekStart.minus({ milliseconds: 1 });
        const prevStart = this.getWeekStart(prevEnd);
        return {
          start: prevStart,
          end: prevEnd
        };
      }
      case 'thisMonth': {
        const monthStart = now.startOf('month').set({ hour: this.dayStartHour });
        const prevEnd = monthStart.minus({ milliseconds: 1 });
        const prevStart = prevEnd.startOf('month').set({ hour: this.dayStartHour });
        return {
          start: prevStart,
          end: prevEnd
        };
      }
      case 'lastMonth': {
        const lastMonthStart = now.minus({ months: 1 }).startOf('month').set({ hour: this.dayStartHour });
        const prevEnd = lastMonthStart.minus({ milliseconds: 1 });
        const prevStart = prevEnd.startOf('month').set({ hour: this.dayStartHour });
        return {
          start: prevStart,
          end: prevEnd
        };
      }
      case 'twoDaysAgo': {
        const twoDaysAgo = now.minus({ days: 2 });
        return {
          start: this.getDayStart(twoDaysAgo),
          end: this.getDayEnd(twoDaysAgo)
        };
      }
      default:
        console.warn(`[TimeManager] No previous period defined for: ${period}`);
        return null;
    }
  }

  groupEventsByInterval(events, interval = 'day', property = null) {
    if (!events?.length) return [];

    const groupedData = new Map();
    const now = DateTime.now().setZone('America/New_York');

    for (const event of events) {
      const datetime = DateTime.fromISO(event.attributes.datetime);
      let groupKey;

      switch (interval) {
        case 'hour':
          groupKey = datetime.startOf('hour').toISO();
          break;
        case 'day':
          groupKey = datetime.startOf('day').toISO();
          break;
        case 'week':
          groupKey = datetime.startOf('week').toISO();
          break;
        case 'month':
          groupKey = datetime.startOf('month').toISO();
          break;
        default:
          groupKey = datetime.startOf('day').toISO();
      }

      const existingGroup = groupedData.get(groupKey) || {
        datetime: groupKey,
        count: 0,
        value: 0
      };

      existingGroup.count++;

      if (property) {
        // Extract property value from event
        const props = event.attributes?.event_properties || event.attributes?.properties || {};
        let value = 0;

        if (property === '$value') {
          // Special case for $value - use event value
          value = Number(event.attributes?.value || 0);
        } else {
          // Otherwise get from properties
          value = Number(props[property] || 0);
        }

        existingGroup.value = (existingGroup.value || 0) + value;
      }

      groupedData.set(groupKey, existingGroup);
    }

    // Convert to array and sort by datetime
    return Array.from(groupedData.values())
      .sort((a, b) => DateTime.fromISO(a.datetime) - DateTime.fromISO(b.datetime));
  }
} 