<?php

namespace Limb_Chatbot\Includes\AI_Providers\Open_Router\Handlers;

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 OpenRouter endpoints.
 *
 * Extends the base response handler and adds OpenRouter-specific
 * error handling and response body processing.
 *
 * @package Limb_Chatbot\Includes\AI_Providers\Open_Router\Handlers
 * @since 1.0.14
 */
class Response_Handler extends Base_Response_Handler implements Response_Handler_Interface {

	const STATUS_CODE_BAD_REQUEST   = 400;
	const STATUS_CODE_UNAUTHORIZED  = 401;
	const STATUS_CODE_PAYMENT_REQUIRED = 402;
	const STATUS_CODE_NOT_FOUND     = 404;
	const STATUS_CODE_QUOTA_EXCEED  = 429;
	const STATUS_CODE_SERVER_ERROR  = 500;
	const STATUS_CODE_BAD_GATEWAY   = 502;
	const STATUS_CODE_UNAVAILABLE   = 503;

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

	/**
	 * Token usage information extracted from the response.
	 *
	 * @var Token_Usage|null
	 * @since 1.0.14
	 */
	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 OpenRouter error is found in the response.
	 * @since 1.0.14
	 */
	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.14
	 */
	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 {
			$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.14
	 */
	protected function check_for_errors(): void {
		$error   = null;
		$message = null;

		if ( empty( $this->stream_parser ) ) {
			$body = $this->get_body();

			// Ensure body is in consistent format
			if ( is_string( $body ) ) {
				$body = json_decode( $body, true );
			}

			// Check for error in response body
			if ( is_object( $body ) && isset( $body->error ) ) {
				$error   = $body->error;
				$message = $error->message ?? __( 'An unknown error occurred.', 'limb-chatbot' );
			} elseif ( is_array( $body ) && isset( $body['error'] ) ) {
				$error   = $body['error'];
				$message = $error['message'] ?? __( 'An unknown error occurred.', 'limb-chatbot' );
			}
		} elseif ( isset( $this->stream_parser->error ) ) {
			$error   = $this->stream_parser->error;
			$message = $error['message'] ?? __( 'An unknown error occurred.', 'limb-chatbot' );
		}

		if ( isset( $message ) && isset( $error ) ) {
			$error_code = $this->determine_error_code( $error, $message );
			$exception  = new Exception( $error_code, $message, $error, $this->get_status_code() );

			// Set quota flag for quota-related errors
			if ( $error_code === Error_Codes::QUOTA_EXCEED ) {
				$exception->set_related_to_quota( true );
			}

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

	/**
	 * Determines the appropriate error code based on the error data and message.
	 *
	 * @param mixed  $error   Error object or array from response.
	 * @param string $message Error message.
	 * @return int Appropriate error code.
	 * @since 1.0.14
	 */
	private function determine_error_code( $error, string $message ): int {
		$status_code   = $this->get_status_code();
		$message_lower = strtolower( $message );

		// Check HTTP status codes first
		switch ( $status_code ) {
			case self::STATUS_CODE_BAD_REQUEST:
				return Error_Codes::VALIDATION_INVALID_VALUE;
			case self::STATUS_CODE_UNAUTHORIZED:
				return Error_Codes::AUTHENTICATION_UNAUTHORIZED;
			case self::STATUS_CODE_PAYMENT_REQUIRED:
			case self::STATUS_CODE_QUOTA_EXCEED:
				return Error_Codes::QUOTA_EXCEED;
			case self::STATUS_CODE_NOT_FOUND:
				return Error_Codes::NOT_FOUND;
			case self::STATUS_CODE_SERVER_ERROR:
			case self::STATUS_CODE_BAD_GATEWAY:
			case self::STATUS_CODE_UNAVAILABLE:
				return Error_Codes::TECHNICAL_ERROR;
		}

		// Check error object/array properties if available
		if ( is_object( $error ) ) {
			if ( isset( $error->code ) ) {
				return $this->map_error_code( $error->code );
			}
		} elseif ( is_array( $error ) && isset( $error['code'] ) ) {
			return $this->map_error_code( $error['code'] );
		}

		// Default to technical error
		return Error_Codes::TECHNICAL_ERROR;
	}

	/**
	 * Maps OpenRouter error codes to internal error codes.
	 *
	 * @param  string|int|null  $code  The OpenRouter error code from the response body.
	 *
	 * @return int Internal error code.
	 * @since 1.0.14
	 */
	protected function map_error_code( $code ): int {
		// Use error code from response body
		$error_code = (int) $code;

		switch ( $error_code ) {
			case self::STATUS_CODE_BAD_REQUEST:
				return Error_Codes::VALIDATION_INVALID_VALUE;
			case self::STATUS_CODE_UNAUTHORIZED:
				return Error_Codes::AUTHENTICATION_UNAUTHORIZED;
			case self::STATUS_CODE_PAYMENT_REQUIRED:
			case self::STATUS_CODE_QUOTA_EXCEED:
				return Error_Codes::QUOTA_EXCEED;
			case self::STATUS_CODE_NOT_FOUND:
				return Error_Codes::NOT_FOUND;
			case self::STATUS_CODE_SERVER_ERROR:
			case self::STATUS_CODE_BAD_GATEWAY:
			case self::STATUS_CODE_UNAVAILABLE:
				return Error_Codes::TECHNICAL_ERROR;
			default:
				return Error_Codes::TECHNICAL_ERROR;
		}
	}
}
