<?php

namespace Limb_Chatbot\Includes\Services\Chatbot_Analytics\Calculators;

use Limb_Chatbot\Includes\Data_Objects\Chat;
use Limb_Chatbot\Includes\Data_Objects\Chat_Meta;
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 Conversations_Calculator
 *
 * Calculates conversation analytics by counting chat 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 Conversations_Calculator implements Analytics_Calculator {

	/**
	 * Calculate conversations analytics for a chatbot.
	 *
	 * @param Analytics_Type $type          The analytics type (must be 'conversations')
	 * @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 conversations 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 conversation data from database
		$all_conversations = $this->get_conversation_records( $chatbot, $starting_from, $ending_at );

		// Create data points by aggregating conversations into buckets
		$data_points = [];
		foreach ( $buckets as $bucket ) {
			$count = $this->count_conversations_in_bucket( $all_conversations, $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($all_conversations),
			$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 conversation records for a chatbot within optional time range.
	 *
	 * Uses Chat::where() with created_at filtering.
	 * Date format from database: '2026-01-08 09:27:35'
	 *
	 * @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 objects with timestamps
	 * @since 1.0.0
	 */
	private function get_conversation_records( Chatbot $chatbot, ?int $starting_from, int $ending_at ): array {
		$chatbot_id = $chatbot->get_id();
		$preview_chat_ids = $this->get_preview_chat_ids_for_chatbot();

		$where = [
			'chatbot_id' => $chatbot_id,
		];
		if ( ! empty( $preview_chat_ids ) ) {
			$where['id NOT IN'] = $preview_chat_ids;
		}

		// Add timestamp filters if not 'all'
		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['created_at>='] = $start_datetime;
			$where['created_at<='] = $end_datetime;
		}

		// Query using Chat ORM
		$collection = Chat::where( $where );

		// Convert collection to array
		return $collection->get();
	}

	/**
	 * Count conversations within a specific bucket (time range).
	 *
	 * @param array $conversations  Array of Chat objects
	 * @param int   $bucket_start   Unix timestamp for bucket start
	 * @param int   $bucket_end     Unix timestamp for bucket end
	 *
	 * @return int Count of conversations in this bucket
	 * @since 1.0.0
	 */
	private function count_conversations_in_bucket( array $conversations, int $bucket_start, int $bucket_end ): int {
		$count = 0;

		foreach ( $conversations as $conversation ) {
			// Convert created_at string to Unix timestamp
			// Format: '2026-01-08 09:27:35'
			$created_timestamp = strtotime( $conversation->created_at );

			// Check if this conversation falls within the bucket time range
			if ( $created_timestamp >= $bucket_start && $created_timestamp < $bucket_end ) {
				$count++;
			}
		}

		return $count;
	}

	/**
	 * Calculate comparison with previous period.
	 *
	 * Compares current period conversation count with previous same-length period.
	 * Returns an array with comparison percentage and both period totals.
	 *
	 * @param int|null $starting_from Unix timestamp or null for 'all'
	 * @param int      $ending_at     Unix timestamp
	 * @param Chatbot  $chatbot       The chatbot
	 *
	 * @return array Array with keys: 'comparison', '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', comparison doesn't make sense
			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 );

		// Count conversations in previous period
		$previous_count = Chat::count( [
			'chatbot_id'   => $chatbot->get_id(),
			'created_at>=' => $prev_start_date,
			'created_at<=' => $prev_end_date,
			'id NOT IN'    => $this->get_preview_chat_ids_for_chatbot( $chatbot->get_id() ),
		] );
		// Calculate percentage change
		if ( 0 === $previous_count || null === $previous_count ) {
			return [
				'current_total'  => $current_count,
				'previous_total' => 0,
			];
		}


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

	/**
	 * Get preview chat IDs for a specific chatbot.
	 *
	 * @return int[]
	 */
	private function get_preview_chat_ids_for_chatbot(): array {
		$preview_metas = Chat_Meta::where( [ 'meta_key' => 'preview' ], -1, -1 );
		if ( $preview_metas->is_empty() ) {
			return [];
		}

		return array_values(
			array_unique(
				array_filter( array_map( 'absint', $preview_metas->pluck( 'chat_id' ) ) )
			)
		);
	}

	/**
	 * 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 );
	}
}
