<?php

namespace Limb_Chatbot\Includes\Services\Chatbot_Analytics\Calculators;

/**
 * Class Period_Bucketing
 *
 * Utility class for determining the appropriate time period bucketing strategy
 * based on the time range and generating bucket labels.
 *
 * Bucketing Strategy:
 * - < 1 day: Hourly buckets (12 AM, 1 AM, 2 AM, ..., 11 PM)
 * - 1-7 days: Day of week (Mon, Tue, Wed, Thu, Fri, Sat, Sun)
 * - 8-30 days: Weekly buckets (Week 1, Week 2, Week 3, Week 4)
 * - 31-365 days: Monthly buckets (Jan, Feb, Mar, ..., Dec)
 * - > 365 days: Yearly buckets (Year 2024, Year 2025, etc.)
 *
 * @package Limb_Chatbot\Includes\Services\Chatbot_Analytics\Calculators
 * @since 1.0.0
 */
class Period_Bucketing {

	// Bucketing strategies
	const STRATEGY_HOURLY  = 'hourly';
	const STRATEGY_DAILY   = 'daily';
	const STRATEGY_WEEKLY  = 'weekly';
	const STRATEGY_MONTHLY = 'monthly';
	const STRATEGY_YEARLY  = 'yearly';

	/**
	 * Determine the bucketing strategy based on time range.
	 *
	 * @param int|null $starting_from Unix timestamp or null for 'all'
	 * @param int      $ending_at      Unix timestamp (default: current time)
	 *
	 * @return string The bucketing strategy constant
	 * @since 1.0.0
	 */
	public static function get_strategy( ?int $starting_from, int $ending_at = 0 ): string {
		if ( 0 === $ending_at ) {
			$ending_at = time();
		}

		// If no starting time ('all'), determine based on data span
		if ( null === $starting_from ) {
			// For 'all', use yearly to span potentially large date ranges
			return self::STRATEGY_YEARLY;
		}

		$diff_seconds = $ending_at - $starting_from;
		$diff_days    = $diff_seconds / ( 24 * 60 * 60 );

		if ( $diff_days < 1 ) {
			return self::STRATEGY_HOURLY;
		} elseif ( $diff_days <= 7 ) {
			return self::STRATEGY_DAILY;
		} elseif ( $diff_days <= 30 ) {
			return self::STRATEGY_WEEKLY;
		} elseif ( $diff_days <= 365 ) {
			return self::STRATEGY_MONTHLY;
		}

		return self::STRATEGY_YEARLY;
	}

	/**
	 * Get bucket boundaries for a given strategy.
	 *
	 * @param string   $strategy       The bucketing strategy
	 * @param int|null $starting_from  Unix timestamp in UTC+0 or null for 'all'
	 * @param int      $ending_at      Unix timestamp in UTC+0 (default: current time)
	 * @param float    $utc_offset     UTC offset in hours for timezone conversion (default: 0.0)
	 *
	 * @return array Array of bucket definitions with 'start', 'end', and 'label'
	 * @since 1.0.0
	 */
	public static function get_buckets( string $strategy, ?int $starting_from, int $ending_at = 0, float $utc_offset = 0.0 ): array {
		if ( 0 === $ending_at ) {
			$ending_at = time();
		}

		switch ( $strategy ) {
			case self::STRATEGY_HOURLY:
				return self::get_hourly_buckets( $starting_from ?? $ending_at, $ending_at, $utc_offset );
			case self::STRATEGY_DAILY:
				return self::get_daily_buckets( $starting_from ?? $ending_at, $ending_at, $utc_offset );
			case self::STRATEGY_WEEKLY:
				return self::get_weekly_buckets( $starting_from ?? $ending_at, $ending_at, $utc_offset );
			case self::STRATEGY_MONTHLY:
				return self::get_monthly_buckets( $starting_from ?? $ending_at, $ending_at, $utc_offset );
			case self::STRATEGY_YEARLY:
				// For yearly, pass the actual $starting_from (can be null for 'all time')
				return self::get_yearly_buckets( $starting_from, $ending_at, $utc_offset );
			default:
				return [];
		}
	}

	/**
	 * Get hourly buckets (24 hours from starting_from to ending_at).
	 *
	 * @param int   $starting_from Unix timestamp in UTC+0
	 * @param int   $ending_at     Unix timestamp in UTC+0
	 * @param float $utc_offset    UTC offset in hours for timezone conversion
	 *
	 * @return array Array of hourly buckets
	 * @since 1.0.0
	 */
	private static function get_hourly_buckets( int $starting_from, int $ending_at, float $utc_offset = 0.0 ): array {
		$buckets = [];

		// Convert starting_from to user's timezone to determine which hour it falls in
		$user_starting_from = $starting_from + (int) ( $utc_offset * 3600 );
		
		// Get the beginning of the hour in user's timezone
		$user_hour_start = strtotime( date( 'Y-m-d H:00:00', $user_starting_from ) );
		
		// Convert back to UTC+0 for the actual bucket start time
		$current = $user_hour_start - (int) ( $utc_offset * 3600 );
		
		// Ensure we don't start before the actual starting_from timestamp
		if ( $current < $starting_from ) {
			$current = $starting_from;
		}

		while ( $current <= $ending_at ) {
			$next_hour = $current + ( 60 * 60 );

			// Convert UTC+0 timestamp to user's timezone for label
			$user_timestamp = $current + (int) ( $utc_offset * 3600 );
			$label = gmdate( 'g A', $user_timestamp ); // e.g., "12 AM", "1 AM", etc.

			$buckets[] = [
				'start'  => $current,
				'end'    => $next_hour,
				'label'  => $label,
				'format' => self::STRATEGY_HOURLY,
			];

			$current = $next_hour;
		}

		return $buckets;
	}

	/**
	 * Get daily buckets by day of week.
	 *
	 * @param int   $starting_from Unix timestamp in UTC+0
	 * @param int   $ending_at     Unix timestamp in UTC+0
	 * @param float $utc_offset    UTC offset in hours for timezone conversion
	 *
	 * @return array Array of daily buckets
	 * @since 1.0.0
	 */
	private static function get_daily_buckets( int $starting_from, int $ending_at, float $utc_offset = 0.0 ): array {
		$buckets = [];

		// Convert starting_from to user's timezone to determine which day it falls in
		$user_starting_from = $starting_from + (int) ( $utc_offset * 3600 );
		
		// Get the beginning of the day in user's timezone
		$user_day_start = strtotime( 'today', $user_starting_from );
		
		// Convert back to UTC+0 for the actual bucket start time
		$current = $user_day_start - (int) ( $utc_offset * 3600 );
		
		// Ensure we don't start before the actual starting_from timestamp
		if ( $current < $starting_from ) {
			$current = $starting_from;
		}

		while ( $current <= $ending_at ) {
			$next_day = $current + ( 24 * 60 * 60 );

			// Convert UTC+0 timestamp to user's timezone for label
			$user_timestamp = $current + (int) ( $utc_offset * 3600 );
			$day_name = gmdate( 'D', $user_timestamp ); // Mon, Tue, Wed, etc.

			$buckets[] = [
				'start'  => $current,
				'end'    => $next_day,
				'label'  => $day_name,
				'format' => self::STRATEGY_DAILY,
			];

			$current = $next_day;
		}

		return $buckets;
	}

	/**
	 * Get weekly buckets.
	 *
	 * @param int   $starting_from Unix timestamp in UTC+0
	 * @param int   $ending_at     Unix timestamp in UTC+0
	 * @param float $utc_offset    UTC offset in hours for timezone conversion
	 *
	 * @return array Array of weekly buckets
	 * @since 1.0.0
	 */
	private static function get_weekly_buckets( int $starting_from, int $ending_at, float $utc_offset = 0.0 ): array {
		$buckets = [];
		$week_num = 1;

		// Convert starting_from to user's timezone to determine which week it falls in
		$user_starting_from = $starting_from + (int) ( $utc_offset * 3600 );
		
		// Get the beginning of the week (Monday) in user's timezone
		$user_week_start = strtotime( 'monday this week', $user_starting_from );
		
		// Convert back to UTC+0 for the actual bucket start time
		$current = $user_week_start - (int) ( $utc_offset * 3600 );
		
		// Ensure we don't start before the actual starting_from timestamp
		if ( $current < $starting_from ) {
			$current = $starting_from;
		}

		while ( $current <= $ending_at ) {
			$next_week = $current + ( 7 * 24 * 60 * 60 );

			$buckets[] = [
				'start'  => $current,
				'end'    => $next_week,
				'label'  => sprintf( _x( 'Week %d', 'week number', 'limb-chatbot' ), $week_num ),
				'format' => self::STRATEGY_WEEKLY,
			];

			$current  = $next_week;
			$week_num++;
		}

		return $buckets;
	}

	/**
	 * Get monthly buckets.
	 *
	 * @param int   $starting_from Unix timestamp in UTC+0
	 * @param int   $ending_at     Unix timestamp in UTC+0
	 * @param float $utc_offset    UTC offset in hours for timezone conversion
	 *
	 * @return array Array of monthly buckets
	 * @since 1.0.0
	 */
	private static function get_monthly_buckets( int $starting_from, int $ending_at, float $utc_offset = 0.0 ): array {
		$buckets = [];

		// Convert starting_from to user's timezone to determine which month it falls in
		$user_starting_from = $starting_from + (int) ( $utc_offset * 3600 );
		
		// Get the first day of the month in user's timezone
		$user_month_start = strtotime( 'first day of this month', $user_starting_from );
		
		// Convert back to UTC+0 for the actual bucket start time
		$current = $user_month_start - (int) ( $utc_offset * 3600 );
		
		// Ensure we don't start before the actual starting_from timestamp
		// If we had to adjust, recalculate the month label from the actual starting point
		if ( $current < $starting_from ) {
			$current = $starting_from;
		}

		while ( $current <= $ending_at ) {
			// Calculate next month in user's timezone
			$user_current = $current + (int) ( $utc_offset * 3600 );
			$user_next_month = strtotime( 'first day of next month', $user_current );
			$next_month = $user_next_month - (int) ( $utc_offset * 3600 );

			// Convert UTC+0 timestamp to user's timezone for label
			// Always use the actual $current timestamp (which may have been adjusted to starting_from)
			$user_timestamp = $current + (int) ( $utc_offset * 3600 );
			$month_label = gmdate( 'M', $user_timestamp ); // Jan, Feb, Mar, etc.

			$buckets[] = [
				'start'  => $current,
				'end'    => $next_month,
				'label'  => $month_label,
				'format' => self::STRATEGY_MONTHLY,
			];

			$current = $next_month;
		}

		return $buckets;
	}

	/**
	 * Get yearly buckets.
	 *
	 * @param int|null $starting_from Unix timestamp in UTC+0 or null for 'all'
	 * @param int      $ending_at     Unix timestamp in UTC+0
	 * @param float    $utc_offset    UTC offset in hours for timezone conversion
	 *
	 * @return array Array of yearly buckets
	 * @since 1.0.0
	 */
	private static function get_yearly_buckets( ?int $starting_from, int $ending_at, float $utc_offset = 0.0 ): array {
		$buckets = [];

		// For 'all time' requests (starting_from is null), start from 2025
		if ( null === $starting_from ) {
			$starting_from = strtotime( '2025-01-01' );
		}

		// Convert starting_from to user's timezone to determine which year it falls in
		$user_starting_from = $starting_from + (int) ( $utc_offset * 3600 );
		
		// Get the first day of the year in user's timezone
		$start_year = (int) gmdate( 'Y', $user_starting_from );
		$user_year_start = strtotime( $start_year . '-01-01' );
		
		// Convert back to UTC+0 for the actual bucket start time
		$current = $user_year_start - (int) ( $utc_offset * 3600 );
		
		// Ensure we don't start before the actual starting_from timestamp
		if ( $current < $starting_from ) {
			$current = $starting_from;
		}

		while ( $current <= $ending_at ) {
			// Calculate next year in user's timezone
			$user_current = $current + (int) ( $utc_offset * 3600 );
			$year = (int) gmdate( 'Y', $user_current );
			$user_next_year = strtotime( ( $year + 1 ) . '-01-01' );
			$next_year = $user_next_year - (int) ( $utc_offset * 3600 );

			$buckets[] = [
				'start'  => $current,
				'end'    => $next_year,
				'label'  => sprintf( _x( '%s', 'year', 'limb-chatbot' ), $year ),
				'format' => self::STRATEGY_YEARLY,
			];

			$current = $next_year;
		}

		return $buckets;
	}
}
