<?php
/**
 * Biscotti CMP API Client
 * 
 * Standardized communication with the Biscotti Cloud API.
 * All premium feature data is fetched dynamically from the API.
 * 
 * @package Biscotti_CMP
 * @since 3.0.0
 */

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

class Biscotti_API_Client
{
    /**
     * API base URL
     * @var string
     */
    private $api_url;

    /**
     * Website ID for authentication
     * @var string
     */
    private $website_id;

    /**
     * API key for authenticated requests
     * @var string
     */
    private $api_key;

    /**
     * Transient cache TTL (seconds)
     */
    const CACHE_TTL = 300; // 5 minutes

    /**
     * Constructor
     */
    public function __construct()
    {
        $options = get_option('biscotti_options', []);
        $this->website_id = $options['website_id'] ?? '';
        $this->api_key = $options['api_key'] ?? '';

        // Allow custom API URL for dev environments
        $custom_url = get_option('biscotti_cmp_api_url', '');
        if (!empty($custom_url)) {
            $this->api_url = rtrim($custom_url, '/');
        } else {
            $this->api_url = defined('BISCOTTI_API_URL')
                ? rtrim(BISCOTTI_API_URL, '/')
                : 'https://api.biscotti-cmp.com';
        }
    }

    /**
     * Check if the plugin is connected to the API
     * @return bool
     */
    public function is_connected()
    {
        return !empty($this->website_id);
    }

    /**
     * Get the current Website ID
     * @return string
     */
    public function get_website_id()
    {
        return $this->website_id;
    }

    /**
     * Build standard request headers
     * @return array
     */
    private function get_headers()
    {
        $headers = [
            'Content-Type'    => 'application/json',
            'X-Website-ID'    => $this->website_id,
            'Accept-Language' => $this->get_api_language(),
        ];

        if (!empty($this->api_key)) {
            $headers['X-API-Key'] = $this->api_key;
        }

        return $headers;
    }

    /**
     * Get plugin language code (2-letter ISO)
     * Uses Language Bridge for universal translation plugin support.
     * @return string
     */
    private function get_language()
    {
        if (class_exists('Biscotti_Language_Bridge')) {
            return Biscotti_Language_Bridge::get_current_language();
        }

        // Fallback if Language Bridge not loaded yet
        $options = get_option('biscotti_options', []);
        $lang = $options['default_language'] ?? '';

        if (empty($lang)) {
            $lang = substr(get_locale(), 0, 2);
        }

        return $lang ?: 'en';
    }

    /**
     * Precise locale for the API Accept-Language header.
     *
     * Unlike get_language() (which collapses to a 2-letter code for legacy
     * callers and the shortcode/banner text indexing), this PRESERVES the
     * canonical regional variants the Cloud localizes by — pt-BR, pt-PT, zh-TW —
     * so server-rendered content (compliance report, jurisdiction questions /
     * schema, banner texts) is returned in the exact locale instead of falling
     * back to English. The plugin's own Plugin-Language setting already stores
     * these precise codes (see settings.php).
     *
     * @return string e.g. 'de', 'pt-BR', 'zh-TW'
     */
    private function get_api_language()
    {
        $options = get_option('biscotti_options', []);
        $lang    = isset($options['default_language']) ? (string) $options['default_language'] : '';

        if ('' !== $lang && preg_match('/^[a-z]{2}(-[A-Za-z]{2})?$/i', $lang)) {
            // Normalise to canonical casing: base lower-case, region upper-case (pt-br -> pt-BR).
            if (false !== strpos($lang, '-')) {
                list($base, $region) = explode('-', $lang, 2);
                return strtolower($base) . '-' . strtoupper($region);
            }
            return strtolower($lang);
        }

        // No explicit Plugin-Language set → fall back to the 2-letter bridge code.
        return $this->get_language();
    }

    // =========================================================================
    // CORE API METHODS
    // =========================================================================

    /**
     * Network-related WP_Error codes that warrant a retry
     * @var array
     */
    private static $retryable_error_codes = [
        'http_request_failed',
        'http_failure',
        'connect_error',
        'timeout',
    ];

    /**
     * Determine if a WP_Error is a transient network failure worth retrying
     *
     * @param WP_Error $error
     * @return bool
     */
    private function is_retryable_error($error)
    {
        if (!is_wp_error($error)) {
            return false;
        }
        $code = $error->get_error_code();
        return in_array($code, self::$retryable_error_codes, true);
    }

    /**
     * GET request to the Biscotti Cloud API
     * 
     * @param string $endpoint Relative path (e.g., '/wp-plugin/features')
     * @param array  $args     Optional query parameters
     * @param bool   $cache    Whether to cache the response
     * @return array|WP_Error  Decoded response body or WP_Error
     */
    public function get($endpoint, $args = [], $cache = false)
    {
        $url = $this->api_url . '/api/v1' . $endpoint;

        if (!empty($args)) {
            $url = add_query_arg($args, $url);
        }

        // Check transient cache
        if ($cache) {
            $cache_key = 'biscotti_' . md5($url);
            $cached = get_transient($cache_key);
            if ($cached !== false) {
                return $cached;
            }
        }

        $response = wp_remote_get($url, [
            'headers' => $this->get_headers(),
            'timeout' => 15,
        ]);

        // Retry once on transient network failures (2s delay)
        if (is_wp_error($response) && $this->is_retryable_error($response)) {
            sleep(2);
            $response = wp_remote_get($url, [
                'headers' => $this->get_headers(),
                'timeout' => 15,
            ]);
        }

        if (is_wp_error($response)) {
            return $response;
        }

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

        if ($status >= 400) {
            return new \WP_Error(
                'api_error',
                $body['error']['message'] ?? __('API error', 'biscotti-cmp'),
                ['status' => $status, 'body' => $body]
            );
        }

        $result = $body['data'] ?? $body;

        // Store in transient cache
        if ($cache && !is_wp_error($result)) {
            set_transient($cache_key, $result, self::CACHE_TTL);
        }

        return $result;
    }

    /**
     * POST request to the Biscotti Cloud API
     * 
     * @param string $endpoint Relative path
     * @param array  $data     Body data (will be JSON-encoded)
     * @return array|WP_Error
     */
    public function post($endpoint, $data = [], $timeout = 30)
    {
        $url = $this->api_url . '/api/v1' . $endpoint;

        $response = wp_remote_post($url, [
            'headers' => $this->get_headers(),
            'body'    => wp_json_encode($data),
            'timeout' => $timeout,
        ]);

        // Retry once on transient network failures (2s delay)
        if (is_wp_error($response) && $this->is_retryable_error($response)) {
            sleep(2);
            $response = wp_remote_post($url, [
                'headers' => $this->get_headers(),
                'body'    => wp_json_encode($data),
                'timeout' => $timeout,
            ]);
        }

        if (is_wp_error($response)) {
            return $response;
        }

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

        if ($status >= 400) {
            return new \WP_Error(
                'api_error',
                $body['error']['message'] ?? __('API error', 'biscotti-cmp'),
                ['status' => $status, 'body' => $body]
            );
        }

        return $body['data'] ?? $body;
    }

    // =========================================================================
    // CLOUD-BRIDGE METHODS
    // =========================================================================

    /**
     * Heartbeat — verify connection and get account info
     * @return array|WP_Error
     */
    public function heartbeat()
    {
        return $this->get('/wp-plugin/heartbeat');
    }

    /**
     * Get feature registry for the current plan
     * Cached for 5 minutes.
     * @return array|WP_Error
     */
    public function get_features()
    {
        return $this->get('/wp-plugin/features', [], true);
    }

    /**
     * Get i18n translations for the plugin shell
     * Cached for 1 hour.
     * 
     * @param string $lang 2-letter language code
     * @return array|WP_Error
     */
    public function get_translations($lang = null)
    {
        if (!$lang) {
            $lang = $this->get_language();
        }
        return $this->get('/wp-plugin/translations/' . sanitize_key($lang), [], true);
    }

    /**
     * Proxy an action through the cloud API
     * 
     * @param string $action Action name
     * @param array  $data   Request body
     * @return array|WP_Error
     */
    public function proxy_action($action, $data = [])
    {
        // AI-backed actions can take well over the default 30s; give them more headroom.
        $slow_actions = array( 'generate-document' );
        if ( in_array( $action, $slow_actions, true ) ) {
            // Lift PHP execution cap so the longer cURL timeout can actually complete.
            if ( function_exists( 'set_time_limit' ) ) {
                @set_time_limit( 125 ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged, Squiz.PHP.DiscouragedFunctions.Discouraged -- AI document generation can exceed the default 30s cap; raising it is required and guarded by function_exists().
            }
            return $this->post('/wp-plugin/proxy/' . sanitize_key($action), $data, 120);
        }
        return $this->post('/wp-plugin/proxy/' . sanitize_key($action), $data, 30);
    }

    /**
     * Generic proxy to any API endpoint (for backward compatibility)
     * 
     * @param string $method   HTTP method (GET, POST, PUT, DELETE)
     * @param string $endpoint Full API path (e.g., '/api/v1/consent/...')
     * @param array  $body     Optional body data
     * @return array|WP_Error
     */
    public function proxy_raw($method, $endpoint, $body = null)
    {
        $url = $this->api_url . $endpoint;

        $args = [
            'method'  => strtoupper($method),
            'headers' => $this->get_headers(),
            'timeout' => 30,
        ];

        if ($body !== null) {
            $args['body'] = wp_json_encode($body);
        }

        $response = wp_remote_request($url, $args);

        if (is_wp_error($response)) {
            return $response;
        }

        $status = wp_remote_retrieve_response_code($response);
        $result = json_decode(wp_remote_retrieve_body($response), true);

        if ($status >= 400) {
            return new \WP_Error(
                'api_error',
                $result['error']['message'] ?? __('API error', 'biscotti-cmp'),
                ['status' => $status, 'body' => $result]
            );
        }

        return $result;
    }

    /**
     * Clear all cached API responses
     */
    public function clear_cache()
    {
        global $wpdb;
        // Get all biscotti transient keys, then delete them properly
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Transient cleanup requires direct query, no persistent cache needed
        $biscotti_transient_keys = $wpdb->get_col(
            $wpdb->prepare(
                "SELECT REPLACE(option_name, '_transient_', '') FROM {$wpdb->options} WHERE option_name LIKE %s",
                '_transient_biscotti_%'
            )
        );
        foreach ($biscotti_transient_keys as $biscotti_key) {
            delete_transient($biscotti_key);
        }
    }
}
