<?php
/**
 * Pricing engine with priority-based rules
 *
 * @package WahraRentalsBookings
 */

defined('ABSPATH') || exit;

/**
 * Pricing engine class
 */
class WAHRB_Pricing_Engine {

    /**
     * Single instance
     *
     * @var WAHRB_Pricing_Engine|null
     */
    private static ?WAHRB_Pricing_Engine $instance = null;

    /**
     * Database instance
     *
     * @var WAHRB_Database
     */
    private WAHRB_Database $db;

    /**
     * Get single instance
     *
     * @return WAHRB_Pricing_Engine
     */
    public static function instance(): WAHRB_Pricing_Engine {
        if (is_null(self::$instance)) {
            self::$instance = new self();
        }
        return self::$instance;
    }

    /**
     * Constructor
     */
    private function __construct() {
        $this->db = WAHRB_Database::instance();
    }

    /**
     * Calculate total price for a booking
     *
     * @param int $product_id Product ID
     * @param string $start_date Start date
     * @param string $end_date End date
     * @param int|null $variation_id Variation ID
     * @param int $qty Quantity
     * @return array Pricing breakdown
     */
    public function calculate(
        int $product_id,
        string $start_date,
        string $end_date,
        ?int $variation_id = null,
        int $qty = 1
    ): array {
        // Get booking mode
        $booking_mode = get_post_meta($product_id, '_wahrb_booking_mode', true) ?: 'day';
        
        // Get base price (per day, per hour, or per session depending on mode)
        $base_price = $this->get_base_price($product_id, $variation_id);

        // Calculate start/end times
        $start = new DateTime($start_date);
        $end = new DateTime($end_date);
        $duration_seconds = $end->getTimestamp() - $start->getTimestamp();
        
        // Get applicable rules
        $rules = $this->get_applicable_rules($product_id, $variation_id);
        
        // Mode-specific calculations
        if ($booking_mode === 'slot') {
            // ============================================
            // SLOT MODE: Calculate per hour
            // ============================================
            $hours = max(1, ceil($duration_seconds / 3600));
            $unit_label = __('hour', 'wahra-rentals-bookings');
            $units = $hours;
            
            // Calculate price per hour with rules
            $hourly_prices = [];
            $rules_applied = [];
            $current = clone $start;
            
            for ($h = 0; $h < $hours; $h++) {
                $hour_price = $base_price;
                $hour_rules = [];
                
                foreach ($rules as $rule) {
                    if ($this->rule_matches($rule, $current, $hours)) {
                        $modifier = $this->apply_modifier($hour_price, $rule);
                        $hour_price = $modifier['new_price'];
                        $hour_rules[] = [
                            'rule_id' => $rule['rule_id'],
                            'name' => $rule['rule_name'],
                            'type' => $rule['rule_type'],
                            'modifier_type' => $rule['modifier_type'],
                            'modifier_value' => $rule['modifier_value'],
                            'applied_amount' => $modifier['applied_amount'],
                        ];
                    }
                }
                
                $hourly_prices[] = [
                    'hour' => $h + 1,
                    'time' => $current->format('H:i'),
                    'base_price' => $base_price,
                    'final_price' => $hour_price,
                    'rules_applied' => $hour_rules,
                ];
                
                // Track unique rules
                foreach ($hour_rules as $rule) {
                    $key = $rule['rule_id'];
                    if (!isset($rules_applied[$key])) {
                        $rules_applied[$key] = $rule;
                        $rules_applied[$key]['units_count'] = 0;
                        $rules_applied[$key]['total_impact'] = 0;
                    }
                    $rules_applied[$key]['units_count']++;
                    $rules_applied[$key]['total_impact'] += $rule['applied_amount'];
                }
                
                $current->modify('+1 hour');
            }
            
            $subtotal = array_sum(array_column($hourly_prices, 'final_price'));
            $unit_prices = $hourly_prices;
            
        } elseif ($booking_mode === 'service') {
            // ============================================
            // SERVICE MODE: Fixed price per session
            // ============================================
            $units = 1; // Always 1 session
            $unit_label = __('session', 'wahra-rentals-bookings');
            
            // Apply rules to session price
            $session_price = $base_price;
            $rules_applied = [];
            
            foreach ($rules as $rule) {
                if ($this->rule_matches($rule, $start, 1)) {
                    $modifier = $this->apply_modifier($session_price, $rule);
                    $session_price = $modifier['new_price'];
                    $rules_applied[$rule['rule_id']] = [
                        'rule_id' => $rule['rule_id'],
                        'name' => $rule['rule_name'],
                        'type' => $rule['rule_type'],
                        'modifier_type' => $rule['modifier_type'],
                        'modifier_value' => $rule['modifier_value'],
                        'applied_amount' => $modifier['applied_amount'],
                        'units_count' => 1,
                        'total_impact' => $modifier['applied_amount'],
                    ];
                }
            }
            
            $subtotal = $session_price;
            $unit_prices = [[
                'session' => 1,
                'base_price' => $base_price,
                'final_price' => $session_price,
                'rules_applied' => array_values($rules_applied),
            ]];
            
        } else {
            // ============================================
            // DAY MODE: Calculate per day (default)
            // ============================================
            $days = max(1, $start->diff($end)->days);
            $units = $days;
            $unit_label = __('day', 'wahra-rentals-bookings');
            
            // Calculate price per day
            $daily_prices = [];
            $rules_applied = [];
            $current = clone $start;

            while ($current < $end) {
                $date_str = $current->format('Y-m-d');
                $day_price = $base_price;
                $day_rules = [];

                foreach ($rules as $rule) {
                    if ($this->rule_matches($rule, $current, $days)) {
                        $modifier = $this->apply_modifier($day_price, $rule);
                        $day_price = $modifier['new_price'];
                        $day_rules[] = [
                            'rule_id' => $rule['rule_id'],
                            'name' => $rule['rule_name'],
                            'type' => $rule['rule_type'],
                            'modifier_type' => $rule['modifier_type'],
                            'modifier_value' => $rule['modifier_value'],
                            'applied_amount' => $modifier['applied_amount'],
                        ];
                    }
                }

                $daily_prices[$date_str] = [
                    'date' => $date_str,
                    'base_price' => $base_price,
                    'final_price' => $day_price,
                    'rules_applied' => $day_rules,
                ];

                // Track unique rules
                foreach ($day_rules as $rule) {
                    $key = $rule['rule_id'];
                    if (!isset($rules_applied[$key])) {
                        $rules_applied[$key] = $rule;
                        $rules_applied[$key]['units_count'] = 0;
                        $rules_applied[$key]['total_impact'] = 0;
                    }
                    $rules_applied[$key]['units_count']++;
                    $rules_applied[$key]['total_impact'] += $rule['applied_amount'];
                }

                $current->modify('+1 day');
            }
            
            $subtotal = array_sum(array_column($daily_prices, 'final_price'));
            $unit_prices = $daily_prices;
        }

        // Calculate totals
        $subtotal_qty = $subtotal * $qty;

        // Apply tax
        $tax_rate = $this->get_tax_rate($product_id);
        $tax_amount = round($subtotal_qty * ($tax_rate / 100), 2);
        $total = $subtotal_qty + $tax_amount;

        return [
            'booking_mode' => $booking_mode,
            'base_price_per_unit' => $base_price,
            'unit_label' => $unit_label,
            'units' => $units,
            'qty' => $qty,
            'unit_prices' => $unit_prices,
            'rules_applied' => array_values($rules_applied),
            'subtotal_per_unit' => $subtotal,
            'subtotal' => $subtotal_qty,
            'tax_rate' => $tax_rate,
            'tax_amount' => $tax_amount,
            'total' => $total,
            'currency' => get_woocommerce_currency(),
            'formatted_total' => wc_price($total),
        ];
    }

    /**
     * Get price for a single day
     *
     * @param int $product_id Product ID
     * @param string $date Date
     * @param int|null $variation_id Variation ID
     * @return array Day price info
     */
    public function get_day_price(int $product_id, string $date, ?int $variation_id = null): array {
        $base_price = $this->get_base_price($product_id, $variation_id);
        $rules = $this->get_applicable_rules($product_id, $variation_id);

        $day = new DateTime($date);
        $price = $base_price;
        $is_high_season = false;

        foreach ($rules as $rule) {
            if ($this->rule_matches($rule, $day, 1)) {
                $modifier = $this->apply_modifier($price, $rule);
                $price = $modifier['new_price'];

                if ($rule['rule_type'] === 'date_range' && $modifier['applied_amount'] > 0) {
                    $is_high_season = true;
                }
            }
        }

        return [
            'date' => $date,
            'base_price' => $base_price,
            'price' => $price,
            'is_high_season' => $is_high_season,
            'formatted_price' => wc_price($price),
        ];
    }

    /**
     * Get base price for product/variation
     *
     * @param int $product_id Product ID
     * @param int|null $variation_id Variation ID
     * @return float
     */
    private function get_base_price(int $product_id, ?int $variation_id = null): float {
        // Check variation price first
        if ($variation_id) {
            $variation_price = get_post_meta($variation_id, '_wahrb_base_price', true);
            if ($variation_price) {
                return floatval($variation_price);
            }
        }

        // Product price
        $product_price = get_post_meta($product_id, '_wahrb_base_price', true);
        if ($product_price) {
            return floatval($product_price);
        }

        // Fallback to WooCommerce price
        $product = wc_get_product($variation_id ?: $product_id);
        if ($product) {
            return floatval($product->get_price());
        }

        return 0;
    }

    /**
     * Get applicable pricing rules
     *
     * @param int $product_id Product ID
     * @param int|null $variation_id Variation ID
     * @return array Rules sorted by priority
     */
    private function get_applicable_rules(int $product_id, ?int $variation_id = null): array {
        global $wpdb;

        $where = 'product_id = %d AND active = 1';
        $params = [$product_id];

        if ($variation_id) {
            $where .= ' AND (variation_id IS NULL OR variation_id = %d)';
            $params[] = $variation_id;
        } else {
            $where .= ' AND variation_id IS NULL';
        }

        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, PluginCheck.Security.DirectDB.UnescapedDBParameter
        $rules = $wpdb->get_results($wpdb->prepare(
            "SELECT * FROM {$this->db->table('price_rules')}
             WHERE {$where}
             ORDER BY priority ASC",
            $params
        ), ARRAY_A);

        return $rules ?: [];
    }

    /**
     * Check if a rule matches for a given day
     *
     * @param array $rule Rule data
     * @param DateTime $date Date to check
     * @param int $duration Total duration in days
     * @return bool
     */
    private function rule_matches(array $rule, DateTime $date, int $duration): bool {
        switch ($rule['rule_type']) {
            case 'date_range':
                if (empty($rule['date_start']) || empty($rule['date_end'])) {
                    return false;
                }
                $start = new DateTime($rule['date_start']);
                $end = new DateTime($rule['date_end']);
                return $date >= $start && $date <= $end;

            case 'weekday':
                if (!empty($rule['weekdays'])) {
                    $weekdays = json_decode($rule['weekdays'], true);
                    return in_array((int) $date->format('N'), $weekdays);
                }
                if (!empty($rule['weekday'])) {
                    return (int) $date->format('N') === (int) $rule['weekday'];
                }
                return false;

            case 'duration':
                $min = $rule['min_duration_days'] ?? 0;
                $max = $rule['max_duration_days'] ?? PHP_INT_MAX;
                return $duration >= $min && $duration <= $max;

            case 'guests':
                // Future implementation
                return false;

            default:
                return false;
        }
    }

    /**
     * Apply modifier to price
     *
     * @param float $price Current price
     * @param array $rule Rule data
     * @return array New price and applied amount
     */
    private function apply_modifier(float $price, array $rule): array {
        $modifier_value = floatval($rule['modifier_value']);

        if ($rule['modifier_type'] === 'fixed') {
            $applied = $modifier_value;
            $new_price = $price + $modifier_value;
        } else { // percent
            $applied = round($price * ($modifier_value / 100), 2);
            $new_price = $price + $applied;
        }

        return [
            'new_price' => max(0, $new_price),
            'applied_amount' => $applied,
        ];
    }

    /**
     * Get tax rate for product
     *
     * @param int $product_id Product ID
     * @return float
     */
    private function get_tax_rate(int $product_id): float {
        // Check product-specific rate
        $product_rate = get_post_meta($product_id, '_wahrb_tax_rate', true);
        if ($product_rate !== '') {
            return floatval($product_rate);
        }

        // Check vendor settings
        $product = wc_get_product($product_id);
        if ($product) {
            $vendor_id = get_post_field('post_author', $product_id);
            $vendor_rate = get_user_meta($vendor_id, '_wahrb_tax_rate', true);
            if ($vendor_rate !== '') {
                return floatval($vendor_rate);
            }
        }

        // Use WooCommerce tax if enabled
        if (wc_tax_enabled()) {
            $tax_rates = WC_Tax::get_rates();
            if (!empty($tax_rates)) {
                $first_rate = reset($tax_rates);
                return floatval($first_rate['rate']);
            }
        }

        return 0;
    }

    /**
     * Preview pricing calculation (for wizard)
     *
     * @param int $product_id Product ID
     * @param float $base_price Base price to use
     * @param array $rules Rules to apply
     * @param int $example_days Example duration
     * @return array Preview data
     */
    public function preview(
        int $product_id,
        float $base_price,
        array $rules,
        int $example_days = 3
    ): array {
        $start = new DateTime('next monday');
        $end = clone $start;
        $end->modify("+{$example_days} days");

        $daily_prices = [];
        $current = clone $start;

        while ($current < $end) {
            $day_price = $base_price;

            foreach ($rules as $rule) {
                if ($this->rule_matches($rule, $current, $example_days)) {
                    $modifier = $this->apply_modifier($day_price, $rule);
                    $day_price = $modifier['new_price'];
                }
            }

            $daily_prices[] = [
                'date' => $current->format('Y-m-d'),
                'day_name' => $current->format('l'),
                'base' => $base_price,
                'final' => $day_price,
            ];

            $current->modify('+1 day');
        }

        $total = array_sum(array_column($daily_prices, 'final'));

        return [
            'example_duration' => $example_days,
            'daily_prices' => $daily_prices,
            'total' => $total,
            'formatted_total' => wc_price($total),
        ];
    }
}
