<?php
/**
 * Biscotti CMP Banner
 *
 * Handles frontend consent banner injection via wp_head.
 * Loads the Biscotti CMP script (biscotti.min.js) from the cloud, configured via the Website ID.
 *
 * @package Biscotti_CMP
 * @since 3.0.0
 */

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

class Biscotti_Banner
{
    /**
     * @var Biscotti_API_Client
     */
    private $api;

    /**
     * Constructor
     * @param Biscotti_API_Client $api
     */
    public function __construct($api)
    {
        $this->api = $api;

        add_action('wp_enqueue_scripts', [$this, 'enqueue_frontend']);
        // Boot blob (Pre-Boot Shield + TCF stub) MUST be the very first script
        // in <head>. wp_head priority 0 runs BEFORE wp_print_scripts (default 9),
        // so this fires before any other script we or anyone else enqueues.
        add_action('wp_head', [$this, 'inject_boot_blob'], 0);
        add_action('wp_head', [$this, 'inject_consent_script'], 1);
    }

    /**
     * Inject the Pre-Boot Shield + TCF stub INLINE in <head> as the very
     * first <script>. Required for pre-consent blocking of inline tracking
     * pixels (Meta, Google, LinkedIn, …) — external <script src="..."> is too
     * late because the HTML parser preload speculation already dispatched
     * sibling tracker requests.
     *
     * The blob is auto-generated from biscotti-shield.js + cmp-stub.js by
     * scripts/build-cmp-stub-inline.js and shipped as assets/js/biscotti-boot.js.
     *
     * @since 4.0.1
     */
    public function inject_boot_blob()
    {
        $options = get_option('biscotti_options', []);

        if (empty($options['banner_enabled'])) {
            return;
        }
        if (empty($options['website_id'])) {
            return;
        }

        $boot_path = BISCOTTI_PLUGIN_DIR . 'assets/js/biscotti-boot.js';
        if (!file_exists($boot_path)) {
            return; // Asset missing — fail soft (Shield missing is a degradation, not a fatal)
        }

        $boot_code = file_get_contents($boot_path);
        if (empty($boot_code)) {
            return;
        }

        // Output as inline script. NOT via wp_add_inline_script — that hangs
        // the script onto another handle which is printed later. We need
        // this RIGHT HERE in <head> as the literal first script.
        // wp_kses-safe: this is verified static asset content shipped with
        // the plugin, not user input.
        echo '<!-- Biscotti Pre-Boot Shield (auto-generated) -->' . "\n";
        // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- static plugin asset, not user input
        echo '<script>' . $boot_code . '</script>' . "\n";
    }

    /**
     * Enqueue frontend styles (only floating button CSS)
     */
    public function enqueue_frontend()
    {
        // Never load frontend CSS on admin pages (C11 fix)
        if (is_admin()) {
            return;
        }

        $options = get_option('biscotti_options', []);

        if (empty($options['banner_enabled'])) {
            return;
        }

        if (empty($options['website_id'])) {
            return;
        }

        // Frontend CSS (floating button + shortcode styles)
        wp_enqueue_style(
            'biscotti-frontend',
            BISCOTTI_PLUGIN_URL . 'assets/css/frontend.css',
            [],
            BISCOTTI_VERSION
        );
    }

    /**
     * Inject consent banner script in <head>
     * Loads biscotti.min.js from the Biscotti Cloud API
     */
    public function inject_consent_script()
    {
        $options = get_option('biscotti_options', []);

        if (empty($options['banner_enabled'])) {
            return;
        }

        $website_id = $options['website_id'] ?? '';
        if (empty($website_id)) {
            return;
        }

        $show_floating_button = $options['show_floating_button'] ?? true;
        $geo_ip_enabled = $options['geo_ip'] ?? false;

        // GEO-IP region-based config (if enabled)
        $region_config = [];
        if ($geo_ip_enabled) {
            try {
                $region_config = Biscotti_MultiRegion_Banner::get_config();
            } catch (\Exception $e) {
                // Silently fall back — GeoIP is non-critical
                $region_config = [];
            }
            // If GeoIP returned empty/unknown, use defaults instead of suppressing banner
            if (empty($region_config) || ($region_config['region'] ?? '') === 'XX') {
                $region_config = [
                    'region'      => $options['default_region'] ?? 'eu',
                    'consentMode' => $options['default_consent_mode'] ?? 'opt-in',
                ];
            }
        }

        // Load the consent banner script from the API (always up-to-date).
        // This ensures all customers get the latest version without plugin updates.
        // The local copy in assets/js/ serves as offline fallback only.
        $api_base = rtrim(get_option('biscotti_cmp_api_url', 'https://api.biscotti-cmp.com'), '/');
        $widget_url = $api_base . '/scripts/biscotti.min.js';

        // Register and enqueue the widget script
        wp_register_script(
            'biscotti-widget',
            $widget_url,
            [],
            BISCOTTI_VERSION,
            ['strategy' => 'defer', 'in_footer' => false]
        );

        // Build config object
        $api_url = get_option('biscotti_cmp_api_url', 'https://api.biscotti-cmp.com');
        $api_url = rtrim($api_url, '/') . '/api/v1';

        $config_data = [
            'websiteId'          => $website_id,
            'apiUrl'             => $api_url,
            'showFloatingButton' => (bool) $show_floating_button,
            'showPoweredBy'      => (bool) ($options['show_attribution'] ?? false),
            'primaryColor'       => $options['primary_color'] ?? '#9B6B3C',
            'language'           => class_exists('Biscotti_Language_Bridge')
                ? Biscotti_Language_Bridge::get_current_language()
                : substr(get_locale(), 0, 2),
        ];

        // C2 fix: Forward missing config keys from biscotti_options to BiscottiConfig
        if (!empty($options['tcf_enabled'])) {
            $config_data['tcfEnabled'] = true;
        }
        if (!empty($options['google_consent_mode'])) {
            $config_data['googleConsentMode'] = true;
        }
        if (!empty($options['auto_block'])) {
            $config_data['autoBlock'] = true;
        }
        $config_data['consentDuration'] = (int) ($options['consent_duration'] ?? 180);

        // C10 fix: Normalize position to match biscotti.js expectations (hyphenated)
        $raw_position = $options['banner_position'] ?? 'bottom-center';
        $position_map = [
            'bottom_center' => 'bottom-center',
            'bottom_left'   => 'bottom-left',
            'bottom_right'  => 'bottom-right',
            'top_center'    => 'top-center',
            'top_left'      => 'top-left',
            'top_right'     => 'top-right',
            'center'        => 'center',
        ];
        $config_data['position'] = $position_map[$raw_position] ?? $raw_position;

        if ($geo_ip_enabled && !empty($region_config)) {
            $config_data['geoIpEnabled'] = true;

            // C10 fix: Normalize region to lowercase (biscotti.js expects: 'eu', 'us', 'br', etc.)
            $config_data['region'] = strtolower($region_config['region'] ?? 'eu');

            // C10 fix: Normalize consent mode to client-expected values
            $raw_consent_mode = strtolower($region_config['consentMode'] ?? 'opt-in');
            $consent_mode_map = [
                'strict_opt_in' => 'opt-in',
                'strict-opt-in' => 'opt-in',
                'optin'         => 'opt-in',
                'opt_in'        => 'opt-in',
                'optout'        => 'opt-out',
                'opt_out'       => 'opt-out',
                'hybrid'        => 'opt-in',
                'no_requirement' => 'notice',
                'no-requirement' => 'notice',
            ];
            $config_data['consentMode'] = $consent_mode_map[$raw_consent_mode] ?? $raw_consent_mode;

            $config_data['language'] = $region_config['language'] ?? null;
        }

        // C15 fix: Fetch and forward white-label config
        $wl_config = $this->get_white_label_config($website_id, $api_url);
        if (!empty($wl_config)) {
            $config_data['whiteLabel'] = $wl_config;
        }

        // C16 fix: Fetch active A/B test and assign variant
        $ab_config = $this->get_ab_test_config($website_id, $api_url);
        if (!empty($ab_config)) {
            $config_data['abTest'] = $ab_config;
        }

        // Add inline config script before the widget
        $config_script = 'window.BiscottiConfig = ' . wp_json_encode($config_data) . ';';
        wp_add_inline_script('biscotti-widget', $config_script, 'before');

        wp_enqueue_script('biscotti-widget');
    }

    /**
     * Fetch white-label config from the API (cached for 5 minutes).
     */
    private function get_white_label_config($website_id, $api_url)
    {
        $cache_key = 'biscotti_wl_' . md5($website_id);
        $cached = get_transient($cache_key);
        if ($cached !== false) {
            return $cached;
        }

        $response = wp_remote_get($api_url . '/white-label/public/' . $website_id, [
            'timeout' => 5,
            'headers' => ['Accept' => 'application/json'],
        ]);

        if (is_wp_error($response) || wp_remote_retrieve_response_code($response) !== 200) {
            set_transient($cache_key, [], 60); // Cache empty for 1 min on failure
            return [];
        }

        $body = json_decode(wp_remote_retrieve_body($response), true);
        if (empty($body) || empty($body['data'])) {
            set_transient($cache_key, [], 60);
            return [];
        }

        $wl = [
            'logoUrl'       => $body['data']['logoUrl'] ?? null,
            'brandColor'    => $body['data']['brandColor'] ?? null,
            'customCss'     => !empty($body['data']['customCss']) ? wp_strip_all_tags($body['data']['customCss']) : '',
            'hidePoweredBy' => !empty($body['data']['hidePoweredBy']),
        ];

        // Only return if at least one value is set
        if (empty($wl['logoUrl']) && empty($wl['brandColor']) && empty($wl['customCss']) && !$wl['hidePoweredBy']) {
            set_transient($cache_key, [], 300);
            return [];
        }

        set_transient($cache_key, $wl, 300); // Cache for 5 minutes
        return $wl;
    }

    /**
     * Get active A/B test config and assign visitor to a variant.
     */
    private function get_ab_test_config($website_id, $api_url)
    {
        $cache_key = 'biscotti_ab_' . md5($website_id);
        $cached = get_transient($cache_key);

        // Fetch active test info (cached for 2 minutes)
        if ($cached === false) {
            $response = wp_remote_get($api_url . '/wp-plugin/ab-test-active/' . $website_id, [
                'timeout' => 5,
                'headers' => ['Accept' => 'application/json'],
            ]);

            if (is_wp_error($response) || wp_remote_retrieve_response_code($response) !== 200) {
                set_transient($cache_key, [], 60);
                return [];
            }

            $body = json_decode(wp_remote_retrieve_body($response), true);
            if (empty($body) || empty($body['test'])) {
                set_transient($cache_key, [], 120);
                return [];
            }

            $cached = $body['test'];
            set_transient($cache_key, $cached, 120);
        }

        if (empty($cached) || empty($cached['variants'])) {
            return [];
        }

        // Assign visitor to variant using cookie
        $cookie_name = 'biscotti_ab_' . ($cached['id'] ?? 'default');
        $assigned_variant = null;

        if (isset($_COOKIE[$cookie_name])) {
            // Find the assigned variant
            foreach ($cached['variants'] as $v) {
                if (($v['id'] ?? $v['name']) === $_COOKIE[$cookie_name]) {
                    $assigned_variant = $v;
                    break;
                }
            }
        }

        if (!$assigned_variant) {
            // Random assignment based on weights
            $total_weight = array_sum(array_column($cached['variants'], 'weight'));
            $rand = wp_rand(1, max($total_weight, 1));
            $cumulative = 0;
            foreach ($cached['variants'] as $v) {
                $cumulative += ($v['weight'] ?? 50);
                if ($rand <= $cumulative) {
                    $assigned_variant = $v;
                    break;
                }
            }
            if (!$assigned_variant) {
                $assigned_variant = $cached['variants'][0];
            }
            // Set cookie (30 days)
            $variant_id = $assigned_variant['id'] ?? $assigned_variant['name'];
            setcookie($cookie_name, $variant_id, time() + (30 * 86400), '/');
        }

        return [
            'testId'      => $cached['id'] ?? null,
            'variantId'   => $assigned_variant['id'] ?? null,
            'variantName' => $assigned_variant['name'] ?? null,
            'config'      => $assigned_variant['config'] ?? null,
        ];
    }
}
