<?php
defined('ABSPATH') || exit;

class GSPSYNC_Metal_Price_Updater
{
    /**
     * Metal configuration: units, validation ranges, and detection keywords
     */
    private $metal_config = array(
        'gold' => array(
            'symbol'     => 'XAU',
            'unit'       => 'troy_ounce',
            'divisor'    => 31.1034768,
            'min_usd'    => 200,
            'max_usd'    => 10000,
            'categories' => array('oro', 'gold'),
            'tags'       => array('oro', 'gold'),
            'keywords'   => array('oro', 'gold'),
        ),
        'silver' => array(
            'symbol'     => 'XAG',
            'unit'       => 'troy_ounce',
            'divisor'    => 31.1034768,
            'min_usd'    => 5,
            'max_usd'    => 200,
            'categories' => array('argento', 'silver'),
            'tags'       => array('argento', 'silver'),
            'keywords'   => array('argento', 'silver'),
        ),
    );

    /**
     * Default purity factors per metal
     */
    private $default_purities = array(
        'gold' => array(
            '24k' => 1.000,
            '22k' => 0.917,
            '18k' => 0.750,
            '14k' => 0.583,
            '10k' => 0.417,
        ),
        'silver' => array(
            '.999' => 1.000,
            '.925' => 0.925,
            '.900' => 0.900,
            '.800' => 0.800,
        ),
    );

    public function init()
    {
        // Price filters (priority 20 = after WooCommerce defaults)
        add_filter('woocommerce_product_get_price', array($this, 'apply_dynamic_price'), 20, 2);
        add_filter('woocommerce_product_get_regular_price', array($this, 'apply_dynamic_price'), 20, 2);
        add_filter('woocommerce_product_get_sale_price', array($this, 'apply_dynamic_price'), 20, 2);

        // Variation price filters
        add_filter('woocommerce_product_variation_get_price', array($this, 'apply_dynamic_price'), 20, 2);
        add_filter('woocommerce_product_variation_get_regular_price', array($this, 'apply_dynamic_price'), 20, 2);
        add_filter('woocommerce_product_variation_get_sale_price', array($this, 'apply_dynamic_price'), 20, 2);

        // Variation prices array filters
        add_filter('woocommerce_variation_prices_price', array($this, 'apply_dynamic_price'), 20, 2);
        add_filter('woocommerce_variation_prices_regular_price', array($this, 'apply_dynamic_price'), 20, 2);
        add_filter('woocommerce_variation_prices_sale_price', array($this, 'apply_dynamic_price'), 20, 2);

        // Display price filter
        add_filter('woocommerce_get_price_to_display', array($this, 'apply_display_price'), 20, 3);

        // HTML price display
        add_filter('woocommerce_get_price_html', array($this, 'apply_price_html'), 20, 2);
        add_filter('woocommerce_cart_item_price', array($this, 'modify_cart_item_price'), 20, 3);

        // Grouped product prices
        add_filter('woocommerce_grouped_price_html', array($this, 'modify_grouped_price_html'), 20, 2);

        // Cache invalidation: metal prices are part of the variation price hash,
        // so WooCommerce regenerates cached prices automatically when they change.
        add_filter('woocommerce_get_variation_prices_hash', array($this, 'add_metal_price_to_hash'), 10, 3);

        // Async price update check
        add_action('wp_loaded', array($this, 'maybe_update_metal_prices'));
    }

    /**
     * Get the metal configuration array
     */
    public function get_metal_config()
    {
        return $this->metal_config;
    }

    /**
     * Get default purities for a given metal
     */
    public function get_default_purities($metal = null)
    {
        if ($metal && isset($this->default_purities[$metal])) {
            return $this->default_purities[$metal];
        }
        return $this->default_purities;
    }

    /**
     * Fetch prices for all metals
     */
    public function fetch_all_metal_prices()
    {
        $success = false;
        foreach (array_keys($this->metal_config) as $metal) {
            if ($this->fetch_metal_price($metal)) {
                $success = true;
            }
        }

        if ($success) {
            update_option('gspsync_last_update', current_time('timestamp'));
        }

        return $success;
    }

    /**
     * Fetch price for a specific metal from the CRM
     */
    public function fetch_metal_price($metal)
    {
        if (!isset($this->metal_config[$metal])) {
            return false;
        }

        $config = $this->metal_config[$metal];
        $transient_key = 'gspsync_' . $metal . '_api_response';
        $cached_response = get_transient($transient_key);

        if ($cached_response !== false) {
            $data = $cached_response;
            if (defined('WP_DEBUG') && WP_DEBUG) {
                gspsync_log("GSPSYNC: Using cached {$metal} API response (transient)");
            }
        } else {
            // Failure lock: if the CRM just failed, don't retry on every request
            if (get_transient('gspsync_' . $metal . '_api_fail')) {
                return false;
            }

            $data = $this->fetch_from_crm($metal, $config);

            if ($data === false) {
                // Back off for 2 minutes before the next attempt
                set_transient('gspsync_' . $metal . '_api_fail', 1, 120);
                gspsync_log("GSPSYNC: CRM unreachable for {$metal}");
                return false;
            }

            // Cache for 10 minutes (matches the CRM update heartbeat)
            set_transient($transient_key, $data, 600);
        }

        return $this->process_price_data($metal, $config, $data);
    }

    /**
     * Fetch metal price from the API endpoint.
     * Premium users get cached prices for 10 minutes via /api/metal-prices/premium/{symbol}
     * Free users get cached prices for 60 minutes via /api/metal-prices/{symbol}
     *
     * @param string $metal  Metal key (gold, silver)
     * @param array  $config Metal configuration
     * @return array|false   Decoded JSON data or false on failure
     */
    private function fetch_from_crm($metal, $config)
    {
        $api_base_url = defined('GSPSYNC_API_BASE_URL') ? GSPSYNC_API_BASE_URL : 'https://crm-dynamic-bullion.it/api';
        $api_base_url = apply_filters('gspsync_api_base_url', $api_base_url, $metal);
        $symbol = $config['symbol'];

        $is_premium = false;
        $headers = array('Accept' => 'application/json');
        $url = trailingslashit($api_base_url) . 'metal-prices/' . $symbol;

        // Premium: the price service offers a lower-latency cache tier that
        // requires license authentication server-side.
        if ( function_exists('gspsync_fs') && gspsync_fs()->is__premium_only() ) {
            if ( gspsync_fs()->can_use_premium_code() ) {
                $is_premium = true;
                $url = trailingslashit($api_base_url) . 'metal-prices/premium/' . $symbol;

                $license = gspsync_fs()->_get_license();
                if (is_object($license) && !empty($license->secret_key)) {
                    $headers['X-Freemius-License'] = $license->secret_key;
                }

                $site = gspsync_fs()->get_site();
                if (is_object($site) && !empty($site->id)) {
                    $headers['X-Freemius-Site-Id'] = $site->id;
                }
            }
        }
        $args = array(
            'headers' => $headers,
            'timeout' => 15,
        );

        if (defined('WP_DEBUG') && WP_DEBUG) {
            $plan_label = $is_premium ? 'premium (10min cache)' : 'free (60min cache)';
            gspsync_log("GSPSYNC: Fetching {$metal} from API ({$plan_label}): {$url}");
        }

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

        if (is_wp_error($response)) {
            gspsync_log("GSPSYNC: API error for {$metal}: " . $response->get_error_message());
            return false;
        }

        $http_code = wp_remote_retrieve_response_code($response);
        if ($http_code !== 200) {
            gspsync_log("GSPSYNC: API returned HTTP {$http_code} for {$metal}");
            return false;
        }

        $body = wp_remote_retrieve_body($response);
        $data = json_decode($body, true);

        if (!is_array($data)) {
            gspsync_log("GSPSYNC: Invalid JSON from API for {$metal}");
            return false;
        }
        
        return $data;
    }

    /**
     * Process price data from any source and update WordPress options.
     *
     * @param string $metal  Metal key (gold, silver)
     * @param array  $config Metal configuration
     * @param array  $data   Decoded JSON price data
     * @return bool          Whether processing was successful
     */
    private function process_price_data($metal, $config, $data)
    {
        $currency = GSPSYNC_Currency::get();

        // CRM format ({rate: {price: <EUR>}}) on a euro shop: the quote already is
        // in the target currency. Kept as its own path so euro shops never depend
        // on the exchange rate feed, exactly as before multi-currency support.
        if ('EUR' === $currency && isset($data['rate']['price']) && is_numeric($data['rate']['price'])) {
            $price = floatval($data['rate']['price']);

            // Sanity range: reject values outside a wide band derived from the USD
            // validation range, so a corrupted CRM value never reaches shop prices.
            $min_eur = $config['min_usd'] * 0.5;
            $max_eur = $config['max_usd'] * 2.0;
            if ($price < $min_eur || $price > $max_eur) {
                gspsync_log("GSPSYNC: Invalid {$metal} price (rate.price): {$price} EUR - out of range [{$min_eur}, {$max_eur}] - rejected");
                return false;
            }

            return $this->store_metal_price($metal, $config, $price, $currency);
        }

        $usd_price = $this->extract_usd_price($metal, $data);

        if ($usd_price === false) {
            return false;
        }

        // Validation always happens in USD, so both feed formats share one range.
        if ($usd_price <= 0 || $usd_price < $config['min_usd'] || $usd_price > $config['max_usd']) {
            gspsync_log("GSPSYNC: Invalid {$metal} price: {$usd_price} USD - rejected");
            return false;
        }

        $rate = GSPSYNC_Currency::get_usd_rate($currency);

        if ($rate === false) {
            // Better to keep yesterday's price than to write one converted at a
            // guessed rate: these values go straight onto products for sale.
            gspsync_log("GSPSYNC: no USD/{$currency} rate available - {$metal} price left unchanged");
            return false;
        }

        return $this->store_metal_price($metal, $config, $usd_price * $rate, $currency);
    }

    /**
     * Read a USD quote out of a feed response, normalizing the EUR-denominated
     * CRM format when that is what came back.
     *
     * @param string $metal Metal key (gold, silver)
     * @param array  $data  Decoded JSON price data
     * @return float|false  Price in USD, or false when unusable
     */
    private function extract_usd_price($metal, $data)
    {
        // Price service format: {price: <USD>}
        if (isset($data['price']) && is_numeric($data['price'])) {
            return floatval($data['price']);
        }

        // CRM format: {rate: {price: <EUR>}}
        if (isset($data['rate']['price']) && is_numeric($data['rate']['price'])) {
            $eur_rate = GSPSYNC_Currency::get_usd_rate('EUR');

            if ($eur_rate === false || $eur_rate <= 0) {
                gspsync_log("GSPSYNC: cannot convert the {$metal} EUR quote without a USD/EUR rate");
                return false;
            }

            return floatval($data['rate']['price']) / $eur_rate;
        }

        gspsync_log("GSPSYNC: API response for {$metal} has unexpected structure");

        return false;
    }

    /**
     * Persist a metal price, already converted to the target currency.
     *
     * @param string $metal    Metal key (gold, silver)
     * @param array  $config   Metal configuration
     * @param float  $price    Price per unit (ounce or pound) in $currency
     * @param string $currency Currency the price is denominated in
     * @return bool
     */
    private function store_metal_price($metal, $config, $price, $currency)
    {
        $price_per_gram = $price / $config['divisor'];
        $unit_label     = ($config['unit'] === 'pound') ? 'per_pound' : 'per_ounce';

        update_option("gspsync_{$metal}_price_{$unit_label}", $price);
        update_option("gspsync_{$metal}_price_per_gram", $price_per_gram);
        update_option("gspsync_{$metal}_last_update", current_time('timestamp'));

        // Which currency the stored figures are in, so the dashboard labels them
        // correctly even if a currency switch could not be followed by a refetch.
        update_option('gspsync_prices_currency', $currency);

        if (defined('WP_DEBUG') && WP_DEBUG) {
            gspsync_log("GSPSYNC: {$metal} price updated - {$currency}/{$unit_label}: {$price}, {$currency}/gram: {$price_per_gram}");
        }

        return true;
    }

    /**
     * Backward-compatible wrapper for fetch_gold_price
     */
    public function fetch_gold_price()
    {
        return $this->fetch_all_metal_prices();
    }

    /**
     * Clear API transients for all metals
     */
    public function clear_api_transients()
    {
        foreach (array_keys($this->metal_config) as $metal) {
            delete_transient('gspsync_' . $metal . '_api_response');
            delete_transient('gspsync_' . $metal . '_api_fail'); // reset failure back-off on manual refresh
        }
        GSPSYNC_Currency::clear_rates_cache();

        if (defined('WP_DEBUG') && WP_DEBUG) {
            gspsync_log('GSPSYNC: API transients cleared');
        }
    }

    /**
     * Calculate product price based on weight, metal type, purity, and margin
     */
    public function calculate_price_by_weight($product)
    {
        // Detect metal type
        $metal_type = $this->is_metal_product($product);
        if (!$metal_type) {
            return false;
        }

        // Get weight
        $weight_grams = 0;
        if ($product->is_type('variation')) {
            $weight_grams = $this->get_variation_weight($product);
        } else {
            $weight_grams = $this->get_product_weight_from_attributes($product);

            if (empty($weight_grams)) {
                $wc_weight = $product->get_weight();
                if (!empty($wc_weight)) {
                    // Convert from the store's configured weight unit (kg, g, lbs, oz) to grams
                    $weight_grams = wc_get_weight((float) $wc_weight, 'g');
                }
            }
        }

        if (empty($weight_grams)) {
            return false;
        }

        // Get metal price per gram
        $price_per_gram = (float) get_option("gspsync_{$metal_type}_price_per_gram", 0);

        if ($price_per_gram <= 0) {
            $this->fetch_metal_price($metal_type);
            $price_per_gram = (float) get_option("gspsync_{$metal_type}_price_per_gram", 0);
            if ($price_per_gram <= 0) {
                return false;
            }
        }

        // Base price
        $base_price = $weight_grams * $price_per_gram;

        // Apply purity factor
        $purity = get_post_meta($product->get_id(), '_gspsync_purity', true);
        $purity_factor = 1.0;

        if (!empty($purity)) {
            $purity_factors = get_option("gspsync_{$metal_type}_purity_factors", $this->get_default_purities($metal_type));
            if (isset($purity_factors[$purity])) {
                $purity_factor = (float) $purity_factors[$purity];
                $base_price = $base_price * $purity_factor;
            }
        }

        // Apply profit margin
        $profit_margin = (float) get_option('gspsync_profit_margin', 20);
        $final_price = $base_price * (1 + ($profit_margin / 100));

        // Apply fixed markup
        $markup = 0;
        if ($product->is_type('variation')) {
            $variation_markup = get_post_meta($product->get_id(), '_gspsync_markup', true);
            if ($variation_markup !== '' && $variation_markup !== false) {
                $markup = (float) $variation_markup;
            } else {
                $parent_id = $product->get_parent_id();
                if ($parent_id) {
                    $markup = (float) get_post_meta($parent_id, '_gspsync_markup', true);
                }
            }
        } else {
            $markup = (float) get_post_meta($product->get_id(), '_gspsync_markup', true);
        }
        $final_price += $markup;

        return round($final_price, 2);
    }

    /**
     * Detect if a product is a metal product and return its type
     *
     * @return string|false 'gold', 'silver', or false
     */
    public function is_metal_product($product)
    {
        static $metal_product_cache = array();

        $product_id = $product->get_id();

        if (isset($metal_product_cache[$product_id])) {
            return $metal_product_cache[$product_id];
        }

        // For variations, check parent
        if ($product->is_type('variation')) {
            $parent_id = $product->get_parent_id();

            if (isset($metal_product_cache[$parent_id])) {
                $metal_product_cache[$product_id] = $metal_product_cache[$parent_id];
                return $metal_product_cache[$parent_id];
            }

            $parent_product = wc_get_product($parent_id);
            if ($parent_product) {
                $result = $this->is_metal_product($parent_product);
                $metal_product_cache[$product_id] = $result;
                return $result;
            }

            $metal_product_cache[$product_id] = false;
            return false;
        }

        // Method 1: Check explicit meta _gspsync_metal_type
        $metal_type = get_post_meta($product_id, '_gspsync_metal_type', true);
        if (!empty($metal_type) && isset($this->metal_config[$metal_type])) {
            $metal_product_cache[$product_id] = $metal_type;
            return $metal_type;
        }

        // Legacy support: check old _is_gold_product meta
        $is_gold = get_post_meta($product_id, '_is_gold_product', true);
        if ($is_gold === 'yes') {
            $metal_product_cache[$product_id] = 'gold';
            return 'gold';
        }

        // Method 2: Check categories
        $categories = wp_get_post_terms($product_id, 'product_cat', array('fields' => 'slugs'));
        if (!is_wp_error($categories)) {
            foreach ($this->metal_config as $metal => $config) {
                foreach ($config['categories'] as $cat_slug) {
                    if (in_array($cat_slug, $categories, true)) {
                        $metal_product_cache[$product_id] = $metal;
                        return $metal;
                    }
                }
            }
        }

        // Method 3: Check tags
        $tags = wp_get_post_terms($product_id, 'product_tag', array('fields' => 'slugs'));
        if (!is_wp_error($tags)) {
            foreach ($this->metal_config as $metal => $config) {
                foreach ($config['tags'] as $tag_slug) {
                    if (in_array($tag_slug, $tags, true)) {
                        $metal_product_cache[$product_id] = $metal;
                        return $metal;
                    }
                }
            }
        }

        // Method 4: Check name/description keywords
        $title = strtolower($product->get_name());
        $description = strtolower($product->get_short_description());

        foreach ($this->metal_config as $metal => $config) {
            foreach ($config['keywords'] as $keyword) {
                if (strpos($title, $keyword) !== false || strpos($description, $keyword) !== false) {
                    $metal_product_cache[$product_id] = $metal;
                    return $metal;
                }
            }
        }

        $metal_product_cache[$product_id] = false;
        return false;
    }

    /**
     * Backward-compatible: Check if product is a gold product (returns bool)
     */
    public function is_gold_product($product)
    {
        return $this->is_metal_product($product) !== false;
    }

    /**
     * Get product weight from attributes
     *
     * BUG FIX: Now checks _gspsync_weight_grams meta FIRST before product attributes.
     * Previously, WooCommerce product attributes (like "peso") would override
     * the manually-set plugin weight, which caused incorrect price calculations.
     */
    private function get_product_weight_from_attributes($product)
    {
        $weight_grams = 0;

        // PRIORITY 1: Check plugin meta fields FIRST (this is the bug fix)
        $meta_weight_keys = array(
            '_gspsync_weight_grams',
            '_peso_grammi',  // Legacy support
            '_weight_grams',
            '_peso',
            '_weight',
            'peso_grammi',
            'weight_grams'
        );

        foreach ($meta_weight_keys as $meta_key) {
            $meta_value = get_post_meta($product->get_id(), $meta_key, true);
            if (!empty($meta_value)) {
                preg_match('/([0-9]+[.,]?[0-9]*)/', $meta_value, $matches);
                if (!empty($matches[1])) {
                    $weight_grams = floatval(str_replace(',', '.', $matches[1]));
                    if (defined('WP_DEBUG') && WP_DEBUG) {
                        gspsync_log("GSPSYNC: Weight found in meta '{$meta_key}': {$weight_grams}g");
                    }
                    return $weight_grams;
                }
            }
        }

        // PRIORITY 2: Check product attributes (fallback only if meta is empty)
        $weight_attribute_names = array(
            'peso', 'peso-grammi', 'peso_grammi',
            'weight', 'weight-grams', 'weight_grams',
            'grammi', 'grams', 'gr', 'g'
        );

        if ($product->is_type('variation')) {
            $variation_attributes = $product->get_variation_attributes();

            foreach ($variation_attributes as $attribute_name => $attribute_value) {
                $clean_name = strtolower(str_replace(array('pa_', '-', '_'), array('', '', ''), $attribute_name));

                if (in_array($clean_name, $weight_attribute_names)) {
                    if (!empty($attribute_value)) {
                        preg_match('/([0-9]+[.,]?[0-9]*)/', $attribute_value, $matches);
                        if (!empty($matches[1])) {
                            $weight_grams = floatval(str_replace(',', '.', $matches[1]));
                            break;
                        }
                    }
                }
            }
        } else {
            $attributes = $product->get_attributes();

            foreach ($attributes as $attribute_name => $attribute) {
                if (!is_object($attribute)) {
                    continue;
                }

                $clean_name = strtolower(str_replace(array('pa_', '-', '_'), array('', '', ''), $attribute_name));

                if (in_array($clean_name, $weight_attribute_names)) {
                    $weight_value = '';
                    if ($attribute->is_taxonomy()) {
                        $terms = wp_get_post_terms($product->get_id(), $attribute->get_name());
                        if ($terms && !is_wp_error($terms)) {
                            $weight_value = $terms[0]->name;
                        }
                    } else {
                        $options = $attribute->get_options();
                        $weight_value = !empty($options) ? $options[0] : '';
                    }

                    if (!empty($weight_value)) {
                        preg_match('/([0-9]+[.,]?[0-9]*)/', $weight_value, $matches);
                        if (!empty($matches[1])) {
                            $weight_grams = floatval(str_replace(',', '.', $matches[1]));
                            break;
                        }
                    }
                }
            }
        }

        return $weight_grams;
    }

    /**
     * Get weight for a product variation
     */
    public function get_variation_weight($variation)
    {
        if (!$variation->is_type('variation')) {
            return 0;
        }

        // 1. Plugin meta (highest priority)
        $weight_grams = get_post_meta($variation->get_id(), '_gspsync_weight_grams', true);
        if (!empty($weight_grams)) {
            return floatval($weight_grams);
        }

        // Legacy support
        $weight_grams = get_post_meta($variation->get_id(), '_peso_grammi', true);
        if (!empty($weight_grams)) {
            return floatval($weight_grams);
        }

        // 2. WooCommerce standard weight field (convert from store unit to grams)
        $wc_weight = $variation->get_weight();
        if (!empty($wc_weight)) {
            return wc_get_weight((float) $wc_weight, 'g');
        }

        // 3. Variation attributes
        $weight_attribute_names = array(
            'peso', 'peso-grammi', 'peso_grammi',
            'weight', 'weight-grams', 'weight_grams',
            'grammi', 'grams', 'gr', 'g'
        );

        $variation_attributes = $variation->get_variation_attributes();
        foreach ($variation_attributes as $attribute_name => $attribute_value) {
            $clean_name = strtolower(str_replace(array('pa_', '-', '_'), array('', '', ''), $attribute_name));

            if (in_array($clean_name, $weight_attribute_names)) {
                if (!empty($attribute_value)) {
                    preg_match('/([0-9]+[.,]?[0-9]*)/', $attribute_value, $matches);
                    if (!empty($matches[1])) {
                        return floatval(str_replace(',', '.', $matches[1]));
                    }
                }
            }
        }

        // 4. Parent product fallback
        $parent_product = wc_get_product($variation->get_parent_id());
        if ($parent_product) {
            return $this->get_product_weight_from_attributes($parent_product);
        }

        return 0;
    }

    /**
     * Apply dynamic price to product
     */
    public function apply_dynamic_price($price, $product)
    {
        if (is_admin() && !wp_doing_ajax()) {
            return $price;
        }

        static $processing = false;
        if ($processing) {
            return $price;
        }

        static $price_cache = array();
        $product_id = $product->get_id();

        if (isset($price_cache[$product_id])) {
            return $price_cache[$product_id];
        }

        $processing = true;
        $new_price = $this->calculate_price_by_weight($product);
        $processing = false;

        if ($new_price === false) {
            $price_cache[$product_id] = $price;
            return $price;
        }

        $price_cache[$product_id] = $new_price;
        return $new_price;
    }

    /**
     * Apply price for display
     */
    public function apply_display_price($price, $qty, $product)
    {
        if (is_admin() && !wp_doing_ajax()) {
            return $price;
        }

        $new_price = $this->calculate_price_by_weight($product);
        return ($new_price !== false) ? $new_price : $price;
    }

    /**
     * Modify price HTML
     */
    public function apply_price_html($price_html, $product)
    {
        if (is_admin() && !wp_doing_ajax()) {
            return $price_html;
        }

        $new_price = $this->calculate_price_by_weight($product);
        if ($new_price !== false && $product->is_type('simple')) {
            return wc_price($new_price);
        }

        return $price_html;
    }

    /**
     * Modify cart item price display
     */
    public function modify_cart_item_price($price_html, $cart_item, $cart_item_key)
    {
        $product = null;

        if (isset($cart_item['variation_id']) && $cart_item['variation_id'] > 0) {
            $product = wc_get_product($cart_item['variation_id']);
        } elseif (isset($cart_item['product_id'])) {
            $product = wc_get_product($cart_item['product_id']);
        }

        if ($product && $this->is_metal_product($product)) {
            $new_price = $this->calculate_price_by_weight($product);
            if ($new_price !== false) {
                return wc_price($new_price);
            }
        }

        return $price_html;
    }

    /**
     * Modify grouped product price HTML
     */
    public function modify_grouped_price_html($price_html, $product)
    {
        $children = array_filter(array_map('wc_get_product', $product->get_children()), 'wc_products_array_filter_visible_grouped');
        $child_prices = array();

        foreach ($children as $child) {
            $dynamic_price = $this->calculate_price_by_weight($child);
            if ($dynamic_price !== false) {
                $child_prices[] = $dynamic_price;
            }
        }

        if (!empty($child_prices)) {
            $min_price = min($child_prices);
            $max_price = max($child_prices);
            return ($min_price !== $max_price) ? wc_format_price_range($min_price, $max_price) : wc_price($min_price);
        }

        return $price_html;
    }

    /**
     * Check if metal prices need updating
     */
    public function maybe_update_metal_prices()
    {
        // Frontend: never block a visitor's page load with a remote fetch.
        // Updates happen via WP-Cron or when an admin loads the dashboard.
        if (!is_admin() && !wp_doing_cron()) {
            return;
        }

        $last_update = (int) get_option('gspsync_last_update', 0);
        $has_any_price = false;

        foreach (array_keys($this->metal_config) as $metal) {
            $price = (float) get_option("gspsync_{$metal}_price_per_gram", 0);
            if ($price > 0) {
                $has_any_price = true;
                break;
            }
        }

        // First-time: must fetch
        if (!$has_any_price) {
            $this->fetch_all_metal_prices();
            return;
        }

        // Admin: check if update needed based on the configured frequency
        $now = current_time('timestamp');

        $intervals = gspsync_get_allowed_frequencies();
        $interval = $intervals[gspsync_get_update_frequency()];

        if (($now - $last_update) >= $interval) {
            $this->fetch_all_metal_prices();
        }
    }

    /**
     * Clear WooCommerce caches.
     *
     * Not hooked on every page load: cache invalidation is handled by
     * add_metal_price_to_hash() via the variation price hash. Clears product
     * transients on demand only.
     */
    public function clear_wc_caches()
    {
        if (function_exists('wc_delete_product_transients')) {
            wc_delete_product_transients();
        }
    }

    /**
     * Add metal prices to variation hash for cache invalidation
     */
    public function add_metal_price_to_hash($hash, $product, $for_display)
    {
        foreach (array_keys($this->metal_config) as $metal) {
            $price = get_option("gspsync_{$metal}_price_per_gram", 0);
            $hash[] = "gspsync_{$metal}_" . $price;
        }
        return $hash;
    }

    /**
     * Clear price caches for a specific product
     */
    public function clear_price_caches($product_id = null)
    {
        if ($product_id) {
            delete_transient('wc_var_prices_' . $product_id);
            delete_transient('wc_product_children_' . $product_id);
            wp_cache_delete($product_id, 'products');
        } else {
            wc_delete_product_transients();
        }
    }

    /**
     * Update all products with new prices
     */
    public function update_all_products()
    {
        $this->fetch_all_metal_prices();

        $products = wc_get_products(array('status' => 'publish', 'limit' => -1));

        foreach ($products as $product) {
            $new_price = $this->calculate_price_by_weight($product);
            if ($new_price !== false) {
                update_post_meta($product->get_id(), '_price', $new_price);
                update_post_meta($product->get_id(), '_regular_price', $new_price);
            }
        }

        return count($products);
    }

    /**
     * Force refresh variable product prices
     */
    public function force_refresh_variable_product_prices($product_id)
    {
        $product = wc_get_product($product_id);
        if (!$product || !$product->is_type('variable')) {
            return false;
        }

        $this->fetch_all_metal_prices();

        $variations = $product->get_children();
        $updated_count = 0;

        foreach ($variations as $variation_id) {
            $variation = wc_get_product($variation_id);
            if (!$variation) {
                continue;
            }

            $new_price = $this->calculate_price_by_weight($variation);
            if ($new_price !== false) {
                update_post_meta($variation_id, '_price', $new_price);
                update_post_meta($variation_id, '_regular_price', $new_price);
                $this->clear_price_caches($variation_id);
                $updated_count++;
            }
        }

        $this->clear_price_caches($product_id);
        delete_transient('wc_var_prices_' . $product_id);
        delete_transient('wc_product_children_' . $product_id);

        return $updated_count;
    }

    /**
     * Debug price calculation for a product
     */
    public function debug_price_calculation($product_id)
    {
        if (!current_user_can('manage_options')) {
            return false;
        }

        $product = wc_get_product($product_id);
        if (!$product) {
            return false;
        }

        $metal_type = $this->is_metal_product($product);
        $weight_from_attributes = $this->get_product_weight_from_attributes($product);
        $weight_kg = $product->get_weight();
        $weight_from_standard = $weight_kg ? $weight_kg * 1000 : 0;
        $final_weight = $weight_from_attributes ?: $weight_from_standard;

        $price_per_gram = $metal_type ? (float) get_option("gspsync_{$metal_type}_price_per_gram", 0) : 0;
        $base_price = $final_weight * $price_per_gram;

        $purity = get_post_meta($product_id, '_gspsync_purity', true);
        $purity_factor = 1.0;
        if (!empty($purity) && $metal_type) {
            $purity_factors = get_option("gspsync_{$metal_type}_purity_factors", $this->get_default_purities($metal_type));
            if (isset($purity_factors[$purity])) {
                $purity_factor = $purity_factors[$purity];
                $base_price *= $purity_factor;
            }
        }

        $profit_margin = (float) get_option('gspsync_profit_margin', 20);
        $final_price = $base_price * (1 + ($profit_margin / 100));

        return array(
            'metal_type'       => $metal_type,
            'weight_meta'      => $weight_from_attributes,
            'weight_standard'  => $weight_from_standard,
            'weight_final'     => $final_weight,
            'price_per_gram'   => $price_per_gram,
            'base_price'       => $base_price,
            'purity'           => $purity,
            'purity_factor'    => $purity_factor,
            'profit_margin'    => $profit_margin,
            'calculated_price' => round($final_price, 2),
            'displayed_price'  => $product->get_price()
        );
    }
}
