<?php

namespace Limb_Chatbot\Includes\Services\Chatbot_Analytics\Calculators;

use Limb_Chatbot\Includes\Data_Objects\Action;
use Limb_Chatbot\Includes\Data_Objects\Action_Submission;
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 Action_Submissions_Calculator
 *
 * Calculates action submission analytics by counting submission records based on created_at timestamp.
 * Tracks how many times an action was submitted and succeeded/failed.
 * Supports flexible period bucketing (hourly, daily, weekly, monthly, yearly).
 *
 * @package Limb_Chatbot\Includes\Services\Chatbot_Analytics\Calculators
 * @since 1.0.0
 */
class Action_Submissions_Calculator implements Analytics_Calculator {

	/**
	 * Calculate action submissions analytics.
	 *
	 * @param  Analytics_Type  $type  The analytics type (action submission type)
	 * @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 (used to determine context)
	 * @param  float  $utc_offset  UTC offset in hours for timezone conversion (default: 0.0)
	 *
	 * @return Analytics_Data The calculated action submissions 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 {

		// Extract action ID from the type ID (format: "action_{action_id}")
		$action = $this->extract_action_id_from_type( $type->get_id() );

		if ( ! ( $action instanceof Action ) ) {
			// Return empty analytics data if action not found
			return new Analytics_Data(
				$type,
				'No Data',
				$starting_from,
				[]
			);
		}

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

		// Get buckets for this strategy
		$buckets = Period_Bucketing::get_buckets( $strategy, $starting_from, $ending_at, $utc_offset );

		// Fetch submission data from database
		$all_submissions = $this->get_submission_records( $action->get_id(), $starting_from, $ending_at );

		// Create data points by aggregating submissions into buckets
		$data_points = [];
		foreach ( $buckets as $bucket ) {
			$count = $this->count_submissions_in_bucket( $all_submissions, $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(
			$action->get_id(),
			$starting_from,
			$ending_at,
			count( $all_submissions )
		);

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

	/**
	 * Extract action ID from analytics type ID.
	 * Type ID format: numeric ID (e.g., "1", "2", "Contact Us")
	 * For action types created by factory, the ID is the action's numeric ID
	 *
	 * @param  string  $type_id  The analytics type ID
	 *
	 * @return Action|null The action ID or null if invalid format
	 * @since 1.0.0
	 */
	private function extract_action_id_from_type( string $type_id ): ?Action {
		// The type ID is already the action ID (numeric)
		return Action::find_by_name( $type_id );
	}

	/**
	 * Fetch all action submission records within optional time range.
	 *
	 * Uses Action_Submission::where() with created_at filtering.
	 * Date format from database: '2026-01-08 09:27:35'
	 *
	 * @param  int  $action_id  The action ID
	 * @param  int|null  $starting_from  Unix timestamp or null for 'all'
	 * @param  int  $ending_at  Unix timestamp
	 *
	 * @return array Array of Action_Submission objects with timestamps
	 * @since 1.0.0
	 */
	private function get_submission_records( int $action_id, ?int $starting_from, int $ending_at ): array {
		$where = [
			'action_id' => $action_id,
		];

		// 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 Action_Submission ORM
		$collection = Action_Submission::where( $where );

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

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

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

			// Check if this submission 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 submission count with previous same-length period.
	 * Returns an array with both period totals.
	 *
	 * @param  int  $action_id  The action ID
	 * @param  int|null  $starting_from  Unix timestamp or null for 'all'
	 * @param  int  $ending_at  Unix timestamp
	 * @param  int  $current_count  Current period submission count
	 *
	 * @return array Array with keys: 'current_total', 'previous_total'
	 * @since 1.0.0
	 */
	private function calculate_comparison(
		int $action_id,
		?int $starting_from,
		int $ending_at,
		int $current_count
	): 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 submissions in previous period
		$previous_count = Action_Submission::count( [
			'action_id'    => $action_id,
			'created_at>=' => $prev_start_date,
			'created_at<=' => $prev_end_date,
		] );

		// 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,
		];
	}

	/**
	 * Determine human-readable period label.
	 *
	 * @param  int|null  $starting_from  Unix timestamp or null for 'all'
	 * @param  int  $ending_at  Unix timestamp
	 *
	 * @return string Human-readable 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';
		}

		$strategy = Period_Bucketing::get_strategy( $starting_from, $ending_at );

		$start_date = gmdate( 'M d', $starting_from );
		$end_date   = gmdate( 'M d, Y', $ending_at );

		switch ( $strategy ) {
			case 'hourly':
				return 'Last 24 Hours';
			case 'daily':
				$days = (int) ( ( $ending_at - $starting_from ) / 86400 );

				return sprintf( 'Last %d Days', $days );
			case 'weekly':
				$weeks = ceil( ( $ending_at - $starting_from ) / 604800 );

				return sprintf( 'Last %d Weeks', $weeks );
			case 'monthly':
				$months = ceil( ( $ending_at - $starting_from ) / 2592000 );

				return sprintf( 'Last %d Months', $months );
			case 'yearly':
				$years = ceil( ( $ending_at - $starting_from ) / 31536000 );

				return sprintf( 'Last %d Years', $years );
			default:
				return $start_date . ' - ' . $end_date;
		}
	}
}
