<?php
/**
 * Teleship Live Rates Shipping Method Class
 *
 * Provides live shipping rates from Teleship API
 *
 * @package Teleship
 */

// Exit if accessed directly
if (!defined('ABSPATH')) {
    exit;
}

/**
 * Teleship Live Rates Shipping Method Class
 */
// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedClassFound -- WC_ prefix is required for WooCommerce shipping methods
class WC_Teleship_Shipping_Method extends WC_Shipping_Method {
    /**
     * API endpoint for getting rates
     *
     * @var string
     */
    private $api_endpoint;

    /**
     * Debug mode
     *
     * @var boolean
     */
    private $debug;

    /**
     * Constructor.
     *
     * @param int $instance_id Shipping method instance.
     */
    public function __construct($instance_id = 0) {
        $this->id                 = 'teleship_shipping';
        $this->instance_id        = absint($instance_id);
        $this->method_title       = __('Teleship Global Shipping', 'teleship-global-shipping');
        $this->method_description = __('Get live shipping rates from Teleship directly in your WooCommerce store.', 'teleship-global-shipping');
        $this->supports           = array(
            'shipping-zones',
            'instance-settings',
            'instance-settings-modal',
        );

        $this->init();
        $this->api_endpoint = teleship_get_api_url() . '/api/rates/checkout';

        // Debug mode - check both global and instance settings
        $global_debug = get_option('teleship_debug_mode') === 'yes';
        $instance_debug = $this->get_option('debug') === 'yes';
        $this->debug = $global_debug || $instance_debug;
    }

    /**
     * Initialize settings
     */
    private function init() {
        // Load the settings
        $this->init_form_fields();
        $this->init_settings();

        // Define settings
        $this->enabled = $this->get_option('enabled');
        $this->title   = __('Teleship Global Shipping', 'teleship-global-shipping'); // Hard-coded title instead of option

        // Save settings in admin
        add_action('woocommerce_update_options_shipping_' . $this->id, array($this, 'process_admin_options'));
    }

    /**
     * Initialize form fields
     */
    public function init_form_fields() {
        $this->instance_form_fields = array(
            'enabled' => array(
                'title'       => __('Enable', 'teleship-global-shipping'),
                'type'        => 'checkbox',
                'description' => __('Enable this shipping method', 'teleship-global-shipping'),
                'default'     => 'yes'
            ),
            'debug' => array(
                'title'       => __('Debug Mode', 'teleship-global-shipping'),
                'type'        => 'checkbox',
                'description' => __('Enable debug mode to log API requests and responses for this shipping zone.', 'teleship-global-shipping'),
                'default'     => 'no'
            ),
        );
    }

    /**
     * Calculate shipping rates
     *
     * @param array $package Shipping package.
     */
    public function calculate_shipping($package = array()) {
        if ($this->enabled !== 'yes') {
            return;
        }

        // Check if there are items to ship
        if (empty($package['contents'])) {
            return;
        }

        // Prepare the API request
        $request_data = $this->prepare_api_request($package);
        if (!$request_data) {
            $this->add_fallback_rate();
            return;
        }

        // Call the Teleship API
        $response = $this->call_teleship_api($request_data);
        if (!$response) {
            $this->add_fallback_rate();
            return;
        }

        // Process the API response
        $this->process_api_response($response);
    }

    /**
     * Prepare the API request from WooCommerce package
     *
     * @param array $package WooCommerce shipping package.
     * @return array|false Request data or false on failure.
     */
    private function prepare_api_request($package) {
        // Check if destination address is set
        if (empty($package['destination'])) {
            if ($this->debug) {
                do_action('teleship_log', 'Cannot prepare Teleship API request: No destination address.');
            }
            return false;
        }

        // Check for required destination fields
        // Country is always required
        if (empty($package['destination']['country'])) {
            if ($this->debug) {
                do_action('teleship_log', 'Cannot prepare Teleship API request: Missing required destination field: country');
            }
            return false;
        }

        // Countries that don't require postal codes
        $countries_without_postcode = array('AE', 'HK');
        $destination_country = $package['destination']['country'];

        // Only require postcode for countries that use them
        if (!in_array($destination_country, $countries_without_postcode, true) && empty($package['destination']['postcode'])) {
            if ($this->debug) {
                do_action('teleship_log', 'Cannot prepare Teleship API request: Missing required destination field: postcode');
            }
            return false;
        }

        // Get store info for origin address
        $store_country = WC()->countries->get_base_country();
        $store_state = WC()->countries->get_base_state();
        $store_postcode = WC()->countries->get_base_postcode();
        $store_city = WC()->countries->get_base_city();
        $store_address = WC()->countries->get_base_address();
        $store_address_2 = WC()->countries->get_base_address_2();
        $store_name = get_bloginfo('name');

        // Prepare origin address in the format expected by the API
        $origin = array(
            'country' => $store_country,
            'postal_code' => $store_postcode,
            'province' => $store_state,
            'city' => $store_city,
            'name' => null,
            'address1' => $store_address,
            'address2' => $store_address_2,
            'address_type' => null,
            'company_name' => $store_name
        );

        // Prepare destination address in the format expected by the API
        $destination = array(
            'country' => $package['destination']['country'],
            'postal_code' => $package['destination']['postcode'],
            'province' => $package['destination']['state'] ? $package['destination']['state'] : '',
            'city' => $package['destination']['city'] ? $package['destination']['city'] : '',
            'name' => WC()->customer->get_first_name() . ' ' . WC()->customer->get_last_name(),
            'address1' => $package['destination']['address_1'] ? $package['destination']['address_1'] : '',
            'address2' => $package['destination']['address_2'] ? $package['destination']['address_2'] : '',
            'address_type' => 'residential', // Default to residential unless we know otherwise
            'company_name' => WC()->customer->get_billing_company() ? WC()->customer->get_billing_company() : null
        );

        // Prepare items array according to the API format
        $items = array();

        foreach ($package['contents'] as $item_id => $values) {
            $product = $values['data'];
            $quantity = $values['quantity'];

            // Skip items that don't need shipping
            if (!$product->needs_shipping()) {
                continue;
            }

            // Convert weight to grams (API expects weight in grams)
            $weight_in_grams = 0;
            if ($product->has_weight()) {
                $weight = (float) $product->get_weight();
                // Convert weight to grams based on WooCommerce weight unit
                $weight_unit = get_option('woocommerce_weight_unit');
                switch ($weight_unit) {
                    case 'kg':
                        $weight_in_grams = $weight * 1000;
                        break;
                    case 'g':
                        $weight_in_grams = $weight;
                        break;
                    case 'lbs':
                        $weight_in_grams = $weight * 453.592;
                        break;
                    case 'oz':
                        $weight_in_grams = $weight * 28.3495;
                        break;
                }
            }

            // Convert price to cents (API expects price in cents)
            // Use wc_get_price_excluding_tax() to always get the net price,
            // regardless of whether the store has "Prices entered with tax" enabled
            $price_in_cents = intval(wc_get_price_excluding_tax($product) * 100);

            // Get product details
            $items[] = array(
                'name' => $product->get_name(),
                'sku' => $product->get_sku() ? $product->get_sku() : 'SKU_' . $product->get_id(),
                'quantity' => $quantity,
                'grams' => round($weight_in_grams),
                'price' => $price_in_cents,
                'vendor' => $product->get_meta('_vendor', true) ? $product->get_meta('_vendor', true) : get_bloginfo('name'),
                'requires_shipping' => true,
                'taxable' => $product->is_taxable(),
                'fulfillment_service' => 'manual',
                'product_id' => $product->get_id(),
                'variant_id' => $product->is_type('variation') ? $product->get_id() : 0
            );
        }

        // Make sure we have at least one item that needs shipping
        if (empty($items)) {
            if ($this->debug) {
                do_action('teleship_log', 'Cannot prepare Teleship API request: No shippable items in cart.');
            }
            return false;
        }

        // Build the request data in the format expected by the API
        $request_data = array(
            'rate' => array(
                'origin' => $origin,
                'destination' => $destination,
                'items' => $items,
                'currency' => get_woocommerce_currency(),
                'presentmentCurrency' => get_woocommerce_currency(),
                'locale' => get_locale() ? substr(get_locale(), 0, 2) : 'en'
            )
        );

        if ($this->debug) {
            do_action('teleship_log', 'Teleship API Request data: ' . json_encode($request_data));
        }

        return $request_data;
    }

    /**
     * Call the Teleship API to get shipping rates
     *
     * @param array $request_data Request data.
     * @return object|false Response object or false on failure.
     */
    private function call_teleship_api($request_data) {
        // Build API URL with store URL as query parameter
        $store_url = wp_parse_url(get_site_url(), PHP_URL_HOST);
        $api_url = add_query_arg('storeUrl', $store_url, $this->api_endpoint);

        if ($this->debug) {
            do_action('teleship_log', 'Teleship API Request to: ' . $api_url);
            do_action('teleship_log', 'Request data: ' . json_encode($request_data));
        }

        // Start timing for performance tracking
        $start_time = microtime(true);

        // Make the API request using CloudFlare-friendly client
        $response = Teleship_HTTP_Client::post($api_url, $request_data);

        // Calculate request time
        $request_time = microtime(true) - $start_time;

        if ($this->debug) {
            do_action('teleship_log', sprintf('Teleship API Request completed in %.4f seconds', $request_time));
        }

        // Check for WP_Error
        if (is_wp_error($response)) {
            $error_message = $response->get_error_message();

            // Check for CloudFlare-specific issues
            if (Teleship_HTTP_Client::has_cloudflare_issues($response)) {
                $error_message = Teleship_HTTP_Client::get_troubleshooting_message();

                // Add admin notice for CloudFlare issues
                add_action('admin_notices', function() use ($error_message) {
                    if (current_user_can('manage_options')) {
                        echo '<div class="notice notice-warning is-dismissible">';
                        echo '<p><strong>' . esc_html__('Teleship CloudFlare Issue:', 'teleship-global-shipping') . '</strong></p>';
                        echo '<p>' . esc_html($error_message) . '</p>';
                        echo '</div>';
                    }
                });
            }

            if ($this->debug) {
                do_action('teleship_log', 'Teleship API Error: ' . $error_message);
            }
            return false;
        }

        // Check response code
        $response_code = wp_remote_retrieve_response_code($response);
        $response_body = wp_remote_retrieve_body($response);

        if ($response_code !== 200) {
            // Check for CloudFlare blocking
            if (Teleship_HTTP_Client::has_cloudflare_issues($response)) {
                $error_message = Teleship_HTTP_Client::get_troubleshooting_message();

                // Add admin notice for CloudFlare issues
                add_action('admin_notices', function() use ($error_message, $response_code) {
                    if (current_user_can('manage_options')) {
                        echo '<div class="notice notice-warning is-dismissible">';
                        /* translators: %d is the HTTP status code */
                        echo '<p><strong>' . esc_html(sprintf(__('Teleship CloudFlare Issue (HTTP %d):', 'teleship-global-shipping'), intval($response_code))) . '</strong></p>';
                        echo '<p>' . esc_html($error_message) . '</p>';
                        echo '</div>';
                    }
                });

                if ($this->debug) {
                    do_action('teleship_log', 'Teleship CloudFlare blocking detected: ' . $error_message);
                }
            } else {
                if ($this->debug) {
                    do_action('teleship_log', 'Teleship API Error: Unexpected response code ' . $response_code);
                    do_action('teleship_log', 'Response: ' . $response_body);
                }
            }
            return false;
        }

        // Decode response
        $response_data = json_decode($response_body);

        // Check if response is valid JSON
        if (json_last_error() !== JSON_ERROR_NONE) {
            if ($this->debug) {
                do_action('teleship_log', 'Teleship API Error: Invalid JSON response - ' . json_last_error_msg());
                do_action('teleship_log', 'Raw response: ' . $response_body);
            }
            return false;
        }

        if ($this->debug) {
            do_action('teleship_log', 'Teleship API Response: ' . $response_body);
        }

        return $response_data;
    }

    /**
     * Process the API response and add shipping rates
     *
     * @param object $response API response.
     */
    private function process_api_response($response) {
        // Check for error messages in the response
        if (isset($response->messages) && !empty($response->messages)) {
            foreach ($response->messages as $message) {
                if (isset($message->level) && $message->level === 'error') {
                    if ($this->debug) {
                        $error_msg = isset($message->message) ? $message->message : 'Unknown error';
                        $error_details = isset($message->details) ? implode('; ', $message->details) : '';
                        do_action('teleship_log', 'Teleship API Error: ' . $error_msg . ($error_details ? ' - ' . $error_details : ''));
                    }
                }
            }
        }

        // Check if we got valid rates
        if (!isset($response->rates) || empty($response->rates)) {
            if ($this->debug) {
                do_action('teleship_log', 'Teleship API returned no rates.');
            }
            $this->add_fallback_rate();
            return;
        }

        // Count how many rates we process
        $rate_count = 0;

        // Loop through rates and add them
        foreach ($response->rates as $rate) {
            // Skip if missing required fields
            if (!isset($rate->service_code) || !isset($rate->service_name) || !isset($rate->total_price)) {
                if ($this->debug) {
                    do_action('teleship_log', 'Teleship API rate missing required fields: ' . json_encode($rate));
                }
                continue;
            }

            // API returns price in cents, convert to decimal
            $rate_cost = (float) ($rate->total_price / 100);

            // Create unique rate ID
            $rate_id = $this->id . '_' . $rate->service_code;

            // Set meta data
            $meta_data = array(
                'teleship_service_code' => $rate->service_code,
                'teleship_service_name' => $rate->service_name,
            );

            // Add optional meta data if available
            if (isset($rate->description)) {
                $meta_data['teleship_description'] = $rate->description;
            }

            if (isset($rate->min_delivery_date)) {
                $meta_data['teleship_min_delivery_date'] = $rate->min_delivery_date;
            }

            if (isset($rate->max_delivery_date)) {
                $meta_data['teleship_max_delivery_date'] = $rate->max_delivery_date;
            }

            if (isset($rate->min_transit_days)) {
                $meta_data['teleship_min_transit_days'] = $rate->min_transit_days;
            }

            if (isset($rate->max_transit_days)) {
                $meta_data['teleship_max_transit_days'] = $rate->max_transit_days;
            }

            // Build the label with transit days if available
            $label = $rate->service_name;
            if (isset($rate->min_transit_days) && isset($rate->max_transit_days)) {
                // Format business days text
                if ($rate->min_transit_days == $rate->max_transit_days) {
                    $label .= ' (' . $rate->min_transit_days . ' business day' . ($rate->min_transit_days > 1 ? 's' : '') . ')';
                } else {
                    $label .= ' (' . $rate->min_transit_days . ' to ' . $rate->max_transit_days . ' business days)';
                }
            }

            // Add cost breakdown if available
            if (isset($rate->description) && !empty($rate->description)) {
                $label .= ' - ' . $rate->description;
            }

            // Add the rate
            $this->add_rate(array(
                'id' => $rate_id,
                'label' => $label,
                'cost' => $rate_cost,
                'meta_data' => $meta_data,
            ));

            $rate_count++;
        }

        if ($this->debug) {
            do_action('teleship_log', "Teleship API processed {$rate_count} shipping rates.");
        }

        // If no rates were added after processing, use fallback
        if ($rate_count === 0) {
            if ($this->debug) {
                do_action('teleship_log', 'Teleship API returned rates but none were valid. Using fallback.');
            }
            $this->add_fallback_rate();
        }
    }

    /**
     * Add fallback rate when API is unavailable
     */
    private function add_fallback_rate() {
        // Use only the global fallback rate
        $fallback_rate = get_option('teleship_fallback_rate');

        if (!empty($fallback_rate)) {
            $this->add_rate(array(
                'id' => $this->id . '_fallback',
                /* translators: %s is the estimated text appended to shipping method title when using fallback rate */
                'label' => $this->title . ' ' .
                /* translators: %s is the estimated text appended to shipping method title when using fallback rate */
                __('(Estimated)', 'teleship-global-shipping'),
                'cost' => $fallback_rate,
                'meta_data' => array(
                    'teleship_fallback' => true,
                ),
            ));

            if ($this->debug) {
                do_action('teleship_log', 'Teleship added fallback rate: ' . $fallback_rate);
            }
        } else {
            if ($this->debug) {
                do_action('teleship_log', 'Teleship no fallback rate available.');
            }
        }
    }
}
