<?php

namespace Mtc\Modules\B2B\Services;

use Mtc\Modules\B2B\Enums\TradeCustomerStatus;
use Mtc\Modules\B2B\Models\TradeCustomer;
use Mtc\Modules\B2B\Models\TradeTier;
use Mtc\Shop\Events\BasketGetItemsEvent;
use Mtc\Shop\Events\ShopItemDataEvent;
use Mtc\Shop\Item;
use Mtc\Shop\Item\Size;

class TradePricingService
{
    /**
     * Per-request cache keyed by member_id.
     * Prevents repeated DB lookups when the same service instance is reused
     * across multiple basket items or displayed products within a single request.
     */
    private array $tradeCustomerCache = [];

    /**
     * Calculate the final trade price for an item (or size) given a tier.
     * Returns null if no trade price is set (caller should use retail price).
     * All prices are ex-VAT.
     */
    public function calculatePrice(Item $item, ?Size $size, TradeTier $tier): ?float
    {
        // Step 1: Get base trade price (size overrides item)
        $baseTradePrice = null;
        if ($size && $size->trade_price !== null) {
            $baseTradePrice = (float) $size->trade_price;
        } elseif ($item->trade_price !== null) {
            $baseTradePrice = (float) $item->trade_price;
        }

        // No trade price set — return null to signal use of retail price
        if ($baseTradePrice === null) {
            return null;
        }

        // Step 2: Apply tier discount
        $discountPercentage = (float) $tier->discount_percentage;
        $discountedPrice = $baseTradePrice * (1 - $discountPercentage / 100);

        // Step 3: Compare with trade sale price — customer gets the lower price
        $tradeSalePrice = null;
        if ($size && $size->trade_sale_price !== null) {
            $tradeSalePrice = (float) $size->trade_sale_price;
        } elseif ($item->trade_sale_price !== null) {
            $tradeSalePrice = (float) $item->trade_sale_price;
        }

        if ($tradeSalePrice !== null && $tradeSalePrice < $discountedPrice) {
            return round($tradeSalePrice, 2);
        }

        return round($discountedPrice, 2);
    }

    /**
     * Calculate the original trade price before tier discount (for display purposes).
     */
    public function getOriginalTradePrice(Item $item, ?Size $size): ?float
    {
        if ($size && $size->trade_price !== null) {
            return (float) $size->trade_price;
        }
        if ($item->trade_price !== null) {
            return (float) $item->trade_price;
        }
        return null;
    }

    /**
     * Calculate the savings percentage for display.
     */
    public function getSavingsPercentage(Item $item, ?Size $size, TradeTier $tier): ?float
    {
        $originalPrice = $this->getOriginalTradePrice($item, $size);
        $finalPrice = $this->calculatePrice($item, $size, $tier);

        if ($originalPrice === null || $finalPrice === null || $originalPrice <= 0) {
            return null;
        }

        if ($finalPrice >= $originalPrice) {
            return 0.0;
        }

        return round((($originalPrice - $finalPrice) / $originalPrice) * 100, 1);
    }

    /**
     * Convenience method: returns trade price, original price, and savings in one call.
     */
    public function getTradeDisplayData(Item $item, ?Size $size, TradeTier $tier): array
    {
        return [
            'price'    => $this->calculatePrice($item, $size, $tier),
            'original' => $this->getOriginalTradePrice($item, $size),
            'savings'  => $this->getSavingsPercentage($item, $size, $tier),
        ];
    }

    /**
     * Event handler for BasketGetItemsEvent.
     * Overrides item prices with trade-calculated prices for trade customers.
     * Recalculated on every basket load — no caching.
     *
     * IMPORTANT: The basket adds event response values to the base price
     * (see class.basket.php lines 990-995). We must return the DIFFERENCE
     * between trade price and retail price, not the absolute trade price.
     */
    public function overrideBasketPrices(BasketGetItemsEvent $event): ?array
    {
        $basket = $event->getBasket();
        $itemData = $event->getItem();

        $memberId = $basket->member ?? null;
        if (!$memberId) {
            return null;
        }

        $tradeCustomer = $this->getTradeCustomerForMember((int) $memberId);
        if (!$tradeCustomer || !$tradeCustomer->tier) {
            return null;
        }

        $item = Item::find($itemData['item_id']);
        if (!$item) {
            return null;
        }

        $size = null;
        if (!empty($itemData['sizeid'])) {
            $size = Size::find($itemData['sizeid']);
        }

        $tradePrice = $this->calculatePrice($item, $size, $tradeCustomer->tier);

        if ($tradePrice === null) {
            return null;
        }

        // The basket merges listener responses into $data['additional'] via array_merge,
        // then iterates each element checking for 'item_price' / 'item_price_exvat' keys.
        // Values are ADDED to the base price. To replace retail with trade price,
        // return the difference (trade - retail) as a nested addition entry.
        $currentPrice = (float) $itemData['item_price'];
        $currentPriceExVat = (float) $itemData['item_price_exvat'];

        return [
            [
                'item_price'        => $tradePrice - $currentPrice,
                'item_price_exvat'  => $tradePrice - $currentPriceExVat,
            ],
        ];
    }

    /**
     * Event handler for ShopItemDataEvent.
     * Mutates item price fields in-place so Vue receives trade prices
     * without any frontend changes.
     *
     * The $item may be either the Eloquent Mtc\Shop\Item (browse page) or
     * the legacy global Item class (detail page). We always load the Eloquent
     * model for trade_price access, then mutate the original object's display fields.
     */
    public function overrideItemPrices(ShopItemDataEvent $event): void
    {
        $member = \Mtc\Modules\Members\Classes\Auth::getLoggedInMember();
        $memberId = $member->exists ? $member->id : null;
        if (!$memberId) {
            return;
        }

        $tradeCustomer = $this->getTradeCustomerForMember((int) $memberId);
        if (!$tradeCustomer || !$tradeCustomer->tier) {
            return;
        }

        $displayItem = $event->getItem();
        $tier = $tradeCustomer->tier;

        $eloquentItem = ($displayItem instanceof Item)
            ? $displayItem
            : Item::find($displayItem->id);

        if (!$eloquentItem) {
            return;
        }

        // Check for sizes — use count() to handle both arrays and Eloquent Collections
        // (empty() returns false for empty Collections since they're objects)
        $hasSizes = is_array($displayItem->sizes)
            ? !empty($displayItem->sizes)
            : (isset($displayItem->sizes) && count($displayItem->sizes) > 0);

        if ($hasSizes) {
            // Batch-load all size models to avoid N+1 queries
            $sizeIds = $this->collectSizeIds($displayItem);
            $sizeModels = Size::whereIn('id', $sizeIds)->get()->keyBy('id');

            $lowestTradePrice = null;

            foreach ($displayItem->sizes as $k => $size) {
                $sizeId = is_array($size) ? ($size['id'] ?? null) : ($size->id ?? null);
                $sizeModel = $sizeModels[$sizeId] ?? null;
                if (!$sizeModel) {
                    continue;
                }

                $tradePrice = $this->calculatePrice($eloquentItem, $sizeModel, $tier);
                if ($tradePrice === null) {
                    continue;
                }

                if (is_array($size)) {
                    $displayItem->sizes[$k]['price'] = $tradePrice;
                    $displayItem->sizes[$k]['sale_price'] = 0;
                } else {
                    $size->price = $tradePrice;
                    $size->sale_price = 0;
                }

                if ($lowestTradePrice === null || $tradePrice < $lowestTradePrice) {
                    $lowestTradePrice = $tradePrice;
                }
            }

            // Also override size_strengths if present (detail page groups sizes by strength)
            if (!empty($displayItem->size_strengths)) {
                foreach ($displayItem->size_strengths as $strength => $sizes) {
                    foreach ($sizes as $k => $size) {
                        $sizeId = is_array($size) ? ($size['id'] ?? null) : ($size->id ?? null);
                        $sizeModel = $sizeModels[$sizeId] ?? null;
                        if (!$sizeModel) {
                            continue;
                        }

                        $tradePrice = $this->calculatePrice($eloquentItem, $sizeModel, $tier);
                        if ($tradePrice === null) {
                            continue;
                        }

                        $displayItem->size_strengths[$strength][$k]['price'] = $tradePrice;
                        $displayItem->size_strengths[$strength][$k]['sale_price'] = 0;
                    }
                }
            }

            if ($lowestTradePrice !== null) {
                $displayItem->from_price = $lowestTradePrice;
                $displayItem->price = $lowestTradePrice;
                $displayItem->sale_price = 0;
                $displayItem->percentage_off = 0;
            }
        } else {
            $tradePrice = $this->calculatePrice($eloquentItem, null, $tier);
            if ($tradePrice === null) {
                return;
            }

            $displayItem->price = $tradePrice;
            $displayItem->from_price = $tradePrice;
            $displayItem->sale_price = 0;
            $displayItem->percentage_off = 0;
        }
    }

    /**
     * Look up the approved trade customer for a member, cached per request.
     */
    private function getTradeCustomerForMember(int $memberId): ?TradeCustomer
    {
        if (!array_key_exists($memberId, $this->tradeCustomerCache)) {
            $this->tradeCustomerCache[$memberId] = TradeCustomer::where('member_id', $memberId)
                ->where('status', TradeCustomerStatus::Approved->value)
                ->with('tier')
                ->first();
        }

        return $this->tradeCustomerCache[$memberId];
    }

    /**
     * Collect all unique size IDs from $displayItem->sizes and ->size_strengths.
     */
    private function collectSizeIds(object $displayItem): array
    {
        $ids = [];

        foreach ($displayItem->sizes as $size) {
            $id = is_array($size) ? ($size['id'] ?? null) : ($size->id ?? null);
            if ($id) {
                $ids[] = $id;
            }
        }

        if (!empty($displayItem->size_strengths)) {
            foreach ($displayItem->size_strengths as $sizes) {
                foreach ($sizes as $size) {
                    $id = is_array($size) ? ($size['id'] ?? null) : ($size->id ?? null);
                    if ($id) {
                        $ids[] = $id;
                    }
                }
            }
        }

        return array_unique($ids);
    }
}
