<?php

namespace Modules\VetClinic\Entities\Services;

use App\PurchaseLine;
use App\Utils\ProductUtil;
use Illuminate\Support\Facades\DB;
use Modules\VetClinic\Entities\VcMedicine;
use Modules\VetClinic\Entities\VcPrescriptionItem;

/**
 * Bridge between the vet pharmacy catalogue (vc_medicines) and the ERP
 * inventory (purchase_lines + variation_location_details).
 *
 *   - fefoSuggestions()  - sorted list of available batches for an item
 *                          (first expired, first out), with qty_available
 *                          and expiry_date for the picker UI.
 *   - dispense()          - decrements ERP stock for a chosen purchase_line
 *                          + stamps the prescription item with the lot ref.
 *
 * Controlled-drug movements are not added here — the existing controlled
 * register already auto-syncs OUT rows when a prescription with a
 * controlled medicine is finalised.
 */
class VcMedicineDispenseService
{
    /**
     * Return purchase_lines that still have qty for the medicine's linked ERP
     * product, sorted FEFO (earliest exp_date first; nulls last).
     *
     * @return \Illuminate\Support\Collection<int, object>
     */
    public function fefoSuggestions(VcMedicine $medicine, ?int $locationId = null)
    {
        if (empty($medicine->product_id)) {
            return collect();
        }

        $rows = PurchaseLine::query()
            ->where('product_id', $medicine->product_id)
            ->whereColumn('quantity', '>', 'quantity_sold')      // still has stock
            ->orderByRaw('CASE WHEN exp_date IS NULL THEN 1 ELSE 0 END, exp_date ASC')
            ->orderBy('id')
            ->get([
                'id', 'product_id', 'variation_id', 'transaction_id',
                'lot_number', 'mfg_date', 'exp_date',
                'quantity', 'quantity_sold', 'quantity_returned',
                'purchase_price', 'purchase_price_inc_tax',
            ]);

        return $rows->map(function ($r) {
            $available = (float) $r->quantity
                       - (float) $r->quantity_sold
                       - (float) ($r->quantity_returned ?? 0);
            return (object) [
                'purchase_line_id' => $r->id,
                'variation_id'     => $r->variation_id,
                'lot_number'       => $r->lot_number,
                'mfg_date'         => $r->mfg_date,
                'exp_date'         => $r->exp_date,
                'qty_available'    => max(0.0, $available),
            ];
        })->filter(fn($x) => $x->qty_available > 0)->values();
    }

    /**
     * Dispense `$qty` of the prescription item against the chosen
     * purchase_line. Decrements ERP stock + stamps the item.
     *
     * Returns the updated item. Throws on validation errors so callers can
     * map them into a JSON 422 response.
     */
    public function dispense(
        VcPrescriptionItem $item,
        int $purchaseLineId,
        float $qty,
        int $locationId,
        ?int $userId = null
    ): VcPrescriptionItem {
        if ($qty <= 0) {
            throw new \InvalidArgumentException('Dispense quantity must be positive.');
        }
        if (!$item->medicine_id) {
            throw new \InvalidArgumentException('Prescription item is not linked to a medicine catalogue entry.');
        }

        $medicine = VcMedicine::findOrFail($item->medicine_id);
        if (empty($medicine->product_id)) {
            throw new \InvalidArgumentException("The medicine '{$medicine->name}' is not linked to an ERP product.");
        }

        $line = PurchaseLine::where('id', $purchaseLineId)
            ->where('product_id', $medicine->product_id)
            ->first();
        if (!$line) {
            throw new \InvalidArgumentException('That batch does not match the prescribed medicine.');
        }

        $available = (float) $line->quantity
                   - (float) $line->quantity_sold
                   - (float) ($line->quantity_returned ?? 0);
        if ($qty > $available + 0.0001) {
            throw new \InvalidArgumentException(
                "Not enough stock in this batch — only " . rtrim(rtrim(number_format($available, 4, '.', ''), '0'), '.') . " available."
            );
        }

        return DB::transaction(function () use ($item, $line, $qty, $locationId, $userId) {
            // Decrement ERP stock for the variation at this location.
            app(ProductUtil::class)->decreaseProductQuantity(
                $line->product_id,
                $line->variation_id,
                $locationId,
                $qty
            );

            // Advance the lot's quantity_sold so subsequent FEFO suggestions
            // reflect the new remaining qty.
            $line->quantity_sold = (float) $line->quantity_sold + $qty;
            $line->save();

            $item->update([
                'purchase_line_id' => $line->id,
                'dispensed_qty'    => $qty,
                'dispensed_at'     => now(),
                'dispensed_by'     => $userId,
                'location_id'      => $locationId,
            ]);

            return $item->fresh();
        });
    }
}
