<?php

namespace Limb_Chatbot\Includes\AI_Providers\Open_Ai\Handlers;

use Limb_Chatbot\Includes\AI_Providers\Open_Ai\Endpoints\Embedding\Handlers\Embedding_Response_Handler;
use Limb_Chatbot\Includes\Data_Objects\Token_Usage;
use Limb_Chatbot\Includes\Exceptions\Error_Codes;
use Limb_Chatbot\Includes\Exceptions\Exception;
use Limb_Chatbot\Includes\Services\Response_Handler as Base_Response_Handler;
use Limb_Chatbot\Includes\Interfaces\Response_Handler_Interface;
use WP_Error;

/**
 * Handles API responses from OpenAI endpoints.
 *
 * Extends the base response handler and adds OpenAI-specific
 * error handling and response body processing.
 *
 * @package Limb_Chatbot\Includes\AI_Providers\Open_Ai\Handlers
 * @since 1.0.0
 */
class Response_Handler extends Base_Response_Handler implements Response_Handler_Interface {

	const STATUS_CODE_QUOTA_EXCEED = 429;
	const STATUS_CODE_UNAUTHORIZED = 401;

	/**
	 * When true, response bodies will be decoded as associative arrays.
	 *
	 * @var bool|null
	 * @since 1.0.0
	 */
	protected ?bool $body_cast_array = null;

	/**
	 * Token usage information extracted from the response.
	 *
	 * @var Token_Usage|null
	 * @since 1.0.0
	 */
	protected ?Token_Usage $usage = null;

	/**
	 * Parse the HTTP response.
	 *
	 * Calls parent parse and then checks for errors in the response.
	 *
	 * @return void
	 * @throws Exception If an OpenAI error is found in the response.
	 * @since 1.0.0
	 */
	public function parse() {
		parent::parse();
		static::check_for_errors();
	}

	/**
	 * Defines and decodes the response body.
	 *
	 * If the HTTP client is WP_Http, retrieves and decodes JSON body accordingly.
	 * For other clients (e.g., Event_Stream_Curl), body is left as null.
	 *
	 * @return void
	 * @since 1.0.0
	 */
	public function define_body(): void {
		if ( get_class( $this->http_client ) === \WP_Http::class ) {
			$body       = wp_remote_retrieve_body( $this->response );
			$this->body = $this->is_application_json() ? json_decode( $body, $this->body_cast_array ) : $body;
		} else {
			/**
			 * Basically when http_client is Event_Stream_Curl, then we are not parsing body from the chunks. So leaving body as null is normal
			 */
			$this->body = null;
		}
	}

	/**
	 * Checks for errors in the response body or stream parser.
	 *
	 * Throws an Exception with error details if an error is detected.
	 *
	 * @return void
	 * @throws Exception When an error is found in the response.
	 * @since 1.0.0
	 */
	protected function check_for_errors(): void {
		$error = null;
		if ( empty( $this->stream_parser ) ) {
			$body = $this->get_body();
			if ( is_string( $body ) ) {
				$body = json_decode( $body, true );
			}
			if ( is_object( $body ) && ! empty( $body->error ) ) {
				$error = (array) $body->error;
			} elseif ( is_array( $body ) && ! empty( $body['error'] ) ) {
				$error = $body['error'];
			}
		} else {
			$error = (array) $this->stream_parser->error;
		}
		if ( ! empty( $error ) ) {
			throw $this->interfere_the_exception( new Exception( Error_Codes::OPEN_AI_ERROR, $error['message'] ?? __( 'error', 'limb-chatbot' ), $error, $this->get_status_code() ) );
		}
	}

	/**
	 * Interferes with the exception to add appropriate error codes and flags.
	 *
	 * @param Exception $exception The exception to modify.
	 *
	 * @return Exception The modified exception.
	 * @since 1.0.0
	 */
	protected function interfere_the_exception( Exception $exception ): Exception {
		$error_data = $exception->get_error_data();
		// Check HTTP status codes for additional context
		$http_status = $exception->get_http_status();
		switch ( $http_status ) {
			case self::STATUS_CODE_FORBIDDEN:
			case self::STATUS_CODE_UNAUTHORIZED:
				$error_code = Error_Codes::AUTHENTICATION_UNAUTHORIZED;
				break;

			case self::STATUS_CODE_QUOTA_EXCEED:
				$exception->set_related_to_quota( true );
				$error_code = Error_Codes::QUOTA_EXCEED;
				break;
		}

		$quota_related = $exception->is_related_to_quota();
		$message       = $exception->getMessage();
		if ( $quota_related || $error_code = Error_Codes::AUTHENTICATION_UNAUTHORIZED ) {
			$message = preg_replace('/\*+/', str_repeat('*', 16), $exception->getMessage());
		}
		// Update the exception with the determined error code if it's different
		$new_exception = new Exception( $error_code ?? Error_Codes::OPEN_AI_ERROR, $message, $error_data, $http_status );
		if ( $quota_related ) {
			$new_exception->set_related_to_quota( true );
		}
		$exception = $new_exception;

		return $exception->attach_utility( $this->get_endpoint()->get_utility(), true );
	}
}
