<?php
/**
 * Claude API Client
 *
 * Implements AIClientInterface for Anthropic's Claude API.
 *
 * @package AIPT\Api\Claude
 * @since 2.4.0
 */

namespace AIPT\Api\Claude;

use AIPT\Api\AIClientInterface;
use AIPT\Core\ErrorHandler;
use AIPT\Utils\PromptFormatter;

/**
 * Claude API client implementation.
 */
class ClaudeClient implements AIClientInterface {

    /**
     * Claude API base URL.
     *
     * @var string
     */
    private $api_url = 'https://api.anthropic.com/v1/messages';

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

    /**
     * Available Claude models with configurations.
     *
     * @var array
     */
    private $models = [];

    /**
     * Anthropic API version header.
     *
     * @var string
     */
    private $anthropic_version = '2023-06-01';

    /**
     * Constructor.
     *
     * @param string $api_key Claude API key.
     */
    public function __construct( string $api_key ) {
        $this->api_key = $api_key;
        $this->initializeModels();
    }

    /**
     * Initialize available Claude models with their configurations.
     *
     * @return void
     */
    private function initializeModels(): void {
        $this->models = [

            'claude-opus-4-6' => [
                'name'                 => 'Claude Opus 4.6',
                'max_tokens'           => 8192,
                'temperature'          => 0.7,
                'has_temperature'      => true,
                'supports_system'      => true,
            ],
            'claude-sonnet-4-6' => [
                'name'                 => 'Claude Sonnet 4.6',
                'max_tokens'           => 8192,
                'temperature'          => 0.7,
                'has_temperature'      => true,
                'supports_system'      => true,
            ],

            'claude-opus-4-5-20251101' => [
                'name'                 => 'Claude Opus 4.5',
                'max_tokens'           => 8192,
                'temperature'          => 0.7,
                'has_temperature'      => true,
                'supports_system'      => true,
            ],
            'claude-sonnet-4-5-20250929' => [
                'name'                 => 'Claude Sonnet 4.5',
                'max_tokens'           => 8192,
                'temperature'          => 0.7,
                'has_temperature'      => true,
                'supports_system'      => true,
            ],
            'claude-haiku-4-5-20251001' => [
                'name'                 => 'Claude Haiku 4.5',
                'max_tokens'           => 8192,
                'temperature'          => 0.7,
                'has_temperature'      => true,
                'supports_system'      => true,
            ],

            'claude-sonnet-4-20250514' => [
                'name'                 => 'Claude Sonnet 4',
                'max_tokens'           => 8192,
                'temperature'          => 0.7,
                'has_temperature'      => true,
                'supports_system'      => true,
            ],
        ];
    }

    /**
     * Set parameters for a specific model.
     *
     * @param string $model      Model identifier.
     * @param array  $parameters Parameters to set.
     * @return void
     */
    public function setParameters( string $model, array $parameters ): void {
        if ( isset( $this->models[ $model ] ) ) {
            $this->models[ $model ] = array_merge( $this->models[ $model ], $parameters );
        }
    }

    /**
     * Get model configuration.
     *
     * @param string $model Model identifier.
     * @return array Model configuration or default.
     */
    private function getModelConfig( string $model ): array {
        return $this->models[ $model ] ?? [
            'name'            => $model,
            'max_tokens'      => 4096,
            'temperature'     => 0.7,
            'has_temperature' => true,
            'supports_system' => true,
        ];
    }

    /**
     * Sanitize prompt content.
     *
     * @param string $content Content to sanitize.
     * @return string Sanitized content.
     */
    private function sanitize_prompt_content( string $content ): string {
        return PromptFormatter::cleanContent( $content );
    }

    /**
     * Generate description content using Claude API.
     *
     * @param string   $system_prompt   System instructions.
     * @param string   $user_prompt     User message/prompt.
     * @param string   $model           Model to use.
     * @param int|null $maximum_length  Maximum response length.
     * @return string Generated content or error message.
     */
    public function generate_description( string $system_prompt, string $user_prompt, string $model = '', ?int $maximum_length = null ): string {
        if ( empty( $model ) ) {
            $model = 'claude-sonnet-4-6';
        }

        $model_config = $this->getModelConfig( $model );

        $body = [
            'model'      => $model,
            'max_tokens' => $maximum_length ?? $model_config['max_tokens'],
            'messages'   => [
                [
                    'role'    => 'user',
                    'content' => $this->sanitize_prompt_content( $user_prompt ),
                ],
            ],
        ];

        if ( ! empty( $system_prompt ) && $model_config['supports_system'] ) {
            $body['system'] = $this->sanitize_prompt_content( $system_prompt );
        }

        if ( $model_config['has_temperature'] ) {
            $body['temperature'] = $model_config['temperature'];
        }

        $response = wp_remote_post(
            $this->api_url,
            [
                'headers' => [
                    'Content-Type'      => 'application/json',
                    'x-api-key'         => $this->api_key,
                    'anthropic-version' => $this->anthropic_version,
                ],
                'body'    => wp_json_encode( $body ),
                'timeout' => 120,
            ]
        );

        return $this->handle_response( $response, 'description' );
    }

    /**
     * Generate FAQ content using Claude API.
     *
     * @param string      $system_prompt System instructions.
     * @param string      $user_prompt   User message/prompt.
     * @param string|null $model         Model to use.
     * @return string Generated content or error message.
     */
    public function generate_faq_content( string $system_prompt, string $user_prompt, ?string $model = null ): string {
        if ( empty( $model ) ) {
            $model = 'claude-sonnet-4-6';
        }

        $model_config = $this->getModelConfig( $model );

        $body = [
            'model'      => $model,
            'max_tokens' => $model_config['max_tokens'],
            'messages'   => [
                [
                    'role'    => 'user',
                    'content' => $this->sanitize_prompt_content( $user_prompt ),
                ],
            ],
        ];

        if ( ! empty( $system_prompt ) && $model_config['supports_system'] ) {
            $body['system'] = $this->sanitize_prompt_content( $system_prompt );
        }

        if ( $model_config['has_temperature'] ) {
            $body['temperature'] = $model_config['temperature'];
        }

        $response = wp_remote_post(
            $this->api_url,
            [
                'headers' => [
                    'Content-Type'      => 'application/json',
                    'x-api-key'         => $this->api_key,
                    'anthropic-version' => $this->anthropic_version,
                ],
                'body'    => wp_json_encode( $body ),
                'timeout' => 120,
            ]
        );

        return $this->handle_response( $response, 'faq' );
    }

    /**
     * Handle API response.
     *
     * @param array|\WP_Error $response     API response.
     * @param string          $content_type Type of content being generated.
     * @return string Extracted content or error message.
     */
    private function handle_response( $response, string $content_type ): string {
        if ( is_wp_error( $response ) ) {

            throw new \Exception(
                ErrorHandler::format_api_response_error(
                    $response->get_error_message(),
                    'claude',
                    0
                )
            );

        }

        $response_code = (int) wp_remote_retrieve_response_code( $response );
        $body          = json_decode( (string) wp_remote_retrieve_body( $response ), true );

        if ( $response_code !== 200 ) {

            if ( isset( $body['request_id'] ) && is_string( $body['request_id'] ) ) {
                ErrorHandler::log_error(
                    sprintf( 'Claude request_id=%s status=%d', sanitize_text_field( $body['request_id'] ), $response_code ),
                    'debug'
                );
            }

            $error_payload = $this->parse_error_response( $body, $response_code );

            throw new \Exception(
                ErrorHandler::format_api_response_error(
                    $error_payload,
                    'claude',
                    $response_code
                )
            );

        }

        if ( isset( $body['content'][0]['text'] ) ) {
            return $body['content'][0]['text'];
        }

        throw new \Exception(
            ErrorHandler::format_api_response_error(
                __( 'Unexpected response format from Claude API', 'ai-product-tools' ),
                'claude',
                0
            )
        );

    }

    /**
     * Parse Claude error response into a structured payload.
     *
     * When Claude returns a structured error body, the full error array is returned
     * so downstream ErrorHandler can surface both type and message. Falls back to a
     * hardcoded HTTP-status-to-message map when no structured error is present.
     *
     * @param array|null $body          Response body.
     * @param int        $response_code HTTP response code.
     * @return array|string Structured error array when available, mapped string otherwise.
     */
    private function parse_error_response( ?array $body, int $response_code ) {

        if ( is_array( $body ) && isset( $body['error'] ) && is_array( $body['error'] ) && isset( $body['error']['message'] ) ) {

            $body['error']['message'] = ErrorHandler::sanitize_error_for_display( $body['error']['message'] );
            return $body['error'];
        }

        $error_messages = [
            400 => __( 'Invalid request to Claude API', 'ai-product-tools' ),
            401 => __( 'Invalid Claude API key', 'ai-product-tools' ),
            403 => __( 'Permission denied for Claude API', 'ai-product-tools' ),
            404 => __( 'Claude API endpoint not found', 'ai-product-tools' ),
            429 => __( 'Claude API rate limit exceeded. Please try again later.', 'ai-product-tools' ),
            500 => __( 'Claude API internal error', 'ai-product-tools' ),
            529 => __( 'Claude API is temporarily overloaded. Please try again later.', 'ai-product-tools' ),
        ];

        return $error_messages[ $response_code ] 
            ?? sprintf(
                /* translators: %d: HTTP status code */
                __( 'Claude API error (HTTP %d)', 'ai-product-tools' ),
                $response_code
            );
    }

	/**
	 * Validate Claude API key.
	 *
	 * @param string $api_key API key to validate.
	 * @return bool True if valid.
	 * @throws \Exception If API key is invalid or validation fails.
	 */
	public static function validate_api_key( string $api_key ): bool {
		if ( empty( $api_key ) ) {

			throw new \Exception( __( 'Claude API key cannot be empty.', 'ai-product-tools' ) );
		}

		if ( strpos( $api_key, 'sk-ant-' ) !== 0 ) {

			throw new \Exception( __( 'Invalid Claude API key format. Key should start with "sk-ant-".', 'ai-product-tools' ) );
		}

		$response = wp_remote_get(
			'https://api.anthropic.com/v1/models',
			[
				'headers' => [
					'x-api-key'         => $api_key,
					'anthropic-version' => '2023-06-01',
				],
				'timeout' => 20,
			]
		);

		if ( is_wp_error( $response ) ) {
			/** @var \WP_Error $response */

			throw new \Exception(
				sprintf(
					/* translators: %s: Error message from API request */
					__( 'Claude API request failed: %s', 'ai-product-tools' ),
					$response->get_error_message()
				)
			);

		}

		$response_code = wp_remote_retrieve_response_code( $response );
		$body_str      = (string) wp_remote_retrieve_body( $response );
		$body          = json_decode( $body_str, true );

		if ( json_last_error() !== JSON_ERROR_NONE || ! is_array( $body ) ) {

			throw new \Exception(
				sprintf(
					/* translators: %s: JSON error message */
					__( 'Invalid response from Claude API: %s', 'ai-product-tools' ),
					json_last_error_msg()
				)
			);

		}

		if ( $response_code === 200 ) {
			return true;
		}

		if ( $response_code === 401 ) {
			$error_msg = isset( $body['error']['message'] ) ? $body['error']['message'] : __( 'Invalid Claude API key', 'ai-product-tools' );

			throw new \Exception( $error_msg );
		}

		if ( $response_code === 403 ) {

			throw new \Exception( __( 'Permission denied for Claude API. Please check your API key permissions.', 'ai-product-tools' ) );
		}

		if ( $response_code === 429 ) {

			throw new \Exception( __( 'Claude API rate limit exceeded. Please try again later.', 'ai-product-tools' ) );
		}

		$error_msg = isset( $body['error']['message'] ) ? $body['error']['message'] : sprintf(
			/* translators: %d: HTTP status code */
			__( 'Claude API error (HTTP %d)', 'ai-product-tools' ),
			$response_code
		);

		throw new \Exception( $error_msg );
	}

    /**
     * Get available Claude models.
     *
     * @return array List of available models with their display names.
     */
    public function get_available_models(): array {
        $available_models = [];

        foreach ( $this->models as $model_id => $config ) {
            $available_models[ $model_id ] = $config['name'];
        }

        return $available_models;
    }
}
