<?php

namespace App\Services;

use App\Models\WarehouseStock;
use App\Models\StockLedger;
use Illuminate\Support\Facades\DB;

/**
 * InventoryCostService
 *
 * Handles Weighted Average Cost (WAC) calculation.
 * Formula: New Avg Cost = (Old Qty × Old Cost + New Qty × New Cost) / (Old Qty + New Qty)
 *
 * IMPORTANT: Never call DB queries outside of a transaction when updating costs.
 */
class InventoryCostService
{
    /**
     * Calculate and return new weighted average cost after receiving stock.
     */
    public function calculateWeightedAvgCost(
        int $productId,
        int $warehouseId,
        string|int|float $incomingQty,
        string|int|float $incomingCost
    ): string {
        $stock = WarehouseStock::where('product_id', $productId)
            ->where('warehouse_id', $warehouseId)
            ->lockForUpdate()
            ->first();

        if (!$stock || bccomp((string) $stock->qty, '0', 4) !== 1) {
            return bcadd((string) $incomingCost, '0', 4);
        }

        $oldQty = (string) $stock->qty;
        $oldCost = (string) $stock->avg_cost;
        $totalQty = bcadd($oldQty, (string) $incomingQty, 4);
        $totalValue = bcadd(bcmul($oldQty, $oldCost, 8), bcmul((string) $incomingQty, (string) $incomingCost, 8), 8);

        return bccomp($totalQty, '0', 4) === 1 ? bcdiv($totalValue, $totalQty, 4) : bcadd((string) $incomingCost, '0', 4);
    }

    /**
     * Update the warehouse stock summary record.
     * This must be called inside a DB::transaction().
     */
    public function updateWarehouseStock(
        int $productId,
        int $warehouseId,
        string|int|float $qtyChange,
        string|int|float $newAvgCost
    ): WarehouseStock {
        return WarehouseStock::updateOrCreate(
            ['product_id' => $productId, 'warehouse_id' => $warehouseId],
            [
                'qty'      => DB::raw("qty + " . $qtyChange),
                'avg_cost' => $newAvgCost,
            ]
        );
    }

    /**
     * Decrease stock (for sales, transfers out, adjustments).
     * Cost stays the same (WAC is not recalculated on decrease).
     */
    public function decreaseWarehouseStock(
        int $productId,
        int $warehouseId,
        string|int|float $qtyDecrease
    ): void {
        $quantity = bcadd((string) $qtyDecrease, '0', 4);
        $stock = WarehouseStock::where('product_id', $productId)
            ->where('warehouse_id', $warehouseId)
            ->lockForUpdate()
            ->first();

        if ($stock) {
            $stock->qty = bcsub((string) $stock->qty, $quantity, 4);
            $stock->save();
            return;
        }

        WarehouseStock::create([
            'product_id' => $productId,
            'warehouse_id' => $warehouseId,
            'qty' => bcsub('0', $quantity, 4),
            'avg_cost' => '0.0000',
        ]);
    }

    /**
     * Get current average cost for a product in a warehouse.
     */
    public function getAvgCost(int $productId, int $warehouseId): string
    {
        $stock = WarehouseStock::where('product_id', $productId)
            ->where('warehouse_id', $warehouseId)
            ->first();
        return $stock ? (string) $stock->avg_cost : '0.0000';
    }

    /**
     * Get running quantity (current base qty) for a product in a warehouse.
     */
    public function getRunningQty(int $productId, int $warehouseId): string
    {
        $stock = WarehouseStock::where('product_id', $productId)
            ->where('warehouse_id', $warehouseId)
            ->first();
        return $stock ? (string) $stock->qty : '0.0000';
    }
}
