<?php
/**
 * Tamrank API Client
 *
 * Centrale communicatie met Tamrank SaaS backend.
 *
 * Belangrijke principes:
 * - UI GATING: Plugin mag features tonen/verbergen op basis van backend response
 * - ENFORCEMENT: Backend beslist altijd wat mag, plugin NOOIT
 * - CREDITS: Alleen read-only display, geen blocking logic
 * - CACHING: 15-30 min voor balance tussen performance en realtime
 *
 * @package TamRank
 * @since 8.0.0 (SaaS refactor)
 */

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

class TamRank_API_Client
{

    /**
     * API base URL
     * @var string
     */
    private $api_base_url = 'https://api.tamrank.com/v1';

    /**
     * Site API key
     * @var string|null
     */
    private $api_key = null;

    /**
     * Site ID
     * @var string|null
     */
    private $site_id = null;

    /**
     * Singleton instance
     * @var TamRank_API_Client|null
     */
    private static $instance = null;

    /**
     * Cache duration: 15 minuten
     * Korte cache zodat feature/usage updates snel doorwerken
     */
    const CACHE_DURATION = 900; // 15 * 60 seconden

    /**
     * Get singleton instance
     *
     * @return TamRank_API_Client
     */
    public static function get_instance()
    {
        if (null === self::$instance) {
            self::$instance = new self();
        }
        return self::$instance;
    }

    /**
     * Constructor
     */
    protected function __construct()
    {
        // Try new SaaS option names first (tam_rank_api_key from LicenseManager)
        // Then fallback to legacy option names (tamrank_api_key)
        $this->api_key = tam_rank_cached_option('tam_rank_api_key', '');
        if (empty($this->api_key)) {
            $this->api_key = tam_rank_cached_option('tamrank_api_key', '');
        }

        // For site_id, we don't need it in the new architecture
        // The API key alone is sufficient for authentication
        $this->site_id = get_option('tamrank_site_id');

        // Allow override via constant (voor development)
        if (defined('TAMRANK_API_URL')) {
            $this->api_base_url = TAMRANK_API_URL;
        }
    }

    // has_feature removed - Feature gating is now strictly local via FreemiumManager

    /**
     * Vraag indexatie aan bij backend
     *
     * @param string $url URL to index
     * @return array|\WP_Error Response
     */
    public function request_indexing($url)
    {
        return $this->request('POST', '/search-console/index-url', array(
            'url' => $url,
        ));
    }

    /**
     * Submit URLs to IndexNow via the TamRank API.
     *
     * Contract (plugin -> api.tamrank.com -> api.indexnow.org):
     *   POST /indexnow/submit  { host, key, keyLocation, urlList }
     *   200  { submitted, indexnow_status, message }
     *   4xx/5xx { error, message }
     *
     * Two things this method exists for, both of which the shared request()
     * mapping gets wrong for this endpoint:
     *
     * 1. Our 200 only says the TamRank API reached IndexNow. IndexNow's own
     *    verdict travels in `indexnow_status` and may well be 403 or 429 inside
     *    a 200. Callers must read that field, never the transport status —
     *    TamRank_IndexNow::interpret() is where that happens.
     * 2. A 401 from this path does NOT mean the api_key is invalid. The route
     *    is new; on an un-deployed VPS it falls into the WooCommerce HMAC
     *    catch-all on /v1 and answers
     *    `401 {"error":"unauthorized","message":"Missing X-Signature, ..."}`.
     *    request()'s generic 401 copy ("your license has expired") would push a
     *    user who merely flipped a toggle into reconnecting or re-registering
     *    their site. It is remapped here, and the licence options are left
     *    strictly alone.
     *
     * @param string $host         Host of this site, `www.` included (never stripped).
     * @param string $key          IndexNow site key.
     * @param string $key_location Absolute URL of the `<key>.txt` proof file.
     * @param array  $url_list     Absolute URLs on $host (max 10.000 per call).
     * @return array|\WP_Error Decoded body on success, WP_Error otherwise.
     */
    public function submit_indexnow($host, $key, $key_location, array $url_list)
    {
        // request() stamps this option with its own generic copy on failure.
        // Snapshot it so an IndexNow hiccup can never leave a licence-shaped
        // error behind in the site's API-health record.
        $error_before = get_option('tam_rank_last_api_error', null);

        $result = $this->request('POST', '/indexnow/submit', array(
            'host'        => $host,
            'key'         => $key,
            'keyLocation' => $key_location,
            'urlList'     => array_values($url_list),
        ));

        if (!is_wp_error($result)) {
            return is_array($result) ? $result : array();
        }

        if (null === $error_before) {
            delete_option('tam_rank_last_api_error');
        } else {
            update_option('tam_rank_last_api_error', $error_before);
        }

        $code = $result->get_error_code();

        // 'unauthorized' / 'forbidden' / 'api_error' on this route all mean the
        // same thing from the user's side: the IndexNow endpoint did not accept
        // the call. Nothing about their licence changed, and nothing here
        // touches tam_rank_api_key, tam_rank_plan or tam_rank_premium_active.
        if (in_array($code, array('unauthorized', 'forbidden', 'api_error', 'no_api_credentials'), true)) {
            return new \WP_Error(
                'indexnow_unavailable',
                tamrank__('indexnow_err_endpoint', 'IndexNow could not be reached right now. Your license and connection are unaffected; TamRank will try again on the next publish.')
            );
        }

        return new \WP_Error('indexnow_unavailable', $result->get_error_message());
    }

    /**
     * Invalideer status cache
     *
     * Gebruik na:
     * - License upgrade
     * - Feature aankoop
     * - Settings wijziging
     */
    public function invalidate_cache()
    {
        delete_transient('tamrank_site_status');
    }


    /**
     * Centrale HTTP request functie
     *
     * @param string $method HTTP method (GET, POST, PUT, DELETE)
     * @param string $endpoint API endpoint (bijv. '/meta/generate')
     * @param array $data Request data
     * @return array|\WP_Error Response data of error
     */
    protected function request($method, $endpoint, $data = array())
    {
        // Valideer API configuratie
        if (empty($this->api_key)) {
            return new \WP_Error(
                'no_api_credentials',
                tamrank__('error_api_key_not_configured', 'TamRank API key not configured. Go to Settings → TamRank.')
            );
        }

        // Bouw URL
        $url = trailingslashit($this->api_base_url) . ltrim($endpoint, '/');

        // Bereid headers voor - use Bearer token (new SaaS architecture)
        $headers = array(
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
            'Authorization' => 'Bearer ' . $this->api_key,
            'X-Site-URL' => home_url(),
            'X-Plugin-Version' => defined('TAM_RANK_VERSION') ? TAM_RANK_VERSION : '8.0.0',
        );

        // Add legacy headers for backwards compatibility (if site_id exists)
        if (!empty($this->site_id)) {
            $headers['X-API-Key'] = $this->api_key;
            $headers['X-Site-ID'] = $this->site_id;
        }

        // Bereid args voor
        $args = array(
            'method' => strtoupper($method),
            'timeout' => 30,
            'headers' => $headers,
            'sslverify' => true,
        );

        // Voeg body toe voor POST/PUT
        if (in_array($method, ['POST', 'PUT']) && !empty($data)) {
            $args['body'] = wp_json_encode($data);
        }

        // Voor GET: voeg data toe aan query string
        if ($method === 'GET' && !empty($data)) {
            $url = add_query_arg($data, $url);
        }

        // ✅ Voer request uit
        $response = wp_remote_request($url, $args);

        // Error handling
        if (is_wp_error($response)) {
            $error_msg = $response->get_error_message();

            update_option('tam_rank_last_api_error', array(
                'time' => current_time('mysql'),
                'message' => $error_msg,
                'code' => 'connection_error'
            ));

            return new \WP_Error(
                'api_connection_failed',
                tamrank__('error_api_connection_failed', 'Could not connect to TamRank API. Please try again later.')
            );
        }

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

        // Als succesvol (200, 201, 202), verwijder eventuele oude foutmelding
        // 200 = OK, 201 = Created, 202 = Accepted (voor async jobs)
        if ($status_code >= 200 && $status_code < 300) {
            delete_option('tam_rank_last_api_error');
            return $data;
        }

        // Backend enforcement via HTTP status codes
        $error_code = 'unknown_error';
        $user_message = tamrank__('error_unknown', 'Unknown error.');

        // Check for insufficient_credits — backend may use 402 or include error_code in body
        $body_error_code = $data['error_code'] ?? $data['code'] ?? '';
        if ($status_code === 402 || $body_error_code === 'insufficient_credits') {
            $error_code    = 'insufficient_credits';
            // Invalidate credits cache so UI shows 0 immediately.
            // MERGEN, niet overschrijven: LicenseManager::get_credits() schrijft
            // dezelfde optie met de volledige zes-velden-payload. Deze tweede
            // schrijver zou monthly_total/purchased/reserved anders wegvagen tot
            // de volgende verse fetch (tot 5 min later).
            $existing_cache = get_option('tam_rank_credits_cache', array());
            $existing_cache = is_array($existing_cache) ? $existing_cache : array();
            // Bewaar het echte maandtotaal uit de vorige cache; alleen
            // remaining/used zijn hier aantoonbaar op. Kennen we het totaal
            // niet, dan blijft het onbekend (null) — geen verzonnen getal.
            $known_total = null;
            foreach (array('total', 'monthly_total') as $total_key) {
                if (isset($existing_cache[$total_key]) && is_numeric($existing_cache[$total_key])) {
                    $known_total = (int) $existing_cache[$total_key];
                    break;
                }
            }
            update_option('tam_rank_credits_cache', array_merge($existing_cache, array(
                'remaining' => 0,
                // available = max(0, remaining - reserved), dus per definitie 0.
                // En omdat remaining het gekochte saldo al meetelt, betekent
                // remaining 0 dat ook de gekochte pot leeg is.
                'available' => 0,
                'purchased' => 0,
                'total' => $known_total,
                'used' => $known_total,
                'resets_at' => $existing_cache['resets_at'] ?? null,
                'fetched_at' => time(),
            )));
            $credits       = $this->get_credits_reset_date();
            $reset_part    = $credits
                ? sprintf(tamrank__('error_credits_reset_on', 'Your credits reset on %s.'), $credits)
                : tamrank__('error_credits_reset_monthly', 'Your credits reset at the start of next month.');
            $user_message  = tamrank__('error_credits_depleted', 'Your AI credits are used up for this month.') . ' ' . $reset_part;
        } elseif ($status_code === 401) {
            $error_code = 'unauthorized';
            $user_message = tamrank__('error_license_expired', 'Your license has expired or is invalid. Reactivate your license to continue.');
            // Only deactivate PRO if VPS explicitly says license is expired/invalid
            // (not for generic auth errors like malformed API key)
            if (in_array($body_error_code, array('license_expired', 'license_invalid', 'license_suspended'), true)) {
                update_option('tam_rank_premium_active', false);
            }
        } elseif ($status_code === 403) {
            $error_code = 'forbidden';
            $user_message = $data['error'] ?? tamrank__('error_no_access_feature', 'No access to this feature.');
        } elseif ($status_code === 429) {
            $error_code = 'rate_limit';
            $user_message = tamrank__('error_rate_limit', 'Rate limit reached. Please try again later.');
        } elseif ($status_code >= 500) {
            $error_code = 'server_error';
            $user_message = tamrank__('error_backend_api', 'Backend API error. Please contact support.');
        } else {
            $error_code = 'api_error';
            $user_message = $data['error'] ?? tamrank__('error_unknown', 'Unknown error.');
        }

        // Sla fout op voor debug indicator
        update_option('tam_rank_last_api_error', array(
            'time' => current_time('mysql'),
            'message' => $user_message,
            'code' => $status_code
        ));

        return new \WP_Error($error_code, $user_message);
    }

    /**
     * Get formatted credits reset date from cache
     *
     * @return string|null Formatted date or null if unavailable
     */
    private function get_credits_reset_date(): ?string
    {
        $cached = get_option('tam_rank_credits_cache', null);
        if (!$cached || empty($cached['resets_at'])) {
            return null;
        }
        $timestamp = strtotime($cached['resets_at']);
        if (!$timestamp) {
            return null;
        }
        return date_i18n(get_option('date_format'), $timestamp);
    }
}
