<?php

namespace App\Support;

use App\Models\PurchaseItem;
use App\Models\PurchaseProduct;
use App\Models\StockMovement;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\DB;

class RawMaterialStock
{
    /**
     * @param  list<array{purchase_product_id: int, quantity: float|int|string}>  $items
     */
    public static function addFromItems(array $items): void
    {
        foreach ($items as $item) {
            self::adjust(
                (int) $item['purchase_product_id'],
                (float) $item['quantity'],
            );
        }
    }

    /**
     * @param  iterable<int, PurchaseItem>|Collection<int, PurchaseItem>  $items
     */
    public static function subtractFromPurchaseItems(iterable $items): void
    {
        foreach ($items as $item) {
            self::adjust((int) $item->purchase_product_id, -((float) $item->quantity));
        }
    }

    /**
     * Adjust stock by a delta in the product's purchase unit (converted to base when configured).
     *
     * @param  array{
     *     type: string,
     *     reference_type?: class-string|null,
     *     reference_id?: int|null,
     *     notes?: string|null
     * }|null  $movement
     */
    public static function adjust(int $productId, float $deltaInProductUnit, ?array $movement = null): void
    {
        if (abs($deltaInProductUnit) < 0.0000001) {
            return;
        }

        $product = PurchaseProduct::query()
            ->whereKey($productId)
            ->lockForUpdate()
            ->first();

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

        $delta = $product->quantityToBase($deltaInProductUnit);

        $product->stock_on_hand = round(max(0, (float) $product->stock_on_hand + $delta), 3);
        $product->save();

        StockMovement::record(
            StockKind::RAW_MATERIAL,
            $productId,
            $delta,
            (float) $product->stock_on_hand,
            $movement,
        );
    }

    public static function resetAll(): void
    {
        PurchaseProduct::query()->update(['stock_on_hand' => 0]);
    }

    public static function recalculateFromPurchases(): void
    {
        DB::transaction(function (): void {
            PurchaseProduct::query()->update(['stock_on_hand' => 0]);

            $items = PurchaseItem::query()
                ->with('product')
                ->get();

            /** @var array<int, float> $totalsBase */
            $totalsBase = [];

            foreach ($items as $item) {
                $productId = (int) $item->purchase_product_id;
                $product = $item->product;
                $baseQuantity = $product !== null
                    ? $product->quantityToBase((float) $item->quantity)
                    : (float) $item->quantity;

                $totalsBase[$productId] = round(
                    ($totalsBase[$productId] ?? 0) + $baseQuantity,
                    3,
                );
            }

            foreach ($totalsBase as $productId => $totalBase) {
                PurchaseProduct::query()
                    ->whereKey($productId)
                    ->update([
                        'stock_on_hand' => $totalBase,
                    ]);
            }
        });
    }
}
