<?php
/**
 * Claude API Client
 *
 * Handles communication with the Anthropic Claude API.
 *
 * @package BoostBot
 * @since 1.0.0
 */

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

/**
 * Class BoostBot_Claude_Client
 *
 * HTTP client for Claude API interactions.
 */
class BoostBot_Claude_Client {

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

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

    /**
     * Default model to use.
     *
     * @var string
     */
    private $model = 'claude-sonnet-4-5-20250929';

    /**
     * Maximum retries for failed requests.
     *
     * @var int
     */
    private $max_retries = 3;

    /**
     * Request timeout in seconds.
     *
     * @var int
     */
    private $timeout = 60;

    /**
     * Constructor.
     *
     * @param string|null $api_key Optional API key. If not provided, uses settings.
     * @throws Exception If API key is not configured.
     */
    public function __construct( $api_key = null ) {
        $this->api_key = $api_key ?: BoostBot_Settings::get( 'api_key' );

        BoostBot_Logger::debug( 'Claude client init', array(
            'has_api_key' => ! empty( $this->api_key ),
            'key_length'  => strlen( $this->api_key ?? '' ),
            'key_prefix'  => substr( $this->api_key ?? '', 0, 10 ),
        ) );

        if ( empty( $this->api_key ) ) {
            throw new Exception( esc_html( __( 'Claude API key not configured', 'boostbot' ) ) );
        }

        // Get model from settings if available
        $configured_model = BoostBot_Settings::get( 'claude_model' );
        if ( ! empty( $configured_model ) ) {
            $this->model = $configured_model;
        }
    }

    /**
     * Send a message to Claude.
     *
     * @param array $messages Array of message objects.
     * @param array $options  Optional parameters (model, max_tokens, system, temperature).
     * @return array Response with content, usage, and stop_reason.
     * @throws Exception On API error.
     */
    public function send_message( array $messages, array $options = array() ) {
        $model_used = $options['model'] ?? $this->model;
        
        // Log with INFO level
        BoostBot_Logger::info( '🤖 Claude API Request - Model: ' . $model_used, array(
            'messages_count' => count( $messages ),
            'model'          => $model_used,
        ) );
        
        BoostBot_Logger::info( '[Claude] 🤖 API Request - Model: ' . $model_used );

        // Build payload
        $payload = array(
            'model'      => $options['model'] ?? $this->model,
            'max_tokens' => $options['max_tokens'] ?? 1024,
            'messages'   => $messages,
        );

        // Add system prompt if provided
        if ( ! empty( $options['system'] ) ) {
            $payload['system'] = $options['system'];
        }

        // Add temperature if specified
        if ( isset( $options['temperature'] ) ) {
            $payload['temperature'] = (float) $options['temperature'];
        }

        // Make the request
        $response = $this->make_request( $payload );

        // Log successful response
        $total_tokens = ( $response['usage']['input_tokens'] ?? 0 ) + ( $response['usage']['output_tokens'] ?? 0 );
        BoostBot_Logger::info( '✅ Claude API Response - Model: ' . $model_used . ' | Tokens: ' . $total_tokens, array(
            'model'         => $model_used,
            'input_tokens'  => $response['usage']['input_tokens'] ?? 0,
            'output_tokens' => $response['usage']['output_tokens'] ?? 0,
            'stop_reason'   => $response['stop_reason'] ?? 'unknown',
        ) );
        
        BoostBot_Logger::info( '[Claude] ✅ API Response - Model: ' . $model_used . ' | Tokens: ' . $total_tokens );

        // Track usage (legacy tracker)
        if ( class_exists( 'BoostBot_Usage_Tracker' ) ) {
            BoostBot_Usage_Tracker::increment( 'messages' );
        }

        return $response;
    }

    /**
     * Make HTTP request to Claude API.
     *
     * @param array $payload Request payload.
     * @param int   $attempt Current attempt number.
     * @return array Parsed response.
     * @throws Exception On API error.
     */
    private function make_request( array $payload, int $attempt = 1 ) {
        BoostBot_Logger::info( '[Claude] 📤 Starting API request - Attempt: ' . $attempt );
        BoostBot_Logger::info( '[Claude] 🎯 Model: ' . ( $payload['model'] ?? 'unknown' ) );
        BoostBot_Logger::info( '[Claude] 🌐 URL: ' . $this->api_url );
        
        try {
            $response = wp_remote_post( $this->api_url, array(
                'timeout' => $this->timeout,
                'headers' => array(
                    'Content-Type'      => 'application/json',
                    'x-api-key'         => $this->api_key,
                    'anthropic-version' => '2023-06-01',
                ),
                'body'    => wp_json_encode( $payload ),
            ) );

            BoostBot_Logger::info( '[Claude] 📥 Got response from API' );

            if ( is_wp_error( $response ) ) {
                BoostBot_Logger::error( '[Claude] ❌ WP Error: ' . $response->get_error_message() );
                throw new Exception( $response->get_error_message() );
            }

            $body = json_decode( wp_remote_retrieve_body( $response ), true );
            $code = wp_remote_retrieve_response_code( $response );
            
            BoostBot_Logger::info( '[Claude] 📊 Response code: ' . $code );

            BoostBot_Logger::debug( 'Claude API response', array(
                'status'      => $code,
                'has_content' => isset( $body['content'] ),
                'body_keys'   => is_array( $body ) ? array_keys( $body ) : 'not_array',
            ) );

            // Handle errors
            if ( $code >= 400 ) {
                $error_message = $body['error']['message'] ?? __( 'API Error', 'boostbot' );
                $error_type    = $body['error']['type'] ?? 'unknown';

                BoostBot_Logger::error( '[Claude] ❌ API Error ' . $code . ': ' . $error_type . ' - ' . $error_message );
                
                BoostBot_Logger::error( 'Claude API error response', array(
                    'code'    => $code,
                    'type'    => $error_type,
                    'message' => $error_message,
                ) );

                throw new Exception( $error_message, $code );
            }

            // Parse successful response
            return array(
                'content'     => $body['content'][0]['text'] ?? '',
                'usage'       => $body['usage'] ?? array(),
                'stop_reason' => $body['stop_reason'] ?? 'unknown',
            );

        } catch ( Exception $e ) {
            BoostBot_Logger::error( '[Claude] ❌ Exception in make_request: ' . $e->getMessage() . ' (code: ' . $e->getCode() . ', attempt: ' . $attempt . ')' );
            
            BoostBot_Logger::error( 'Claude API error', array(
                'error'   => $e->getMessage(),
                'code'    => $e->getCode(),
                'attempt' => $attempt,
            ) );

            // Retry logic
            if ( $attempt < $this->max_retries && $this->is_retryable( $e ) ) {
                // Bounded backoff (1s, 2s) — never block the request path for long.
                sleep( min( $attempt, 2 ) );
                return $this->make_request( $payload, $attempt + 1 );
            }

            throw $e;
        }
    }

    /**
     * Check if an error is retryable.
     *
     * @param Exception $exception The exception.
     * @return bool True if should retry.
     */
    private function is_retryable( Exception $exception ): bool {
        $code = $exception->getCode();
        // Retry on transport failures (code 0, e.g. a WP_Error with no HTTP
        // status), rate limit (429), or server errors (5xx).
        return $code === 0 || $code === 429 || $code >= 500;
    }

    /**
     * Validate an API key.
     *
     * Uses the /v1/models endpoint which:
     * - Doesn't consume any tokens (free)
     * - Doesn't depend on any specific model
     * - Will always work as long as the key is valid
     *
     * @param string $key The API key to validate.
     * @return bool True if valid.
     */
    public static function validate_api_key( string $key ): bool {
        try {
            // Use /v1/models endpoint - it's free and model-independent
            $response = wp_remote_get( 'https://api.anthropic.com/v1/models', array(
                'timeout' => 15,
                'headers' => array(
                    'x-api-key'         => $key,
                    'anthropic-version' => '2023-06-01',
                    'anthropic-beta'    => 'models-2025-01-01',
                ),
            ) );

            if ( is_wp_error( $response ) ) {
                BoostBot_Logger::error( 'API key validation failed - connection error', array(
                    'error' => $response->get_error_message(),
                ) );
                return false;
            }

            $code = wp_remote_retrieve_response_code( $response );
            
            // 200 = valid key, 401 = invalid key
            if ( $code === 200 ) {
                BoostBot_Logger::info( 'API key validated successfully' );
                return true;
            }

            BoostBot_Logger::error( 'API key validation failed', array(
                'http_code' => $code,
                'response'  => wp_remote_retrieve_body( $response ),
            ) );
            return false;

        } catch ( Exception $e ) {
            BoostBot_Logger::error( 'API key validation exception', array(
                'error' => $e->getMessage(),
            ) );
            return false;
        }
    }

    /**
     * Get available Claude models from API.
     *
     * @param bool $force_refresh Force refresh from API instead of cache.
     * @return array List of available models.
     */
    public static function get_available_models( bool $force_refresh = false ): array {
        $cache_key = 'boostbot_claude_models';
        $cache_duration = 24 * HOUR_IN_SECONDS; // Cache for 24 hours

        // Check cache first (unless force refresh)
        if ( ! $force_refresh ) {
            $cached = get_transient( $cache_key );
            if ( false !== $cached && is_array( $cached ) ) {
                return $cached;
            }
        }

        // Try to fetch from API
        $api_key = BoostBot_Settings::get( 'api_key' );
        if ( empty( $api_key ) ) {
            return self::get_fallback_models();
        }

        try {
            $response = wp_remote_get( 'https://api.anthropic.com/v1/models', array(
                'timeout' => 15,
                'headers' => array(
                    'x-api-key'         => $api_key,
                    'anthropic-version' => '2023-06-01',
                    'anthropic-beta'    => 'models-2025-01-01',
                ),
            ) );

            if ( is_wp_error( $response ) ) {
                BoostBot_Logger::warning( '[Claude] Failed to fetch models: ' . $response->get_error_message() );
                return self::get_fallback_models();
            }

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

            if ( $code !== 200 || empty( $body['data'] ) ) {
                BoostBot_Logger::warning( '[Claude] Models API returned error: ' . $code );
                return self::get_fallback_models();
            }

            // Parse models from API response
            $models = self::parse_models_response( $body['data'] );

            // Cache the result
            set_transient( $cache_key, $models, $cache_duration );

            BoostBot_Logger::info( '[Claude] ✓ Fetched ' . count( $models ) . ' models from Anthropic API' );

            return $models;

        } catch ( Exception $e ) {
            BoostBot_Logger::warning( '[Claude] Exception fetching models: ' . $e->getMessage() );
            return self::get_fallback_models();
        }
    }

    /**
     * Parse models response from Anthropic API.
     *
     * @param array $data Raw models data from API.
     * @return array Parsed models array.
     */
    private static function parse_models_response( array $data ): array {
        $models = array();

        foreach ( $data as $model ) {
            $id = $model['id'] ?? '';
            if ( empty( $id ) ) {
                continue;
            }

            // Extract display name
            $display_name = $model['display_name'] ?? $id;

            // Determine category and pricing
            $category = 'other';
            $input_price = 0;
            $output_price = 0;

            if ( strpos( $id, 'claude-3-5-sonnet' ) !== false || strpos( $id, 'claude-sonnet-4' ) !== false ) {
                $category = 'sonnet';
                $input_price = 3;
                $output_price = 15;
            } elseif ( strpos( $id, 'claude-3-5-haiku' ) !== false || strpos( $id, 'claude-haiku' ) !== false ) {
                $category = 'haiku';
                $input_price = 1;
                $output_price = 5;
            } elseif ( strpos( $id, 'opus' ) !== false ) {
                $category = 'opus';
                $input_price = 15;
                $output_price = 75;
            }

            $models[ $id ] = array(
                'id'           => $id,
                'display_name' => $display_name,
                'category'     => $category,
                'input_price'  => $input_price,
                'output_price' => $output_price,
                'created_at'   => $model['created_at'] ?? null,
            );
        }

        // Sort by category priority (sonnet first, then haiku, then opus, then other)
        uasort( $models, function( $a, $b ) {
            $priority = array( 'sonnet' => 1, 'haiku' => 2, 'opus' => 3, 'other' => 4 );
            $a_priority = $priority[ $a['category'] ] ?? 4;
            $b_priority = $priority[ $b['category'] ] ?? 4;
            
            if ( $a_priority === $b_priority ) {
                // Within same category, prefer 'latest' aliases
                $a_latest = strpos( $a['id'], 'latest' ) !== false ? 0 : 1;
                $b_latest = strpos( $b['id'], 'latest' ) !== false ? 0 : 1;
                return $a_latest - $b_latest;
            }
            
            return $a_priority - $b_priority;
        } );

        return $models;
    }

    /**
     * Get fallback models when API is unavailable.
     *
     * @return array Fallback models list.
     */
    private static function get_fallback_models(): array {
        return array(
            'claude-3-5-sonnet-latest' => array(
                'id'           => 'claude-3-5-sonnet-latest',
                'display_name' => 'Claude 3.5 Sonnet (Latest)',
                'category'     => 'sonnet',
                'input_price'  => 3,
                'output_price' => 15,
            ),
            'claude-3-5-haiku-latest' => array(
                'id'           => 'claude-3-5-haiku-latest',
                'display_name' => 'Claude 3.5 Haiku (Latest)',
                'category'     => 'haiku',
                'input_price'  => 1,
                'output_price' => 5,
            ),
            'claude-3-opus-latest' => array(
                'id'           => 'claude-3-opus-latest',
                'display_name' => 'Claude 3 Opus (Latest)',
                'category'     => 'opus',
                'input_price'  => 15,
                'output_price' => 75,
            ),
        );
    }

    /**
     * Clear models cache.
     *
     * @return bool True if cache was cleared.
     */
    public static function clear_models_cache(): bool {
        return delete_transient( 'boostbot_claude_models' );
    }

    /**
     * Get the current model.
     *
     * @return string Current model identifier.
     */
    public function get_model(): string {
        return $this->model;
    }

    /**
     * Set the model to use.
     *
     * @param string $model Model identifier.
     * @return void
     */
    public function set_model( string $model ): void {
        $this->model = $model;
    }
}

