<?php

namespace App\Modules\Procurement\Services;

use App\Modules\Procurement\Exceptions\ReceivingOperationalException;
use App\Modules\Procurement\Models\PurchaseOrder;
use App\Modules\Procurement\Models\PurchaseOrderItem;
use App\Modules\Procurement\Support\PurchaseOrderLifecycleStatus;
use Illuminate\Support\Facades\DB;

final class PurchaseOrderLifecycleService
{
    public function approve(int $tenantId, int $purchaseOrderId): void
    {
        DB::transaction(function () use ($tenantId, $purchaseOrderId): void {
            $po = $this->lockPo($tenantId, $purchaseOrderId);
            $from = PurchaseOrderLifecycleStatus::tryFrom((string) $po->status);
            if ($from === PurchaseOrderLifecycleStatus::Approved) {
                return;
            }
            if ($from !== PurchaseOrderLifecycleStatus::Draft) {
                throw new ReceivingOperationalException('Only draft POs can be approved.', 'po_approve_invalid');
            }
            $po->forceFill(['status' => PurchaseOrderLifecycleStatus::Approved->value])->save();
        });
    }

    public function cancel(int $tenantId, int $purchaseOrderId): void
    {
        DB::transaction(function () use ($tenantId, $purchaseOrderId): void {
            $po = $this->lockPo($tenantId, $purchaseOrderId);
            $from = PurchaseOrderLifecycleStatus::tryFrom((string) $po->status);
            if ($from === null) {
                throw new ReceivingOperationalException('Unknown PO status.', 'po_unknown_status');
            }
            if ($from === PurchaseOrderLifecycleStatus::Cancelled) {
                return;
            }
            if (! in_array($from, [PurchaseOrderLifecycleStatus::Draft, PurchaseOrderLifecycleStatus::Approved], true)) {
                throw new ReceivingOperationalException('Invalid PO cancel transition.', 'po_cancel_invalid');
            }
            $hasReceived = PurchaseOrderItem::withoutGlobalScopes()
                ->where('purchase_order_id', $po->id)
                ->whereNull('deleted_at')
                ->where('quantity_received_posted', '>', 0)
                ->exists();
            if ($hasReceived) {
                throw new ReceivingOperationalException('Cannot cancel PO with received quantity.', 'po_cancel_has_receipts');
            }
            $po->forceFill(['status' => PurchaseOrderLifecycleStatus::Cancelled->value])->save();
        });
    }

    public function assertPurchaseOrderAllowsReceiving(PurchaseOrder $po): void
    {
        $s = PurchaseOrderLifecycleStatus::tryFrom((string) $po->status);
        if ($s === null) {
            throw new ReceivingOperationalException('Unknown PO status.', 'po_unknown_status');
        }
        if (! in_array($s, [
            PurchaseOrderLifecycleStatus::Approved,
            PurchaseOrderLifecycleStatus::PartiallyReceived,
        ], true)) {
            throw new ReceivingOperationalException('Purchase order does not allow receiving.', 'po_not_receivable');
        }
    }

    private function lockPo(int $tenantId, int $purchaseOrderId): PurchaseOrder
    {
        $po = PurchaseOrder::withoutGlobalScopes()
            ->where('tenant_id', $tenantId)
            ->whereKey($purchaseOrderId)
            ->lockForUpdate()
            ->first();
        if ($po === null) {
            throw new ReceivingOperationalException('Purchase order not found.', 'po_not_found');
        }

        return $po;
    }

}
