<?php

namespace App\Modules\Procurement\Services;

use App\Modules\Procurement\Exceptions\ReceivingOperationalException;
use App\Modules\Procurement\Models\GoodsReceipt;
use App\Modules\Procurement\Models\PurchaseOrder;
use App\Modules\Procurement\Support\GoodsReceiptLifecycleStatus;
use Illuminate\Support\Facades\DB;

final class GoodsReceiptLifecycleService
{
    public function __construct(
        private readonly ReceivingValidationService $validation,
        private readonly ProcurementReceivingObservability $obs,
    ) {}

    public function markValidated(int $tenantId, int $goodsReceiptId): void
    {
        DB::transaction(function () use ($tenantId, $goodsReceiptId): void {
            $receipt = GoodsReceipt::withoutGlobalScopes()
                ->where('tenant_id', $tenantId)
                ->whereKey($goodsReceiptId)
                ->lockForUpdate()
                ->first();
            if ($receipt === null) {
                throw new ReceivingOperationalException('Goods receipt not found.', 'receipt_not_found');
            }
            $actual = GoodsReceiptLifecycleStatus::tryFrom((string) $receipt->status);
            if ($actual === GoodsReceiptLifecycleStatus::Validated) {
                return;
            }
            if ($actual !== GoodsReceiptLifecycleStatus::Draft) {
                throw new ReceivingOperationalException('Only draft receipts can be validated.', 'receipt_validate_invalid');
            }

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

            $lines = $receipt->items()->get();
            $this->validation->assertReceiptLinesReceivable($receipt, $po, $lines);

            $receipt->forceFill(['status' => GoodsReceiptLifecycleStatus::Validated->value])->save();
        });
    }

    public function markFailed(int $tenantId, int $goodsReceiptId, string $reasonCode): void
    {
        DB::transaction(function () use ($tenantId, $goodsReceiptId, $reasonCode): void {
            $receipt = GoodsReceipt::withoutGlobalScopes()
                ->where('tenant_id', $tenantId)
                ->whereKey($goodsReceiptId)
                ->lockForUpdate()
                ->first();
            if ($receipt === null) {
                return;
            }
            $correlationId = $receipt->correlation_id;
            $receipt->forceFill(['status' => GoodsReceiptLifecycleStatus::Failed->value])->save();
            $this->obs->postingFailure($reasonCode, [
                'goods_receipt_id' => $goodsReceiptId,
                'tenant_id' => $tenantId,
                'correlation_id' => $correlationId,
            ]);
        });
    }
}
