<?php

namespace Limb_Chatbot\Includes\Services;

use Limb_Chatbot\Factories\Usage_Service_Factory;
use Limb_Chatbot\Includes\Data_Objects\Chat;
use Limb_Chatbot\Includes\Data_Objects\Limit;
use Limb_Chatbot\Includes\Data_Objects\Message;
use Limb_Chatbot\Includes\Data_Objects\AI_Model;
use Limb_Chatbot\Includes\Data_Objects\Token_Usage;


/**
 * Class Usage_Service
 *
 * Handles checking and updating usage limits for AI chatbot services.
 *
 * @since 1.0.0
 */
class Usage_Service {

	/**
	 * Limits collection.
	 *
	 * @var Collection
	 * @since 1.0.0
	 */
	protected Collection $limits;

	/**
	 * Factory to create usage checking services.
	 *
	 * @var Usage_Service_Factory
	 * @since 1.0.0
	 */
	protected Usage_Service_Factory $factory;

	/**
	 * Constructor.
	 *
	 * @param Collection $limits Limits collection to enforce.
	 *
	 * @since 1.0.0
	 */
	public function __construct( Collection $limits ) {
		$this->limits  = $limits;
		$this->factory = new Usage_Service_Factory();
	}

	/**
	 * Checks if any usage limits are exceeded for the given message.
	 *
	 * @param Message|null $message Optional message object.
	 *
	 * @return Limit|null The first exceeded limit or null if none exceeded.
	 * @since 1.0.0
	 */
	public function limits_passed( ?Message $message = null ): ?Limit {
		if ( $this->limits->is_empty() ) {
			return null;
		}
		$ai_provider = $this->limits->first()->get_chatbot()->get_ai_provider_id();
		do_action( "lbaic_before_limits_check_{$ai_provider}", $this->limits );
		$res = $this->limits->filter( function ( Limit $item ) use ( $message ) {
			if ( $item->is_enabled() && $service = $this->factory->make( $item ) ) {
				if ( $item->get_value() > 0 ) {
					return ( $service->check( $item, $message ) instanceof Limit );
				}

				return false;
			}

			return false;
		} );

		return $res->first();
	}

	/**
	 * Updates usage statistics based on token usage and AI model.
	 *
	 * @param Token_Usage $usage Token usage data.
	 * @param AI_Model    $model AI model associated with usage.
	 * @param Chat|null   $chat  Optional chat context.
	 *
	 * @return void
	 * @since 1.0.0
	 */
	public function update_usage( Token_Usage $usage, AI_Model $model, ?Chat $chat = null ) {
		do_action( "lbaic_before_update_usage_{$model->get_ai_provider_id()}", $usage, $model, $chat );
		if ( $this->limits->is_empty() ) {
			return;
		}
		$this->limits->map( function ( $item ) use ( $usage, $model, $chat ) {
			if ( $item->is_enabled() && $service = $this->factory->make( $item ) ) {
				$service->update_usage( $item, $usage, $model, $chat );
			}
		} );
	}

	/**
	 * Gets a localized error message for the given limit.
	 *
	 * Generates dynamic, user-friendly messages that consider all limit types,
	 * their units, and reset periods.
	 *
	 * @param Limit $limit Limit that was exceeded.
	 *
	 * @return string Localized error message.
	 * @since 1.0.0
	 */
	public function get_error_message( Limit $limit ): string {
		// Determine entity type label
		$entity_label = $this->get_entity_label( $limit );

		// Get unit-specific message and period info
		return $this->get_unit_message( $limit, $entity_label );
	}

	/**
	 * Gets the entity label (who reached the limit).
	 *
	 * @param Limit $limit The limit object.
	 *
	 * @return string The entity label - always returns "You" for user-friendly messaging.
	 * @since 1.0.0
	 */
	private function get_entity_label( Limit $limit ): string {
		// Always use "You" for a more personalized and friendly message
		return __( 'You', 'limb-chatbot' );
	}

	/**
	 * Gets the unit-specific error message with period information.
	 *
	 * @param Limit $limit The limit object.
	 * @param string $entity_label The entity label.
	 *
	 * @return string The formatted error message.
	 * @since 1.0.0
	 */
	private function get_unit_message( Limit $limit, string $entity_label ): string {
		$period = $limit->get_period();
		$period_text = $this->get_period_text( $period );
		$error_message = $limit->get_error_message( );
		switch ( $limit->get_unit() ) {
			case Limit::UNIT_MESSAGE_PER_CHAT:
				return $this->get_message_per_chat_message( $entity_label, $period_text,  $error_message);

			case Limit::UNIT_MESSAGE_PER_DAY:
				return $this->get_message_per_day_message( $entity_label, $period_text, $error_message);

			case Limit::UNIT_NEW_CHAT:
				return $this->get_new_chat_message( $entity_label, $period_text, $error_message);

			case Limit::UNIT_TOKEN:
				return $this->get_token_limit_message( $entity_label, $period_text, $error_message);

			case Limit::UNIT_COST:
				return $this->get_cost_limit_message( $entity_label, $period_text, $error_message );

			case Limit::UNIT_WORD:
				return $this->get_word_limit_message( $entity_label, $period_text, $error_message );

			default:
				// translators: %s is the entity that reached the limit.
				return sprintf( __( "%s reached the usage limit. Please try again later.", 'limb-chatbot' ), $entity_label );
		}
	}

	/**
	 * Converts period (in seconds) to human-readable, friendly text.
	 *
	 * Handles year, month, week, day, hour, minute, and second conversions.
	 * Uses natural language like "today", "tomorrow" for better UX.
	 *
	 * @param int|null $period Period in seconds, or null if no period.
	 *
	 * @return string Human-readable period text (e.g., "today", "tomorrow", "in 2 weeks").
	 * @since 1.0.0
	 */
	private function get_period_text( ?int $period ): string {
		if ( $period === null || $period <= 0 ) {
			return '';
		}

		// Days in seconds
		$day_seconds = 86400;

		// Special cases for days: "today", "tomorrow", "in X days"
		if ( $period < $day_seconds ) {
			// Less than a day - return empty as these are usually shorter intervals
			return '';
		}

		$days = (int) ( $period / $day_seconds );

		// Handle natural language for common day-based periods
		if ( $days === 1 ) {
			return __( 'tomorrow', 'limb-chatbot' );
		}

		// Define time units in descending order (largest to smallest)
		// Using approximate conversions for larger units: month = 30 days, year = 365 days
		$time_units = [
			31536000 => [ __( 'year', 'limb-chatbot' ), __( 'years', 'limb-chatbot' ) ],      // 365 * 86400
			2592000  => [ __( 'month', 'limb-chatbot' ), __( 'months', 'limb-chatbot' ) ],    // 30 * 86400
			604800   => [ __( 'week', 'limb-chatbot' ), __( 'weeks', 'limb-chatbot' ) ],      // 7 * 86400
			86400    => [ __( 'day', 'limb-chatbot' ), __( 'days', 'limb-chatbot' ) ],
			3600     => [ __( 'hour', 'limb-chatbot' ), __( 'hours', 'limb-chatbot' ) ],
			60       => [ __( 'minute', 'limb-chatbot' ), __( 'minutes', 'limb-chatbot' ) ],
			1        => [ __( 'second', 'limb-chatbot' ), __( 'seconds', 'limb-chatbot' ) ],
		];

		foreach ( $time_units as $seconds => $labels ) {
			if ( $period >= $seconds ) {
				$count = (int) ( $period / $seconds );
				$label = ( $count === 1 ) ? $labels[0] : $labels[1];
				// translators: %d is the number of time units.
				return sprintf( __( 'in %d %s', 'limb-chatbot' ), $count, $label );
			}
		}

		return '';
	}

	/**
	 * Gets message for message-per-chat limit.
	 *
	 * @param string $entity_label  The entity label.
	 * @param string $period_text   The period text (e.g., "tomorrow", "in 2 days").
	 * @param string $error_message Optional custom error message from limit configuration.
	 *
	 * @return string The formatted message.
	 * @since 1.0.0
	 */
	private function get_message_per_chat_message( string $entity_label, string $period_text, string $error_message = '' ): string {
		if ( ! empty( $error_message ) ) {
			return $error_message;
		}

		if ( ! empty( $period_text ) ) {
			if ( 'tomorrow' === $period_text ) {
				// translators: %s is the entity.
				return sprintf(
					__( "%s've reached the maximum messages for this chat. 😊 Start a new conversation or come back tomorrow!", 'limb-chatbot' ),
					$entity_label
				);
			}
			// translators: %1$s is the entity, %2$s is the period (e.g., "in 2 days").
			return sprintf(
				__( "%s've reached the maximum messages for this chat. 😊 Start a new conversation or try again %s.", 'limb-chatbot' ),
				$entity_label,
				$period_text
			);
		}

		// translators: %s is the entity.
		return sprintf(
			__( "%s've reached the maximum messages for this chat. 😊 Please start a new conversation.", 'limb-chatbot' ),
			$entity_label
		);
	}

	/**
	 * Gets message for message-per-day limit.
	 *
	 * @param string $entity_label  The entity label.
	 * @param string $period_text   The period text (e.g., "tomorrow", "in 2 days").
	 * @param string $error_message Optional custom error message from limit configuration.
	 *
	 * @return string The formatted message.
	 * @since 1.0.0
	 */
	private function get_message_per_day_message( string $entity_label, string $period_text, string $error_message = '' ): string {
		if ( ! empty( $error_message ) ) {
			return $error_message;
		}

		if ( ! empty( $period_text ) ) {
			if ( 'tomorrow' === $period_text ) {
				// translators: %s is the entity.
				return sprintf(
					__( "%s've reached the maximum messages for today. 😊 Start a new conversation or come back tomorrow!", 'limb-chatbot' ),
					$entity_label
				);
			}
			// translators: %1$s is the entity, %2$s is the period (e.g., "in 2 days").
			return sprintf(
				__( "%s've reached the maximum messages for this chat. 😊 Start a new conversation or try again %s.", 'limb-chatbot' ),
				$entity_label,
				$period_text
			);
		}

		// translators: %s is the entity.
		return sprintf(
			__( "%s've reached the maximum messages for this chat. 😊 Please start a new conversation.", 'limb-chatbot' ),
			$entity_label
		);
	}


	/**
	 * Gets message for new-chat limit.
	 *
	 * @param string $entity_label  The entity label.
	 * @param string $period_text   The period text (e.g., "tomorrow", "in 2 days").
	 * @param string $error_message Optional custom error message from limit configuration.
	 *
	 * @return string The formatted message.
	 * @since 1.0.0
	 */
	private function get_new_chat_message( string $entity_label, string $period_text, string $error_message = '' ): string {
		if ( ! empty( $error_message ) ) {
			return $error_message;
		}

		if ( ! empty( $period_text ) ) {
			if ( 'tomorrow' === $period_text ) {
				// translators: %s is the entity.
				return sprintf(
					__( "%s've reached your chat limit for today. 😊 Come back tomorrow to continue!", 'limb-chatbot' ),
					$entity_label
				);
			}
			// translators: %1$s is the entity, %2$s is the period (e.g., "in 2 days").
			return sprintf(
				__( "%s've reached your chat limit. 😊 Please try again %s.", 'limb-chatbot' ),
				$entity_label,
				$period_text
			);
		}

		// translators: %s is the entity.
		return sprintf(
			__( "%s've reached your chat limit. ⏳ Please try again later.", 'limb-chatbot' ),
			$entity_label
		);
	}

	/**
	 * Gets message for token limit.
	 *
	 * @param string $entity_label  The entity label.
	 * @param string $period_text   The period text (e.g., "tomorrow", "in 2 days").
	 * @param string $error_message Optional custom error message from limit configuration.
	 *
	 * @return string The formatted message.
	 * @since 1.0.0
	 */
	private function get_token_limit_message( string $entity_label, string $period_text, string $error_message = '' ): string {
		if ( ! empty( $error_message ) ) {
			return $error_message;
		}

		if ( ! empty( $period_text ) ) {
			if ( 'tomorrow' === $period_text ) {
				// translators: %s is the entity.
				return sprintf(
					__( "%s've reached your chat limit for today. 😊 Come back tomorrow to continue!", 'limb-chatbot' ),
					$entity_label
				);
			}
			// translators: %1$s is the entity, %2$s is the period (e.g., "in 2 days").
			return sprintf(
				__( "%s've reached your chat limit. 😊 Please try again %s.", 'limb-chatbot' ),
				$entity_label,
				$period_text
			);
		}

		// translators: %s is the entity.
		return sprintf(
			__( "%s've reached your chat limit. 😊 Please try again later.", 'limb-chatbot' ),
			$entity_label
		);
	}

	/**
	 * Gets message for cost limit.
	 *
	 * @param string $entity_label  The entity label.
	 * @param string $period_text   The period text (e.g., "tomorrow", "in 2 days").
	 * @param string $error_message Optional custom error message from limit configuration.
	 *
	 * @return string The formatted message.
	 * @since 1.0.0
	 */
	private function get_cost_limit_message( string $entity_label, string $period_text, string $error_message = '' ): string {
		if ( ! empty( $error_message ) ) {
			return $error_message;
		}

		if ( ! empty( $period_text ) ) {
			if ( 'tomorrow' === $period_text ) {
				// translators: %s is the entity.
				return sprintf(
					__( "%s've reached your chat limit for today. 😊 Come back tomorrow to continue!", 'limb-chatbot' ),
					$entity_label
				);
			}
			// translators: %1$s is the entity, %2$s is the period (e.g., "in 2 days").
			return sprintf(
				__( "%s've reached your spending limit. 😊 Please try again %s.", 'limb-chatbot' ),
				$entity_label,
				$period_text
			);
		}

		// translators: %s is the entity.
		return sprintf(
			__( "%s've reached your spending limit. 😊 Please try again later.", 'limb-chatbot' ),
			$entity_label
		);
	}

	/**
	 * Gets message for word limit.
	 *
	 * @param string $entity_label  The entity label.
	 * @param string $period_text   The period text.
	 * @param string $error_message Optional custom error message from limit configuration.
	 *
	 * @return string The formatted message.
	 * @since 1.0.0
	 */
	private function get_word_limit_message( string $entity_label, string $period_text, string $error_message = '' ): string {
		if ( ! empty( $error_message ) ) {
			return $error_message;
		}

		return __( 'The message you sent is too long. 😊 Please try a shorter message.', 'limb-chatbot' );

	}

}