<?php

namespace App\Services;

use App\Models\InventoryMovement;
use App\Models\InventoryProduct;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
use InvalidArgumentException;

/**
 * Registra movimientos y mantiene stock_quantity en transacción (columna cacheada en inventory_products).
 */
class InventoryMovementService
{
    /**
     * @throws InvalidArgumentException
     */
    public function record(
        InventoryProduct $product,
        string $type,
        string $quantityInput,
        ?string $reference = null,
        ?int $userId = null
    ): InventoryMovement {
        return DB::transaction(function () use ($product, $type, $quantityInput, $reference, $userId): InventoryMovement {
            return $this->recordMovementWithoutTransaction($product, $type, $quantityInput, $reference, $userId);
        });
    }

    /**
     * Misma lógica que {@see record} pero sin abrir transacción: el llamador debe estar dentro de `DB::transaction`.
     * Usado por documentos agregados (ADR-002) para confirmar varias líneas atómicamente.
     *
     * @throws InvalidArgumentException
     */
    public function recordMovementWithoutTransaction(
        InventoryProduct $product,
        string $type,
        string $quantityInput,
        ?string $reference = null,
        ?int $userId = null
    ): InventoryMovement {
        $signed = $this->computeSignedDelta($type, $quantityInput);

        $locked = InventoryProduct::query()->lockForUpdate()->findOrFail($product->id);

        $current = (string) $locked->stock_quantity;
        $newStock = bcadd($current, $signed, 4);

        if (bccomp($newStock, '0', 4) < 0) {
            throw new InvalidArgumentException('Stock insuficiente para esta salida.');
        }

        $locked->stock_quantity = $newStock;
        $locked->save();

        return InventoryMovement::query()->create([
            'inventory_product_id' => $locked->id,
            'type' => $type,
            'quantity' => $signed,
            'reference' => $reference,
            'user_id' => $userId ?? Auth::id() ?? throw new InvalidArgumentException('Usuario no autenticado.'),
        ]);
    }

    /**
     * Cantidad firmada aplicada al stock: entrada positiva, salida negativa, ajuste según signo.
     */
    private function computeSignedDelta(string $type, string $quantityInput): string
    {
        $q = trim($quantityInput);
        if ($q === '') {
            throw new InvalidArgumentException('La cantidad es obligatoria.');
        }

        if ($type === InventoryMovement::TYPE_IN) {
            if (bccomp($q, '0', 4) <= 0) {
                throw new InvalidArgumentException('La entrada debe ser mayor que cero.');
            }

            return $q;
        }

        if ($type === InventoryMovement::TYPE_OUT) {
            if (bccomp($q, '0', 4) <= 0) {
                throw new InvalidArgumentException('La salida debe ser mayor que cero.');
            }

            return bcsub('0', $q, 4);
        }

        if ($type === InventoryMovement::TYPE_ADJUSTMENT) {
            if (bccomp($q, '0', 4) === 0) {
                throw new InvalidArgumentException('El ajuste no puede ser cero.');
            }

            return $q;
        }

        throw new InvalidArgumentException('Tipo de movimiento no válido.');
    }
}
