<?php
/**
 * Anthropic Provider Class
 *
 * Integration with Anthropic Claude Messages API.
 *
 * @package SmartyPress_AI_Engine
 * @since 1.1.0
 */

if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

/**
 * Anthropic Provider class.
 */
class SmartyPress_AI_Anthropic_Provider extends SmartyPress_AI_AI_Provider {

	/**
	 * Anthropic API version.
	 */
	const API_VERSION = '2023-06-01';

	/**
	 * Constructor.
	 *
	 * @param string $api_key API key.
	 * @param string $model   Model name.
	 */
	public function __construct( $api_key, $model = 'claude-sonnet-4-5' ) {
		parent::__construct( $api_key, $model );
		$this->api_endpoint = 'https://api.anthropic.com/v1/messages';
	}

	/**
	 * Generate content using Anthropic Messages API.
	 *
	 * @param string $prompt The prompt to send to AI.
	 * @param array  $params Additional parameters.
	 * @return array Response with content or error.
	 */
	public function generate_content( $prompt, $params = array() ) {
		$system_prompt = $params['system_prompt'] ?? __( 'You are a helpful assistant for content generation.', 'smartypress-ai-engine' );
		$model         = ! empty( $params['model'] ) ? sanitize_text_field( $params['model'] ) : $this->model;

		if ( $this->is_json_response_requested( $params ) ) {
			$system_prompt = $this->append_json_instruction( $system_prompt );
		}

		$body = array(
			'model'       => $model,
			'system'      => $system_prompt,
			'messages'    => array(
				array(
					'role'    => 'user',
					'content' => $prompt,
				),
			),
			'max_tokens'  => max( 1, absint( $params['max_tokens'] ?? 2000 ) ),
			'temperature' => isset( $params['temperature'] ) ? (float) $params['temperature'] : 0.7,
		);

		$timeout = ! empty( $params['request_timeout'] ) ? absint( $params['request_timeout'] ) : 60;

		return $this->make_anthropic_request( $body, $timeout );
	}

	/**
	 * Describe image with a custom vision prompt.
	 *
	 * Used by Image ALT generation to create JSON metadata from the actual image.
	 *
	 * @param string $image_path Path to the image file.
	 * @param string $prompt     Prompt for the vision model.
	 * @param array  $params     Additional params.
	 * @return array Response with content or error.
	 */
	public function describe_image_with_prompt( $image_path, $prompt, $params = array() ) {
		if ( ! file_exists( $image_path ) || ! is_readable( $image_path ) ) {
			return array(
				'success' => false,
				'error'   => __( 'Image file not found or not readable.', 'smartypress-ai-engine' ),
			);
		}

		$image_data = file_get_contents( $image_path ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents
		if ( false === $image_data ) {
			return array(
				'success' => false,
				'error'   => __( 'Failed to read image file.', 'smartypress-ai-engine' ),
			);
		}

		$model         = ! empty( $params['model'] ) ? sanitize_text_field( $params['model'] ) : $this->model;
		$system_prompt = __( 'You generate accurate WordPress image metadata from visual content and SEO context.', 'smartypress-ai-engine' );

		if ( $this->is_json_response_requested( $params ) ) {
			$system_prompt = $this->append_json_instruction( $system_prompt );
		}

		$body = array(
			'model'       => $model,
			'system'      => $system_prompt,
			'messages'    => array(
				array(
					'role'    => 'user',
					'content' => array(
						array(
							'type'   => 'image',
							'source' => array(
								'type'       => 'base64',
								'media_type' => $this->detect_image_mime_type( $image_path ),
								'data'       => base64_encode( $image_data ), // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode
							),
						),
						array(
							'type' => 'text',
							'text' => $prompt,
						),
					),
				),
			),
			'max_tokens'  => max( 1, absint( $params['max_tokens'] ?? 500 ) ),
			'temperature' => isset( $params['temperature'] ) ? (float) $params['temperature'] : 0.2,
		);

		return $this->make_anthropic_request( $body, 120 );
	}

	/**
	 * Validate API key.
	 *
	 * @return bool True if valid, false otherwise.
	 */
	public function validate_api_key() {
		$result = $this->generate_content(
			__( 'Say "Hello"', 'smartypress-ai-engine' ),
			array(
				'max_tokens' => 10,
			)
		);

		return $result['success'] ?? false;
	}

	/**
	 * Get available models.
	 *
	 * @return array List of available models with metadata.
	 */
	public function get_available_models() {
		return SmartyPress_AI_Model_Map::get_text_models( 'anthropic' );
	}

	/**
	 * Make an Anthropic Messages API request.
	 *
	 * @param array $body    Request body.
	 * @param int   $timeout Request timeout.
	 * @return array
	 */
	private function make_anthropic_request( array $body, $timeout = 60 ) {
		$request_args = array(
			'headers' => array(
				'Content-Type'      => 'application/json',
				'Accept'            => 'application/json',
				'x-api-key'         => $this->api_key,
				'anthropic-version' => self::API_VERSION,
			),
			'body'    => wp_json_encode( $body ),
			'timeout' => $timeout,
		);

		$this->apply_proxy_to_request_args( $request_args );

		$response = wp_remote_post( $this->api_endpoint, $request_args );

		if ( is_wp_error( $response ) ) {
			$error_message = $response->get_error_message();
			$this->log_api_error( $body, array( 'code' => 0, 'body' => $error_message ), $error_message, 'wp_error' );

			return array(
				'success' => false,
				'error'   => $error_message,
			);
		}

		$response_code = wp_remote_retrieve_response_code( $response );
		$response_body = wp_remote_retrieve_body( $response );
		$data          = json_decode( $response_body, true );

		if ( 200 !== $response_code || ! is_array( $data ) ) {
			$error_message = is_array( $data ) ? ( $data['error']['message'] ?? __( 'Unknown API error', 'smartypress-ai-engine' ) ) : __( 'Invalid API response.', 'smartypress-ai-engine' );
			$error_code    = $this->get_error_code( $response_code, $error_message );

			$this->log_api_error(
				$body,
				array(
					'code' => $response_code,
					'body' => $response_body,
				),
				$error_message,
				$error_code
			);

			return array(
				'success'    => false,
				'error'      => $error_message,
				'error_code' => $error_code,
			);
		}

		$content        = $this->extract_text_content( $data['content'] ?? array() );
		$usage          = $this->normalize_anthropic_usage( $data['usage'] ?? array() );
		$response_model = sanitize_text_field( $data['model'] ?? ( $body['model'] ?? $this->model ) );

		if ( class_exists( 'SmartyPress_AI_Logger' ) ) {
			SmartyPress_AI_Logger::log_api_success(
				$this->get_provider_name(),
				$this->api_endpoint,
				$body,
				array(
					'code'    => $response_code,
					'content' => $content,
					'usage'   => $usage,
				)
			);
		}

		if ( class_exists( 'SmartyPress_AI_Token_Tracker' ) ) {
			SmartyPress_AI_Token_Tracker::record(
				array(
					'provider' => $this->get_provider_name(),
					'model'    => $response_model,
					'usage'    => $usage,
				)
			);
		}

		$result = array(
			'success'       => true,
			'content'       => $content,
			'usage'         => $usage,
			'finish_reason' => sanitize_text_field( $data['stop_reason'] ?? '' ),
			'model'         => $response_model,
		);

		if ( class_exists( 'SmartyPress_AI_Model_Pricing' ) ) {
			$cost = SmartyPress_AI_Model_Pricing::calculate_cost(
				$this->get_provider_name(),
				$response_model,
				(int) ( $usage['input_tokens'] ?? 0 ),
				(int) ( $usage['output_tokens'] ?? 0 ),
				$usage
			);
			$result['cost'] = $cost['cost'];
		}

		return $result;
	}

	/**
	 * Add proxy settings to request args.
	 *
	 * @param array $request_args Request args passed by reference.
	 * @return void
	 */
	private function apply_proxy_to_request_args( array &$request_args ) {
		if ( empty( $this->proxy_url ) ) {
			return;
		}

		$proxy_parts = wp_parse_url( $this->proxy_url );
		if ( ! $proxy_parts ) {
			return;
		}

		$proxy_host = $proxy_parts['host'] ?? '';
		$proxy_port = $proxy_parts['port'] ?? '';

		if ( ! $proxy_host || ! $proxy_port ) {
			return;
		}

		$request_args['proxy'] = $proxy_host . ':' . $proxy_port;

		if ( ! empty( $proxy_parts['user'] ) ) {
			$proxy_auth = $proxy_parts['user'];
			if ( ! empty( $proxy_parts['pass'] ) ) {
				$proxy_auth .= ':' . $proxy_parts['pass'];
			}
			$request_args['headers']['Proxy-Authorization'] = 'Basic ' . base64_encode( $proxy_auth ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode
		}
	}

	/**
	 * Normalize Anthropic usage for shared token tracker.
	 *
	 * @param array $usage Raw usage.
	 * @return array
	 */
	private function normalize_anthropic_usage( array $usage ) {
		$base_input           = absint( $usage['input_tokens'] ?? 0 );
		$cache_creation_input = absint( $usage['cache_creation_input_tokens'] ?? 0 );
		$cache_read_input     = absint( $usage['cache_read_input_tokens'] ?? 0 );
		$output               = absint( $usage['output_tokens'] ?? 0 );
		$total_input          = $base_input + $cache_creation_input + $cache_read_input;

		return array_merge(
			$usage,
			array(
				'input_tokens'  => $total_input,
				'output_tokens' => $output,
				'total_tokens'  => $total_input + $output,
			)
		);
	}

	/**
	 * Extract text blocks from Anthropic response content.
	 *
	 * @param array $content_blocks Response content blocks.
	 * @return string
	 */
	private function extract_text_content( array $content_blocks ) {
		$text_parts = array();

		foreach ( $content_blocks as $block ) {
			if ( is_array( $block ) && 'text' === ( $block['type'] ?? '' ) && isset( $block['text'] ) ) {
				$text_parts[] = (string) $block['text'];
			}
		}

		return trim( implode( "\n", $text_parts ) );
	}

	/**
	 * Determine whether current request expects JSON output.
	 *
	 * @param array $params Request params.
	 * @return bool
	 */
	private function is_json_response_requested( array $params ) {
		return isset( $params['response_format']['type'] ) && 'json_object' === $params['response_format']['type'];
	}

	/**
	 * Append strict JSON instruction for APIs without OpenAI response_format.
	 *
	 * @param string $system_prompt System prompt.
	 * @return string
	 */
	private function append_json_instruction( $system_prompt ) {
		$json_instruction = 'Return ONLY a valid JSON object. Do not use markdown, code fences, comments, explanations, or any text before or after the JSON object.';

		if ( false !== stripos( $system_prompt, 'valid JSON object' ) ) {
			return $system_prompt;
		}

		return trim( $system_prompt . ' ' . $json_instruction );
	}

	/**
	 * Map Anthropic/API errors to plugin error codes.
	 *
	 * @param int    $response_code HTTP response code.
	 * @param string $message       Error message.
	 * @return string
	 */
	private function get_error_code( $response_code, $message ) {
		if ( in_array( (int) $response_code, array( 401, 403 ), true )
			|| false !== stripos( $message, 'authentication' )
			|| false !== stripos( $message, 'api key' )
			|| false !== stripos( $message, 'unauthorized' )
		) {
			return 'api_key_invalid';
		}

		if ( 429 === (int) $response_code || false !== stripos( $message, 'rate limit' ) ) {
			return 'rate_limit';
		}

		if ( false !== stripos( $message, 'quota' ) || false !== stripos( $message, 'billing' ) || false !== stripos( $message, 'credit' ) ) {
			return 'quota_exceeded';
		}

		return '';
	}

	/**
	 * Log Anthropic API error when logging is enabled.
	 *
	 * @param array  $request       Request body.
	 * @param array  $response      Response metadata.
	 * @param string $error_message Error message.
	 * @param string $error_code    Error code.
	 * @return void
	 */
	private function log_api_error( array $request, array $response, $error_message, $error_code = '' ) {
		if ( ! class_exists( 'SmartyPress_AI_Logger' ) ) {
			return;
		}

		SmartyPress_AI_Logger::log_api_error(
			$this->get_provider_name(),
			$this->api_endpoint,
			$request,
			$response,
			$error_message,
			$error_code
		);
	}
}

