<?php

namespace Clonable\Helpers;

use Clonable\Objects\ClonableConfig;
use Exception;
use Throwable;

/**
 * Translate the href attributes of an array of language tags.
 * @param array $urls
 * @return array
 */

class UrlTranslator
{

    /**
     * @param string $haystack
     * @param string|array $needle
     * @return bool
     */

    public static function clonable_translate_hrefs($urls)
    {
        try {
            $transient_key = "clonable_url_translations_" . substr(hash("sha256", json_encode($urls)), 0, 16);
            $cached_response = get_transient($transient_key);

            // check if cache is empty/expired, or random cache refresh chance hits,
            // if one of these conditions fulfills, the cache gets filled with new data
            if ($cached_response === false) {
                $debounce_counter = intval(get_option("clonable_debounce_counter", 0));
                // if debounceCounter is null or 0, then there were no error requests, and you shouldn't debounce
                if ($debounce_counter !== 0) {
                    $sample = 100; // default 9/100
                    $sample -= $debounce_counter; // subtract debounceCounter from request chance percentage
                    $should_debounce = rand(1, $sample);
                    if ($should_debounce < 9) { // default debounce rate of 9% with a sample=100 and a maximum of 90% with sample=10
                        return $urls;
                    }
                }
                $cached_response = self::clonable_get_translated_urls($urls);
                if (empty($cached_response)) {
                    self::clonable_handle_debounce_counter(true);
                    return $urls;
                } else {
                    $jitter = rand(1, 3600);
                    set_transient($transient_key, $cached_response, (86400 + $jitter));
                    self::clonable_handle_debounce_counter(false);
                }
            }
            return $cached_response;
        } catch (Throwable $e) {
            // increase the debounceCounter when an error occurs during the url translation
            self::clonable_handle_debounce_counter(true);
            return $urls;
        }
    }

    private static function clonable_handle_debounce_counter($increment)
    {
        $debounce_counter = get_option('clonable_debounce_counter');
        if ($debounce_counter === false) {
            add_option('clonable_debounce_counter', 0);
            $debounce_counter = 0;
        } else {
            $debounce_counter = intval($debounce_counter);
        }

        if ($increment) {
            // maximum decrease amount of 90, so request are not completely impossible
            // this will result in a maximum debounce chance of 9/10
            if ($debounce_counter < 90) {
                // Add one (to prevent being stuck at 0) and multiply by 1.2, this reaches 90 in about 16 steps
                $debounce_counter = min(90, (int) round(($debounce_counter + 1) * 1.2));
                update_option("clonable_debounce_counter", $debounce_counter);
            }
        } else {
            if ($debounce_counter <= 0) {
                return;
            }
            if ($debounce_counter <= 1) { // if debounceCounter = 1, set it to 0 instead of dividing it by 2
                $debounce_counter = 0;
            } else {
                $debounce_counter = (int) round($debounce_counter / 2);
            }
            update_option("clonable_debounce_counter", $debounce_counter);
        }
    }

    /**
     * Retrieve the translations for the given language tags.
     * @param array $tags
     * @return mixed
     * @throws Exception
     */
    public static function clonable_get_translated_urls($tags)
    {
        // make sure only the url are send to the url translation api
        $urls = [];
        foreach ($tags as $tag) {
            $urls[] = $tag['o'];
        }

        $curl = curl_init();
        curl_setopt_array($curl, [
            CURLOPT_URL => "http://" . ClonableConfig::UT_API_ENDPOINT . '/bulk',
            CURLOPT_POST => true,
            CURLOPT_POSTFIELDS => json_encode($urls),
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_TIMEOUT_MS => 500,
            CURLOPT_CONNECTTIMEOUT_MS => 100,
            CURLOPT_USERAGENT => "Clonable Wordpress " . CLONABLE_VERSION . " (curl)",
        ]);
        curl_setopt(
            $curl,
            CURLOPT_HEADERFUNCTION,
            function ($curl, $header) use (&$headers) {
                $length = strlen($header);
                $header = explode(":", $header, 2);
                if (count($header) < 2) {
                    return $length;
                }

                $headers[strtolower(trim($header[0]))][] = trim($header[1]);

                return $length;
            }
        );

        // retrieve body from curl request
        $response = curl_exec($curl);
        if ($response !== false) {
            curl_close($curl); // make sure to close the request
            // you need to manually strip the header from the body response

            //retrieve and decode the x-signature header
            $signature = base64_decode($headers['x-signature'][0]);
            $public_key = self::clonable_get_public_key();

            // Fix corrupt public key
            if (Functions::str_starts_with($public_key, 'HTTP/')) {
                $public_key = self::clonable_get_public_key(true);
            }

            $ok = openssl_verify($response, $signature, $public_key, OPENSSL_ALGO_SHA256);
            if ($ok === 1) { // if verification is successful, return the result
                return json_decode($response, true);
            } else { // else return the original request
                throw new Exception("Failed to verify response from Clonable API while translating URLS.");
            }
        } else {
            $error = curl_error($curl);
            curl_close($curl);
            throw new Exception("Failed to fetch url translations from Clonable API: $error");
        }
    }

    public static function clonable_get_full_url($site)
    {
        $domain = $site->domain;
        
        // Use fallback '/' for original site data, since there is no notion of subfolders there
        $original_subfolder = ($site->original_subfolder ?? '/');
        $clone_subfolder = ($site->clone_subfolder ?? '/');

        if (empty($_SERVER)) {
            return $domain;
        }
        $server_data = wp_unslash($_SERVER);
        if (!isset($server_data["REQUEST_URI"])) {
            return $domain;
        }

        $url_parts = parse_url($server_data["REQUEST_URI"]);
        $path = $url_parts["path"] ?? '';

        // Check whether the language tag ends with an / when not on the homepage
        if (!($path === '' || $path === '/') && !Functions::str_ends_with($clone_subfolder, '/')) {
            $clone_subfolder .= '/';
        }

        // Swap subfolder if necessary
        if (Functions::str_starts_with($path, $original_subfolder)) {
            $path = substr($path, strlen($original_subfolder));
            $path = $clone_subfolder . $path;
        }

        return "https://$domain$path";
    }

    /**
     * Gets the public openssl key for the url translation api
     * @throws Exception
     */
    public static function clonable_get_public_key($reset = false)
    {
        $public_key = get_option("clonable_public_key");
        if ($public_key === false || $reset) { // check if the public key is set
            $curl = curl_init();
            curl_setopt_array($curl, [
                CURLOPT_URL => "https://" . ClonableConfig::UT_API_ENDPOINT . "/public-key",
                CURLOPT_RETURNTRANSFER => true,
                CURLOPT_HTTPHEADER => array("Accept: text/plain"),
                CURLOPT_TIMEOUT => 5,
                CURLOPT_USERAGENT => "Clonable Wordpress " . CLONABLE_VERSION . " (curl)",
            ]);
            // retrieve body from curl request
            $public_key = curl_exec($curl);
            if ($public_key === false) {
                $error = curl_error($curl);
                curl_close($curl);
                throw new Exception($error);
            }

            curl_close($curl);
            update_option("clonable_public_key", $public_key, true);
        }

        return $public_key;
    }
}
