<?php

namespace App\Modules\Procurement\Services;

use App\Modules\Procurement\Exceptions\GoodsReceiptIntegrityViolationException;
use App\Modules\Procurement\Models\GoodsReceipt;
use App\Modules\Procurement\Models\GoodsReceiptItem;
use App\Modules\Procurement\Models\PurchaseOrderItem;
use App\Modules\Procurement\Support\GoodsReceiptLifecycleStatus;
use Illuminate\Support\Facades\DB;

final class GoodsReceiptItemPersistenceService
{
    public function __construct(
        private readonly GoodsReceiptLineIntegrityValidator $validator,
    ) {}

    public function addLine(
        int $tenantId,
        int $goodsReceiptId,
        int $purchaseOrderItemId,
        string $quantityReceived,
    ): GoodsReceiptItem {
        return DB::transaction(function () use ($tenantId, $goodsReceiptId, $purchaseOrderItemId, $quantityReceived) {
            $receipt = GoodsReceipt::withoutGlobalScopes()
                ->where('tenant_id', $tenantId)
                ->whereKey($goodsReceiptId)
                ->lockForUpdate()
                ->first();
            if ($receipt === null) {
                throw new GoodsReceiptIntegrityViolationException('Goods receipt not found.', 'receipt_not_found');
            }
            $this->assertReceiptLinesMutable($receipt);
            $poi = PurchaseOrderItem::withoutGlobalScopes()
                ->withTrashed()
                ->where('tenant_id', $tenantId)
                ->whereKey($purchaseOrderItemId)
                ->lockForUpdate()
                ->first();
            if ($poi === null) {
                throw new GoodsReceiptIntegrityViolationException('Purchase order line not found.', 'missing_po_item');
            }
            $this->validator->assertWritableLine($receipt, $poi);

            return GoodsReceiptItem::withoutGlobalScopes()->create([
                'tenant_id' => $tenantId,
                'goods_receipt_id' => $goodsReceiptId,
                'purchase_order_item_id' => $purchaseOrderItemId,
                'quantity_received' => $quantityReceived,
            ]);
        });
    }

    public function updateLineQuantity(
        int $tenantId,
        int $goodsReceiptItemId,
        string $quantityReceived,
    ): GoodsReceiptItem {
        return DB::transaction(function () use ($tenantId, $goodsReceiptItemId, $quantityReceived) {
            $line = GoodsReceiptItem::withoutGlobalScopes()
                ->where('tenant_id', $tenantId)
                ->whereKey($goodsReceiptItemId)
                ->lockForUpdate()
                ->first();
            if ($line === null) {
                throw new GoodsReceiptIntegrityViolationException('Receipt line not found.', 'line_not_found');
            }
            if ($line->quarantined_at !== null) {
                throw new GoodsReceiptIntegrityViolationException('Quarantined line cannot be updated.', 'line_quarantined');
            }
            $receipt = GoodsReceipt::withoutGlobalScopes()
                ->where('tenant_id', $tenantId)
                ->whereKey($line->goods_receipt_id)
                ->lockForUpdate()
                ->first();
            if ($receipt === null) {
                throw new GoodsReceiptIntegrityViolationException('Goods receipt not found.', 'receipt_not_found');
            }
            $this->assertReceiptLinesMutable($receipt);
            $poi = PurchaseOrderItem::withoutGlobalScopes()
                ->withTrashed()
                ->where('tenant_id', $tenantId)
                ->whereKey($line->purchase_order_item_id)
                ->lockForUpdate()
                ->first();
            if ($poi === null) {
                throw new GoodsReceiptIntegrityViolationException('Purchase order line not found.', 'missing_po_item');
            }
            $this->validator->assertWritableLine($receipt, $poi);
            $line->forceFill(['quantity_received' => $quantityReceived])->save();

            return $line;
        });
    }

    private function assertReceiptLinesMutable(GoodsReceipt $receipt): void
    {
        $s = GoodsReceiptLifecycleStatus::tryFrom((string) $receipt->status);
        if ($s !== GoodsReceiptLifecycleStatus::Draft) {
            throw new GoodsReceiptIntegrityViolationException('Receipt lines are only editable in draft.', 'receipt_not_draft');
        }
    }
}
