<?php

namespace Limb_Chatbot\Includes\Services\Chatbot_Analytics\Calculators;

use Limb_Chatbot\Includes\Data_Objects\Chat_Participant;
use Limb_Chatbot\Includes\Data_Objects\Chatbot;
use Limb_Chatbot\Includes\Services\Chatbot_Analytics\Data\Analytics_Data;
use Limb_Chatbot\Includes\Services\Chatbot_Analytics\Data\Analytics_Data_Point;
use Limb_Chatbot\Includes\Services\Chatbot_Analytics\Types\Analytics_Type;

/**
 * Class New_Visitors_Calculator
 *
 * Calculates new visitors analytics by counting unique chatbot user records based on created_at timestamp.
 * Supports flexible period bucketing (hourly, daily, weekly, monthly, yearly).
 *
 * @package Limb_Chatbot\Includes\Services\Chatbot_Analytics\Calculators
 * @since 1.0.0
 */
class New_Visitors_Calculator implements Analytics_Calculator {

	/**
	 * Calculate new visitors analytics for a chatbot.
	 *
	 * @param Analytics_Type $type          The analytics type (must be 'new_visitors')
	 * @param int|null       $starting_from Unix timestamp in UTC+0 or null for 'all'
	 * @param int            $ending_at    Unix timestamp in UTC+0 for the end of the period
	 * @param Chatbot        $chatbot       The chatbot to filter by
	 * @param float          $utc_offset    UTC offset in hours for timezone conversion (default: 0.0)
	 *
	 * @return Analytics_Data The calculated new visitors analytics
	 * @since 1.0.0
	 */
	public function calculate( Analytics_Type $type, ?int $starting_from, int $ending_at, Chatbot $chatbot, float $utc_offset = 0.0 ): Analytics_Data {

		// Determine bucketing strategy based on time range
		$strategy = Period_Bucketing::get_strategy( $starting_from, $ending_at );

		// Get buckets for this strategy (with UTC offset for timezone conversion)
		$buckets = Period_Bucketing::get_buckets( $strategy, $starting_from, $ending_at, $utc_offset );

		// Fetch visitor participation data from database
		// Returns Chat_Participant objects for bucketing by joined_at
		$all_participants = $this->get_visitor_records( $chatbot, $starting_from, $ending_at );

		// Create data points by aggregating participants into buckets
		$data_points = [];
		foreach ( $buckets as $bucket ) {
			$count = $this->count_participants_in_bucket( $all_participants, $bucket['start'], $bucket['end'] );

			$data_points[] = new Analytics_Data_Point(
				$bucket['label'],
				$count
			);
		}

		// Calculate comparison with previous period and get period totals
		$comparison_result = $this->calculate_comparison(
			$starting_from,
			$ending_at,
			count( array_unique( array_map( fn( $p ) => $p->user_id, $all_participants ) ) ),
			$chatbot
		);

		// Determine period label based on starting_from
		$period_label = $this->determine_period_label( $starting_from, $ending_at );

		// Create analytics data object
		$analytics_data = new Analytics_Data(
			$type,
			$period_label,
			$starting_from,
			$data_points
		);

		// Set the period totals
		$analytics_data->set_current_period_total( $comparison_result['current_total'] );
		$analytics_data->set_previous_period_total( $comparison_result['previous_total'] );

		return $analytics_data;
	}

	/**
	 * Fetch all visitor participation records for a chatbot within optional time range.
	 *
	 * Fetches Chat_Participant records where joined_at falls within the given period.
	 * Returns unique users who participated in that period (first participation per user).
	 *
	 * @param Chatbot  $chatbot        The chatbot
	 * @param int|null $starting_from  Unix timestamp or null for 'all'
	 * @param int      $ending_at      Unix timestamp
	 *
	 * @return array Array of Chat_Participant objects from the period
	 * @since 1.0.0
	 */
	private function get_visitor_records( Chatbot $chatbot, ?int $starting_from, int $ending_at ): array {
		// Get Chat_Participant records where joined_at is in the given period
		$where = [];

		if ( null !== $starting_from ) {
			// Convert Unix timestamps to database datetime format
			$start_datetime = gmdate( 'Y-m-d H:i:s', $starting_from );
			$end_datetime   = gmdate( 'Y-m-d H:i:s', $ending_at );

			// Use >= and <= operators for date range filtering
			$where['joined_at>='] = $start_datetime;
			$where['joined_at<='] = $end_datetime;
		}

		// Get participants who joined in the period
		$participants_in_period = Chat_Participant::where( $where )->get();

		// Filter to only first participation per user (unique users only)
		$unique_user_ids = [];
		$filtered_participants = [];
		foreach ( $participants_in_period as $participant ) {
			if ( ! isset( $unique_user_ids[ $participant->user_id ] ) ) {
				$unique_user_ids[ $participant->user_id ] = true;
				$filtered_participants[] = $participant;
			}
		}

		return (array) $filtered_participants;
	}

	/**
	 * Count unique visitors (by user_id) within a specific bucket (time range).
	 *
	 * @param array $participants Array of Chat_Participant objects
	 * @param int   $bucket_start Unix timestamp for bucket start
	 * @param int   $bucket_end   Unix timestamp for bucket end
	 *
	 * @return int Count of unique visitors in this bucket
	 * @since 1.0.0
	 */
	private function count_participants_in_bucket( array $participants, int $bucket_start, int $bucket_end ): int {
		$unique_users = [];

		foreach ( $participants as $participant ) {
			// Convert joined_at string to Unix timestamp
			// Format: '2026-01-08 09:27:35'
			$joined_timestamp = strtotime( $participant->joined_at );

			// Check if this participant joined within the bucket time range
			if ( $joined_timestamp >= $bucket_start && $joined_timestamp < $bucket_end ) {
				// Only count each user once per bucket
				$unique_users[ $participant->user_id ] = true;
			}
		}

		return count( $unique_users );
	}

	/**
	 * Calculate comparison with previous period.
	 *
	 * Compares current period visitor count with previous same-length period.
	 * Counts unique users who participated in each period using Chat_Participant.joined_at.
	 * Returns an array with both period totals.
	 *
	 * @param int|null $starting_from Unix timestamp or null for 'all'
	 * @param int      $ending_at     Unix timestamp
	 * @param int      $current_count Current period unique user count
	 * @param Chatbot  $chatbot       The chatbot
	 *
	 * @return array Array with keys: 'current_total', 'previous_total'
	 * @since 1.0.0
	 */
	private function calculate_comparison( ?int $starting_from, int $ending_at, $current_count, Chatbot $chatbot ): array {
		if ( null === $starting_from ) {
			// For 'all', no previous period to compare
			return [
				'current_total'  => $current_count,
				'previous_total' => 0,
			];
		}

		$period_length = $ending_at - $starting_from;
		$prev_start    = $starting_from - $period_length;
		$prev_end      = $starting_from;

		// Get database formatted dates
		$prev_start_date = gmdate( 'Y-m-d H:i:s', $prev_start );
		$prev_end_date   = gmdate( 'Y-m-d H:i:s', $prev_end );

		// Get Chat_Participants in previous period
		$prev_participants = Chat_Participant::where( [
			'joined_at>=' => $prev_start_date,
			'joined_at<=' => $prev_end_date,
		] )->get();

		// Count unique users in previous period
		$previous_user_ids = [];
		foreach ( $prev_participants as $participant ) {
			$previous_user_ids[ $participant->user_id ] = true;
		}

		$previous_count = count( $previous_user_ids );

		return [
			'current_total'  => $current_count,
			'previous_total' => $previous_count,
		];
	}

	/**
	 * Determine human-readable period label.
	 *
	 * @param int|null $starting_from Unix timestamp or null for 'all'
	 * @param int      $ending_at     Unix timestamp
	 *
	 * @return string Period label
	 * @since 1.0.0
	 */
	private function determine_period_label( ?int $starting_from, int $ending_at ): string {
		if ( null === $starting_from ) {
			return __( 'All Time', 'limb-chatbot' );
		}

		$diff = $ending_at - $starting_from;
		$days = (int) ( $diff / ( 24 * 60 * 60 ) );

		if ( $days < 1 ) {
			$hours = (int) ( $diff / ( 60 * 60 ) );
			if ( $hours < 1 ) {
				$minutes = (int) ( $diff / 60 );
				return sprintf( _n( 'Last %d Minute', 'Last %d Minutes', $minutes, 'limb-chatbot' ), $minutes );
			}
			return sprintf( _n( 'Last %d Hour', 'Last %d Hours', $hours, 'limb-chatbot' ), $hours );
		}

		return sprintf( _n( 'Last %d Day', 'Last %d Days', $days, 'limb-chatbot' ), $days );
	}
}
