<?php

namespace Limb_Chatbot\Includes\Services;

use Limb_Chatbot\Includes\Exceptions\Error_Codes;
use Limb_Chatbot\Includes\Exceptions\Exception;
use WP_Error;

/**
 * Custom HTTP client that handles server-sent event (SSE) streaming using cURL.
 *
 * @since 1.0.0
 */
class Event_Stream_Curl extends \WP_Http {

	/**
	 * Holds the streamed chunks of data.
	 *
	 * @since 1.0.0
	 * @var array
	 */
	protected array $chunks = [];

	/**
	 * Original request arguments.
	 *
	 * @since 1.0.0
	 * @var array
	 */
	protected array $args;

	/**
	 * The full response including headers, body, chunks, and parsed data.
	 *
	 * @since 1.0.0
	 * @var array|null
	 */
	protected ?array $response;

	/**
	 * Holds parsed chunks as a single string (optional).
	 *
	 * @since 1.0.0
	 * @var string|null
	 */
	protected ?string $parsed_chunks = '';

	/**
	 * Headers received in the HTTP response.
	 *
	 * @since 1.0.0
	 * @var array|null
	 */
	protected ?array $response_headers;

	/**
	 * Headers to be sent with the request.
	 *
	 * @since 1.0.0
	 * @var array
	 */
	protected array $request_headers = [];

	/**
	 * Default request timeout in seconds.
	 *
	 * @since 1.0.0
	 * @var int
	 */
	private static int $timeout = 60;

	/**
	 * Makes a POST request to the given URL and streams the response if a stream parser is provided.
	 *
	 * @param  string  $url  The endpoint URL.
	 * @param  array  $args  The request arguments, including headers, body, timeout, and stream_parser.
	 *
	 * @return array|WP_Error
	 * @throws Exception
	 * @since 1.0.0
	 */
	public function post( $url, $args = [] ) {
		$this->args = $args;
		$this->prepare_request_headers();
		$options = [
			'method'  => 'POST',
			'headers' => $this->request_headers,
			'body'    => $args['body'],
			'timeout' => $args['timeout'] ?? self::$timeout,
		];
		// Set the necessary CURL options and event stream headers
		if ( empty( $args['stream_parser'] ) || ! is_callable( $args['stream_parser'] ) ) {
			throw new Exception( Error_Codes::EMPTY_VALUE, __( 'Empty stream parser', 'limb-chatbot' ) );
		}
		add_action( 'http_api_curl', [ $this, 'stream_handler' ], 10, 3 );
		try {
			$response = wp_remote_post( $url, $options );
			if ( is_wp_error( $response ) ) {
				return $response;
			}
			$this->response = [
				'headers'       => $this->response_headers ?? [],
				'body'          => [],
				'chunks'        => $this->chunks,
				'parsed_chunks' => $this->parsed_chunks,
			];

			return $this->response;
		} catch ( \Exception $e ) {
			throw new Exception( Error_Codes::TECHNICAL_ERROR, $e->getMessage() );
		} finally {
			remove_action( 'http_api_curl', [ $this, 'stream_handler' ] );
		}
	}

	/**
	 * Converts the headers array to WP_Http-friendly format.
	 *
	 * @since 1.0.0
	 */
	protected function prepare_request_headers() {
		$this->request_headers           = $this->args['headers'] ?? [];
		$this->request_headers['Accept'] = 'text/event-stream';
	}

	/**
	 * Handles streaming data from the HTTP response.
	 *
	 * @param  \CurlHandle  $curl_handle  The cURL handle (passed by http_api_curl).
	 * @param  array  $request_args  The request arguments.
	 * @param  string  $url  The request URL.
	 */
	public function stream_handler( $curl_handle, $request_args, $url ) {
		curl_setopt( $curl_handle, CURLOPT_SSL_VERIFYPEER, true );
		curl_setopt( $curl_handle, CURLOPT_SSL_VERIFYHOST, 2 );
		curl_setopt( $curl_handle, CURLOPT_WRITEFUNCTION, function ( $ch, $data ) {
			$this->chunks[] = $data;
			if ( ! empty( $this->args['stream_parser'] ) && is_callable( $this->args['stream_parser'] ) ) {
				// Pass the chunk to the stream parser
				call_user_func( $this->args['stream_parser'], $data );
				$this->parsed_chunks .= $data; // Accumulate for final response
			}

			return strlen( $data );
		} );
		curl_setopt( $curl_handle, CURLOPT_HEADERFUNCTION, function ( $ch, $data ) {
			// Parse headers
			$this->response_headers[] = $data;

			return strlen( $data );
		} );
	}
}