<?php

namespace App\Services;

use App\Exceptions\InsufficientStockException;
use App\Models\BranchStock;
use App\Models\Catalog\ProductBundle;
use App\Models\Scopes\BranchScope;

class BundleService
{
    /**
     * Expand a bundle into its constituent components scaled by quantity.
     *
     * Components are priced from their branch's FEFO-front sellable lot —
     * the same lot their sale will actually draw from — so the bundle's
     * price distribution and the movement allocation can never disagree.
     * A component with no sellable lot (no stock, or lots without a price)
     * makes the bundle unsellable.
     *
     * @return array<int, array{product_id:int, quantity:float, unit_id:?int, sale_price:float, batch_id:int}>
     */
    public function expand(ProductBundle $bundle, float $qty, int $branchId): array
    {
        return $bundle->items->map(function ($item) use ($qty, $branchId) {
            $lot = $this->sellableLot((int) $item->product_id, $branchId);

            return [
                'product_id' => $item->product_id,
                'quantity' => (float) $item->quantity * $qty,
                'unit_id' => $item->unit_id,
                'sale_price' => $lot['sale_price'],
                'batch_id' => $lot['batch_id'],
            ];
        })->all();
    }

    /** Sum of each component's own FEFO-front lot price × its quantity in the bundle (qty=1). */
    public function componentsRetailTotal(ProductBundle $bundle, int $branchId): float
    {
        return (float) $bundle->items->sum(function ($item) use ($branchId) {
            return $this->sellableLot((int) $item->product_id, $branchId)['sale_price'] * (float) $item->quantity;
        });
    }

    /**
     * The FEFO-front sellable lot of a product at a branch — the price and
     * allocation source for every bundle sale.
     *
     * @return array{batch_id: int, sale_price: float}
     */
    private function sellableLot(int $productId, int $branchId): array
    {
        $lot = app(BatchService::class)->sellableFor($productId, $branchId)->first();

        if (! $lot) {
            throw new InsufficientStockException(
                branchId: $branchId,
                productId: $productId,
                available: 0,
                requested: 0,
                message: "Product #{$productId} has no sellable lot at this branch — nothing to bundle from.",
            );
        }

        return ['batch_id' => $lot->id, 'sale_price' => (float) $lot->sale_price];
    }

    /**
     * @throws InsufficientStockException
     */
    public function validateStock(ProductBundle $bundle, int $branchId, float $qty): void
    {
        foreach ($bundle->items as $item) {
            $needed = (float) $item->quantity * $qty;
            $available = (float) (BranchStock::query()
                ->withoutGlobalScope(BranchScope::class)
                ->where('branch_id', $branchId)
                ->where('product_id', $item->product_id)
                ->value('quantity') ?? 0);

            if ($available < $needed) {
                throw new InsufficientStockException(
                    branchId: $branchId,
                    productId: $item->product_id,
                    available: $available,
                    requested: $needed,
                );
            }
        }
    }
}
