<?php
/**
 * OpenAI Responses API Client
 * Basic communication with OpenAI's new Responses API (2025)
 *
 * @package AgenticWP
 */

namespace Agentic_WP;

defined( 'ABSPATH' ) || exit;

use Agentic_WP\Error_Handler;

/**
 * OpenAI API client with Responses API integration.
 *
 * @package AgenticWP
 * @since 1.0.0
 */
class OpenAI_Client {

	/**
	 * Base URL for OpenAI Responses API.
	 *
	 * @var string
	 */
	private $base_url = 'https://api.openai.com/v1/responses';

	/**
	 * OpenAI API key.
	 *
	 * @var string
	 */
	private $api_key;

	/**
	 * Default parameters for API requests.
	 *
	 * @var array
	 */
	private $default_params;

	/**
	 * Initialize the OpenAI client.
	 */
	public function __construct() {
		$this->api_key        = Settings::get_api_key();
		$this->default_params = array(
			'model'             => Settings::get_model(),
			'max_output_tokens' => Settings::get_max_output_tokens(),
			'instructions'      => 'You are a helpful WordPress assistant. Format responses using Markdown.',
			'reasoning'         => array(
				'effort'  => Settings::get_reasoning_effort(),
				'summary' => 'auto',
			),
		);
	}

	/**
	 * Create a new response (synchronous).
	 *
	 * @param array $params Request parameters.
	 * @return array|WP_Error Response data or error.
	 */
	public function create_response( array $params ): array|\WP_Error {
		$error = $this->validate_request( $params );
		if ( $error ) {
			return $error;
		}

		$params = array_merge( $this->default_params, $params );

		do_action( 'agentic_wp_before_openai_request', $params, $this->base_url );

		$data = $this->post_request( $this->base_url, $params );
		if ( is_wp_error( $data ) ) {
			return $data;
		}

		// Handle tool call loop.
		$previous_response_id = $data['id'] ?? null;
		$function_calls       = $this->extract_function_calls_from_output( $data['output'] ?? array() );
		$iterations           = 0;

		while ( ! empty( $function_calls ) && $iterations < 100 ) {
			$tool_outputs = array_map(
				fn( $call ) => array(
					'type'    => 'function_call_output',
					'call_id' => $call['call_id'],
					'output'  => Tools::dispatch( $call['name'], $call['arguments'] ),
				),
				$function_calls
			);

			$follow_params = array_merge(
				$this->default_params,
				array_filter(
					array(
						'input'                => $tool_outputs,
						'previous_response_id' => $previous_response_id,
						'instructions'         => $params['instructions'] ?? null,
						'tools'                => $params['tools'] ?? null,
					)
				)
			);

			$data = $this->post_request( $this->base_url, $follow_params );
			if ( is_wp_error( $data ) ) {
				return $data;
			}

			$previous_response_id = $data['id'] ?? $previous_response_id;
			$function_calls       = $this->extract_function_calls_from_output( $data['output'] ?? array() );
			++$iterations;
		}

		if ( $iterations >= 100 ) {
			Error_Handler::log_error( 'safety_guard', 'Reached tool-call safety guard (100 iterations)', compact( 'previous_response_id', 'iterations' ) );
		}

		do_action( 'agentic_wp_after_openai_response', $data, $params, $iterations );

		return $data;
	}

	/**
	 * Create a background response.
	 *
	 * @param array $params Request parameters.
	 * @return array|WP_Error Response data or error.
	 */
	public function create_response_background( array $params ): array|\WP_Error {
		$error = $this->validate_request( $params );
		if ( $error ) {
			return $error;
		}

		$params = array_merge(
			$this->default_params,
			$params,
			array(
				'background' => true,
				'store'      => true,
			)
		);

		return $this->post_request( $this->base_url, $params );
	}

	/**
	 * Get background response status.
	 *
	 * @param string $id Response ID.
	 * @return array|WP_Error Response data or error.
	 */
	public function get_response( string $id ): array|\WP_Error {
		$error = $this->validate_api_key();
		if ( $error ) {
			return $error;
		}
		if ( empty( $id ) ) {
			return new \WP_Error( 'missing_id', __( 'Response ID is required.', 'agenticwp' ) );
		}

		return $this->get_request( $this->base_url . '/' . $id );
	}

	/**
	 * Create follow-up background response with tool outputs.
	 *
	 * @param string $previous_id Previous response ID.
	 * @param array  $tool_outputs Function call results.
	 * @param array  $opts Optional parameters.
	 * @return array|WP_Error Response data or error.
	 */
	public function followup_response_background( string $previous_id, array $tool_outputs, array $opts = array() ): array|\WP_Error {
		$error = $this->validate_api_key();
		if ( $error ) {
			return $error;
		}
		if ( empty( $previous_id ) || empty( $tool_outputs ) ) {
			return new \WP_Error( 'missing_params', __( 'Previous response ID and tool outputs are required.', 'agenticwp' ) );
		}

		$params = array_merge(
			$this->default_params,
			$opts,
			array(
				'background'           => true,
				'store'                => true,
				'previous_response_id' => $previous_id,
				'input'                => $tool_outputs,
			)
		);

		return $this->post_request( $this->base_url, $params );
	}

	/**
	 * Create an image using OpenAI's Images API.
	 *
	 * @param string $prompt Text description of the desired image.
	 * @param array  $options Optional parameters.
	 * @return array|WP_Error Response data or error.
	 */
	public function create_image( string $prompt, array $options = array() ): array|\WP_Error {
		$error = $this->validate_api_key();
		if ( $error ) {
			return $error;
		}
		if ( empty( $prompt ) ) {
			return new \WP_Error( 'missing_prompt', __( 'Image prompt is required.', 'agenticwp' ) );
		}

		$params = array_merge(
			array(
				'model'         => 'gpt-image-1.5',
				'size'          => '1024x1024',
				'quality'       => 'low',
				'output_format' => 'webp',
				'background'    => 'auto',
				'n'             => 1,
			),
			$options,
			array( 'prompt' => $prompt )
		);

		return $this->post_request( 'https://api.openai.com/v1/images/generations', $params, 120, true );
	}

	/**
	 * Cancel a background response.
	 *
	 * @param string $id Response ID.
	 * @return array|WP_Error Response data or error.
	 */
	public function cancel_response( string $id ): array|\WP_Error {
		$error = $this->validate_api_key();
		if ( $error ) {
			return $error;
		}
		if ( empty( $id ) ) {
			return new \WP_Error( 'missing_id', __( 'Response ID is required.', 'agenticwp' ) );
		}

		return $this->post_request( $this->base_url . '/' . $id . '/cancel', array() );
	}

	/**
	 * Extract function calls from the Responses API output.
	 *
	 * @param array $output Output array from API response.
	 * @return array Parsed function call entries.
	 */
	public function extract_function_calls_from_output( array $output ): array {
		$calls = array();
		foreach ( $output as $item ) {
			if ( ( $item['type'] ?? '' ) !== 'function_call' ) {
				continue;
			}

			$args = $item['arguments'] ?? array();
			if ( is_string( $args ) ) {
				$decoded = json_decode( $args, true );
				$args    = ( json_last_error() === JSON_ERROR_NONE && is_array( $decoded ) ) ? $decoded : array();
			}

			$calls[] = array(
				'name'      => (string) ( $item['name'] ?? '' ),
				'call_id'   => (string) ( $item['call_id'] ?? '' ),
				'arguments' => $args,
			);
		}
		return $calls;
	}

	/**
	 * Validate API key is configured.
	 *
	 * @return \WP_Error|null Error if invalid, null if valid.
	 */
	private function validate_api_key(): ?\WP_Error {
		return empty( $this->api_key ) ? new \WP_Error( 'no_api_key', __( 'OpenAI API key not configured.', 'agenticwp' ) ) : null;
	}

	/**
	 * Validate request has API key and input.
	 *
	 * @param array $params Request parameters.
	 * @return \WP_Error|null Error if invalid, null if valid.
	 */
	private function validate_request( array $params ): ?\WP_Error {
		$error = $this->validate_api_key();
		if ( $error ) {
			return $error;
		}
		if ( empty( $params['input'] ) ) {
			return new \WP_Error( 'missing_input', __( 'Input parameter is required.', 'agenticwp' ) );
		}
		return null;
	}

	/**
	 * Make a POST request to OpenAI API.
	 *
	 * @param string $url Endpoint URL.
	 * @param array  $params Request body.
	 * @param int    $timeout Request timeout.
	 * @param bool   $is_image Whether this is an image API request.
	 * @return array|WP_Error Response data or error.
	 */
	private function post_request( string $url, array $params, int $timeout = 60, bool $is_image = false ): array|\WP_Error {
		$response = wp_remote_post(
			$url,
			array(
				'headers' => array(
					'Content-Type'  => 'application/json',
					'Authorization' => 'Bearer ' . $this->api_key,
				),
				'body'    => wp_json_encode( $params ),
				'timeout' => $timeout,
			)
		);

		return $this->process_response( $response, $is_image );
	}

	/**
	 * Make a GET request to OpenAI API.
	 *
	 * @param string $url Endpoint URL.
	 * @return array|WP_Error Response data or error.
	 */
	private function get_request( string $url ): array|\WP_Error {
		$response = wp_remote_get(
			$url,
			array(
				'headers' => array(
					'Content-Type'  => 'application/json',
					'Authorization' => 'Bearer ' . $this->api_key,
				),
				'timeout' => 30,
			)
		);

		return $this->process_response( $response );
	}

	/**
	 * Process API response.
	 *
	 * @param array|WP_Error $response HTTP response.
	 * @param bool           $is_image Whether this is an image API response.
	 * @return array|WP_Error Processed response or error.
	 */
	private function process_response( $response, bool $is_image = false ): array|\WP_Error {
		if ( is_wp_error( $response ) ) {
			Error_Handler::log_error( 'http', $response );
			return new \WP_Error( 'http_error', __( 'Network error occurred.', 'agenticwp' ) );
		}

		$status_code = wp_remote_retrieve_response_code( $response );
		$body        = wp_remote_retrieve_body( $response );
		$data        = json_decode( $body, true );

		if ( json_last_error() !== JSON_ERROR_NONE ) {
			Error_Handler::log_error( 'json_parse', json_last_error_msg(), array( 'body_preview' => substr( $body, 0, 100 ) ) );
			return new \WP_Error( 'json_parse_error', __( 'Invalid response from OpenAI.', 'agenticwp' ) );
		}

		if ( 200 !== $status_code ) {
			return $this->handle_api_error( $status_code, $body, $data, $is_image );
		}

		// Extract image base64 data.
		if ( $is_image && isset( $data['data'][0]['b64_json'] ) ) {
			$data['b64_json'] = $data['data'][0]['b64_json'];
		}

		// Extract text and reasoning from Responses API output.
		if ( isset( $data['output'] ) && is_array( $data['output'] ) ) {
			$data = $this->extract_output_content( $data );
		}

		return $data;
	}

	/**
	 * Handle API error responses.
	 *
	 * @param int    $status_code HTTP status code.
	 * @param string $body Response body.
	 * @param array  $data Decoded response data.
	 * @param bool   $is_image Whether this is an image API error.
	 * @return \WP_Error Error object.
	 */
	private function handle_api_error( int $status_code, string $body, ?array $data, bool $is_image ): \WP_Error {
		$error_map = array(
			401 => array( 'invalid_api_key', __( 'Invalid API key.', 'agenticwp' ) ),
			429 => array( 'rate_limit', $is_image ? __( 'Too many requests. Please wait a moment and try again.', 'agenticwp' ) : __( 'Rate limit exceeded. Please try again later.', 'agenticwp' ) ),
			500 => array( 'api_error', __( 'OpenAI service error. Please try again later.', 'agenticwp' ) ),
			502 => array( 'api_error', __( 'OpenAI service error. Please try again later.', 'agenticwp' ) ),
			503 => array( 'api_error', __( 'OpenAI service error. Please try again later.', 'agenticwp' ) ),
		);

		// Handle content moderation for image API.
		if ( $is_image && 400 === $status_code ) {
			$api_error = $data['error']['message'] ?? '';
			if ( stripos( $api_error, 'safety' ) !== false || stripos( $api_error, 'moderation' ) !== false ) {
				$error_map[400] = array( 'content_moderation', __( 'This prompt was flagged by content moderation. Please try a different prompt.', 'agenticwp' ) );
			} else {
				$error_map[400] = array( 'bad_request', __( 'Invalid request. Please check your prompt and settings.', 'agenticwp' ) );
			}
		}

		$error_info = $error_map[ $status_code ] ?? array( 'api_error', __( 'OpenAI API error occurred.', 'agenticwp' ) );

		Error_Handler::log_error( $is_image ? 'image_api_response' : 'api_response', "Status {$status_code}", array( 'body_preview' => substr( $body, 0, 200 ) ) );

		return new \WP_Error( $error_info[0], $error_info[1], array( 'status' => $status_code ) );
	}

	/**
	 * Extract text and reasoning content from output.
	 *
	 * @param array $data Response data with output array.
	 * @return array Data with extracted output_text and reasoning_summary.
	 */
	private function extract_output_content( array $data ): array {
		$text_content      = '';
		$reasoning_summary = '';

		foreach ( $data['output'] as $item ) {
			$type = $item['type'] ?? '';

			if ( 'message' === $type && isset( $item['content'] ) ) {
				foreach ( $item['content'] as $content ) {
					if ( in_array( $content['type'] ?? '', array( 'output_text', 'text' ), true ) ) {
						$text_content .= $content['text'] ?? '';
					}
				}
			}

			if ( 'reasoning' === $type && isset( $item['summary'] ) ) {
				foreach ( $item['summary'] as $summary ) {
					$reasoning_summary .= $summary['text'] ?? '';
				}
			}
		}

		if ( $text_content ) {
			$data['output_text'] = $text_content;
		}
		$data['reasoning_summary'] = $reasoning_summary;

		return $data;
	}
}
