<?php

declare(strict_types=1);

namespace Instinctor\Services;

use Instinctor\Core\Config;
use Instinctor\Core\DB;
use Instinctor\Core\CartSession;

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

/**
 * Cart Service - handles server-side cart session
 */
class CartService
{
    private const SESSION_KEY = 'cart';

    public function __construct() {}

    public function getCart(): array
    {
        return CartSession::get(self::SESSION_KEY) ?? [
            'items' => [],
            'bumps' => [],
            'coupon' => null,
            'shipping_address' => [],
            'shipping_method' => null,
            'totals' => [
                'subtotal' => 0,
                'bumps' => 0,
                'discount' => 0,
                'shipping' => 0,
                'tax' => 0,
                'total' => 0,
            ],
        ];
    }

    public function applyCoupon(array $coupon, float $discount): void
    {
        $cart = $this->getCart();
        $cart['coupon'] = $coupon;
        $cart['totals']['discount'] = $discount;
        $this->calculateTotals($cart);
        $this->saveCart($cart);
    }

    public function removeCoupon(): void
    {
        $cart = $this->getCart();
        $cart['coupon'] = null;
        $cart['totals']['discount'] = 0;
        $this->calculateTotals($cart);
        $this->saveCart($cart);
    }

    public function setShippingAddress(array $address): array
    {
        $cart = $this->getCart();
        $cart['shipping_address'] = $address;
        
        // Calculate available shipping methods
        $shippingService = new ShippingService();
        $shipping = $shippingService->calculateShipping($cart, $address);
        
        // Auto-select first method
        if (!empty($shipping['selected'])) {
            $cart['shipping_method'] = $shipping['selected'];
            $cart['totals']['shipping'] = $shipping['selected']['cost'];
        }
        
        $this->calculateTotals($cart);
        $this->saveCart($cart);
        
        return [
            'success' => true,
            'shipping' => $shipping,
            'cart' => $cart,
        ];
    }

    public function setShippingMethod(int $methodId): array
    {
        $cart = $this->getCart();
        
        $shippingService = new ShippingService();
        $method = $shippingService->getMethod($methodId);
        
        if (!$method) {
            return ['success' => false, 'error' => 'Invalid shipping method'];
        }
        
        $cart['shipping_method'] = [
            'id' => $method->id,
            'name' => $method->name,
            'type' => $method->method_type,
            'cost' => (float) $method->cost,
        ];
        
        $this->calculateTotals($cart);
        $this->saveCart($cart);
        
        return ['success' => true, 'cart' => $cart];
    }

    public function addBump(string $bumpId): array
    {
        $cart = $this->getCart();
        
        // Check if bump already added
        if (isset($cart['bumps'][$bumpId])) {
            return ['success' => false, 'error' => 'Already added'];
        }
        
        // Get bump config
        $bumps = CheckoutConfig::getOrderBumps();
        if (!isset($bumps[$bumpId]) || !$bumps[$bumpId]['enabled']) {
            return ['success' => false, 'error' => 'Invalid bump'];
        }
        
        $bump = $bumps[$bumpId];
        $productId = (int) $bump['product_id'];
        
        // Get product
        $product = DB::get_row(DB::prepare(
            "SELECT * FROM " . DB::prefix() . "instinctor_products WHERE id = %d AND status = 'published'",
            $productId
        ));
        
        if (!$product) {
            return ['success' => false, 'error' => 'Product not found'];
        }
        
        // Calculate bump price - handle case where only sale_price is set
        $regPrice = (float) $product->price;
        $saleP = $product->sale_price ? (float) $product->sale_price : 0;
        // Use sale if it's a valid sale (both prices, sale < regular), otherwise use effective price
        $hasSale = $saleP > 0 && $regPrice > 0 && $saleP < $regPrice;
        $originalPrice = $hasSale ? $saleP : ($regPrice > 0 ? $regPrice : $saleP);
            
        $bumpPrice = $originalPrice;
        if ($bump['discount_type'] === 'percent' && $bump['discount_value'] > 0) {
            $bumpPrice = $originalPrice * (1 - $bump['discount_value'] / 100);
        } elseif ($bump['discount_type'] === 'fixed' && $bump['discount_value'] > 0) {
            $bumpPrice = max(0, $originalPrice - $bump['discount_value']);
        }
        
        $cart['bumps'][$bumpId] = [
            'bump_id' => $bumpId,
            'product_id' => $productId,
            'name' => $product->name,
            'original_price' => $originalPrice,
            'price' => round($bumpPrice, 2),
            'image' => $product->featured_image ? wp_get_attachment_image_url($product->featured_image, 'thumbnail') : '',
        ];
        
        $this->calculateTotals($cart);
        $this->saveCart($cart);
        
        return ['success' => true, 'cart' => $cart];
    }

    public function removeBump(string $bumpId): array
    {
        $cart = $this->getCart();
        
        if (isset($cart['bumps'][$bumpId])) {
            unset($cart['bumps'][$bumpId]);
            $this->calculateTotals($cart);
            $this->saveCart($cart);
        }
        
        return ['success' => true, 'cart' => $cart];
    }

    public function addItem(int $productId, int $quantity = 1, array $meta = []): array
    {
        // Synthetic / custom line (e.g. the standalone Configurator block) — no
        // products row. The caller supplies a display name + unit base price in
        // meta; we build a self-contained line. The resulting order line carries
        // product_id 0 and won't reference a catalogue product (by design).
        if ($productId <= 0) {
            return $this->addCustomLine($quantity, $meta);
        }

        $table = DB::prefix() . 'instinctor_products';

        $product = DB::get_row(DB::prepare(
            "SELECT * FROM $table WHERE id = %d AND status = 'published'",
            $productId
        ));

        if (!$product) {
            return ['success' => false, 'error' => 'Product not found'];
        }

        // Check stock
        if ($product->manage_stock && $product->stock_quantity < $quantity) {
            return ['success' => false, 'error' => 'Insufficient stock'];
        }

        $cart = $this->getCart();
        $itemKey = $this->generateItemKey($productId, $meta);
        
        // Use per-product pricing when available for current currency
        // Handle case where only sale_price is set (no regular price)
        $regPrice = (float) $product->price;
        $saleP = $product->sale_price ? (float) $product->sale_price : 0;
        $hasSale = $saleP > 0 && $regPrice > 0 && $saleP < $regPrice;
        
        // Determine which price field to use for conversion
        $priceField = $hasSale ? 'sale_price' : ($regPrice > 0 ? 'price' : 'sale_price');
        $basePrice = \Instinctor\Services\CurrencyService::convertProductPrice($product, $priceField);

        // Apply volume discount if applicable
        $volumeResult = \Instinctor\Services\VolumeDiscountService::getDiscountedPrice($productId, $basePrice, $quantity);
        $unitPrice = $volumeResult['unit_price'];

        // Apply product add-on pricing (pre-calculated at cart submission time)
        $addonTotal = floatval($meta['addon_price'] ?? 0);

        $price = round($unitPrice + $addonTotal, 2);

        // Store the currency code with the cart item for reference
        $currentCurrency = \Instinctor\Services\CurrencyService::getCurrentCurrency();

        if (isset($cart['items'][$itemKey])) {
            $cart['items'][$itemKey]['quantity'] += $quantity;
            // Recalculate price with new quantity (volume tier may change)
            $newQty = $cart['items'][$itemKey]['quantity'];
            $vr = \Instinctor\Services\VolumeDiscountService::getDiscountedPrice($productId, $basePrice, $newQty);
            $cart['items'][$itemKey]['price'] = round($vr['unit_price'] + $addonTotal, 2);
            $cart['items'][$itemKey]['base_price'] = $basePrice;
            $cart['items'][$itemKey]['currency'] = $currentCurrency->code;
        } else {
            $cart['items'][$itemKey] = [
                'product_id'  => $productId,
                'name'        => $product->name,
                'price'       => $price,
                'base_price'  => $basePrice,
                'quantity'    => $quantity,
                'image'       => $product->featured_image ? wp_get_attachment_image_url($product->featured_image, 'thumbnail') : '',
                'meta'        => $meta,
                'currency'    => $currentCurrency->code,
            ];
        }

        $this->calculateTotals($cart);
        $this->saveCart($cart);

        return ['success' => true, 'cart' => $cart];
    }

    /**
     * Build a synthetic cart line with no catalogue product (product_id 0).
     *
     * Used by the Configurator block: the "size" price is the line base and the
     * toppings are an add-on delta, both passed in $meta. Two identical builds
     * (same meta) merge into one line and bump the quantity; a different build is
     * a distinct line. There is no stock check (nothing to track) and no currency
     * conversion (prices are authored in the store currency).
     *
     * @param array $meta Expects: custom_name, custom_price (size base),
     *                    addon_price (toppings), custom_image (optional), plus the
     *                    built spec (configurator, size) carried through to the order.
     */
    private function addCustomLine(int $quantity, array $meta): array
    {
        $quantity = max(1, $quantity);

        $name = trim((string) ($meta['custom_name'] ?? ''));
        if ($name === '') $name = 'Custom item';

        $base  = round((float) ($meta['custom_price'] ?? 0), 2);
        $addon = round((float) ($meta['addon_price']  ?? 0), 2);
        $unit  = round($base + $addon, 2);

        if ($unit <= 0) {
            return ['success' => false, 'error' => 'Invalid price'];
        }

        $image = (string) ($meta['custom_image'] ?? '');

        $cart    = $this->getCart();
        $itemKey = $this->generateItemKey(0, $meta);

        $currentCurrency = \Instinctor\Services\CurrencyService::getCurrentCurrency();

        if (isset($cart['items'][$itemKey])) {
            $cart['items'][$itemKey]['quantity'] += $quantity;
        } else {
            $cart['items'][$itemKey] = [
                'product_id' => 0,
                'name'       => $name,
                'price'      => $unit,
                'base_price' => $base,
                'quantity'   => $quantity,
                'image'      => $image,
                'meta'       => $meta,
                'currency'   => $currentCurrency->code,
            ];
        }

        $this->calculateTotals($cart);
        $this->saveCart($cart);

        return ['success' => true, 'cart' => $cart];
    }

    public function updateItem(string $itemKey, int $quantity): array
    {
        $cart = $this->getCart();

        if (!isset($cart['items'][$itemKey])) {
            return ['success' => false, 'error' => 'Item not found'];
        }

        if ($quantity <= 0) {
            return $this->removeItem($itemKey);
        }

        // Check stock
        $table = DB::prefix() . 'instinctor_products';
        $productId = $cart['items'][$itemKey]['product_id'];
        
        $product = DB::get_row(DB::prepare(
            "SELECT manage_stock, stock_quantity FROM $table WHERE id = %d",
            $productId
        ));

        if ($product && $product->manage_stock && $product->stock_quantity < $quantity) {
            return ['success' => false, 'error' => 'Insufficient stock'];
        }

        $cart['items'][$itemKey]['quantity'] = $quantity;

        // Recalculate unit price with new quantity (volume tier may change)
        $basePrice = $cart['items'][$itemKey]['base_price'] ?? $cart['items'][$itemKey]['price'];
        $meta      = $cart['items'][$itemKey]['meta'] ?? [];
        $addonTotal = floatval($meta['addon_price'] ?? 0);
        $vr = \Instinctor\Services\VolumeDiscountService::getDiscountedPrice($productId, $basePrice, $quantity);
        $cart['items'][$itemKey]['price'] = round($vr['unit_price'] + $addonTotal, 2);

        $this->calculateTotals($cart);
        $this->saveCart($cart);

        return ['success' => true, 'cart' => $cart];
    }

    public function removeItem(string $itemKey): array
    {
        $cart = $this->getCart();

        if (isset($cart['items'][$itemKey])) {
            unset($cart['items'][$itemKey]);
            $this->calculateTotals($cart);
            $this->saveCart($cart);
        }

        return ['success' => true, 'cart' => $cart];
    }

    public function clear(): array
    {
        $cart = [
            'items' => [],
            'totals' => [
                'subtotal' => 0,
                'tax' => 0,
                'shipping' => 0,
                'total' => 0,
            ],
        ];
        
        $this->saveCart($cart);
        return ['success' => true, 'cart' => $cart];
    }

    public function getItemCount(): int
    {
        $cart = $this->getCart();
        $count = 0;
        
        foreach ($cart['items'] as $item) {
            $count += $item['quantity'];
        }
        
        return $count;
    }

    private function calculateTotals(array &$cart): void
    {
        $subtotal = 0;

        foreach ($cart['items'] as $item) {
            $subtotal += $item['price'] * $item['quantity'];
        }

        $cart['totals']['subtotal'] = round($subtotal, 2);
        
        // Calculate bumps total
        $bumpsTotal = 0;
        foreach ($cart['bumps'] ?? [] as $bump) {
            $bumpsTotal += $bump['price'];
        }
        $cart['totals']['bumps'] = round($bumpsTotal, 2);
        
        // Recalculate coupon discount if coupon exists
        if (!empty($cart['coupon'])) {
            $couponService = new CouponService();
            $coupon = $couponService->getCouponById($cart['coupon']['id']);
            if ($coupon) {
                $cart['totals']['discount'] = $couponService->calculateDiscount($coupon, $cart);
                
                // Check if coupon grants free shipping
                if ($coupon->shipping) {
                    $cart['coupon']['shipping'] = true;
                }
            }
        }

        // Apply bundle discounts — check active bundles against cart contents
        $cart['totals']['bundle_discount'] = 0;
        $cart['bundle_discounts'] = [];
        $cartProductIds = array_column(array_values($cart['items']), 'product_id');
        $activeBundles = \Instinctor\Services\ProductBundleService::getBundles();
        foreach ($activeBundles as $bundle) {
            if (!intval($bundle['is_active'])) continue;
            $bundleProducts = json_decode($bundle['products'] ?? '[]', true) ?: [];
            if (empty($bundleProducts)) continue;
            // Check all bundle products are present in cart
            $matched = true;
            foreach ($bundleProducts as $pid) {
                if (!in_array(intval($pid), array_map('intval', $cartProductIds), true)) {
                    $matched = false;
                    break;
                }
            }
            if (!$matched) continue;
            // Calculate discount for matched bundle
            $bundleSubtotal = 0;
            foreach ($bundleProducts as $pid) {
                foreach ($cart['items'] as $item) {
                    if (intval($item['product_id']) === intval($pid)) {
                        $bundleSubtotal += $item['price'];
                        break;
                    }
                }
            }
            if ($bundle['discount_type'] === 'percent') {
                $bundleDisc = round($bundleSubtotal * (floatval($bundle['discount_value']) / 100), 2);
            } else {
                $bundleDisc = min(round(floatval($bundle['discount_value']), 2), $bundleSubtotal);
            }
            if ($bundleDisc <= 0) continue;
            $cart['totals']['bundle_discount'] += $bundleDisc;
            $cart['bundle_discounts'][] = [
                'id'       => intval($bundle['id']),
                'name'     => $bundle['name'],
                'discount' => $bundleDisc,
            ];
        }

        $discount = ($cart['totals']['discount'] ?? 0) + $cart['totals']['bundle_discount'];
        
        // Shipping - use selected method or 0
        $shipping = 0;
        if (!empty($cart['shipping_method']) && empty($cart['coupon']['shipping'])) {
            $shipping = $cart['shipping_method']['cost'] ?? 0;
        }
        $cart['totals']['shipping'] = round($shipping, 2);
        
        // Tax calculation using TaxService
        $taxEnabled = Config::get('instinctor_tax_enabled', false);
        if ($taxEnabled && !empty($cart['shipping_address'])) {
            $taxService = new TaxService();
            $taxableAmount = max(0, $subtotal + $bumpsTotal - $discount);
            $taxResult = $taxService->calculateTax($taxableAmount, $cart['shipping_address'], true, $shipping);
            $cart['totals']['tax'] = $taxResult['total'];
            $cart['totals']['tax_rates'] = $taxResult['rates'];
        } else {
            $cart['totals']['tax'] = 0;
            $cart['totals']['tax_rates'] = [];
        }
        
        $cart['totals']['total'] = round(
            $cart['totals']['subtotal'] + $cart['totals']['bumps'] - $discount + $cart['totals']['shipping'] + $cart['totals']['tax'],
            2
        );
    }

    private function saveCart(array $cart): void
    {
        CartSession::set(self::SESSION_KEY, $cart);
    }

    private function generateItemKey(int $productId, array $meta = []): string
    {
        return md5($productId . serialize($meta));
    }
}
