<?php

declare(strict_types=1);

namespace App\Business\Inventory;

use App\Business\Inventory\Enums\InventoryDocumentStatus;
use App\Business\MasterData\DberpProductBarcodeLookupResult;
use App\Business\MasterData\DberpProductBarcodeLookupService;
use App\Models\DberpOtherStockOutOrder;
use App\Models\DberpOtherStockOutOrderItem;
use App\Models\DberpProduct;
use App\Models\DberpStocktakeOrder;
use App\Models\DberpStocktakeOrderItem;
use App\Support\DberpDocumentLineBarcodeQuickAddFormFields;
use App\Support\DberpDocumentLineBarcodeQuickAddSupport;
use App\Support\DberpLineProductSkuFormFields;
use App\Support\DberpProductSkuSelectOptions;
use Illuminate\Support\Facades\DB;
use Illuminate\Validation\ValidationException;

/**
 * 库存类单据明细条码快录：不写过账、不在录入时写入最终账面数量。
 */
final class InventoryDocumentLineBarcodeQuickAddService
{
    public function __construct(
        private readonly DberpProductBarcodeLookupService $barcodeLookupService,
    ) {}

    /**
     * @param  array<string, mixed>  $formData
     */
    public function quickAddStocktakeLine(DberpStocktakeOrder $order, array $formData): string
    {
        $lookup = $this->barcodeLookupService->lookup(
            (string) ($formData[DberpDocumentLineBarcodeQuickAddFormFields::FIELD_BARCODE] ?? ''),
            DberpProductSkuSelectOptions::PRODUCT_SCOPE_INVENTORY,
        );
        $quantity = DberpDocumentLineBarcodeQuickAddFormFields::normalizeQuantity(
            $formData[DberpDocumentLineBarcodeQuickAddFormFields::FIELD_QUANTITY] ?? '1',
        );
        $mode = DberpDocumentLineBarcodeQuickAddFormFields::duplicateModeFromData($formData);
        $remark = filled($formData[DberpDocumentLineBarcodeQuickAddFormFields::FIELD_REMARK] ?? null)
            ? (string) $formData[DberpDocumentLineBarcodeQuickAddFormFields::FIELD_REMARK]
            : null;

        return DB::transaction(function () use ($order, $lookup, $quantity, $mode, $remark): string {
            $locked = DberpStocktakeOrder::query()->whereKey($order->getKey())->lockForUpdate()->firstOrFail();
            $this->assertDraft($locked);

            $existing = DberpDocumentLineBarcodeQuickAddSupport::findMergeableLine(
                $locked->items(),
                $lookup->productId,
                $lookup->productSkuId,
                $lookup->unitId,
            );

            return DberpDocumentLineBarcodeQuickAddSupport::handleDuplicateMode(
                $mode,
                $existing,
                $quantity,
                'actual_quantity',
                fn () => $this->createStocktakeLine($locked, $lookup, $quantity, $remark),
            );
        });
    }

    /**
     * @param  array<string, mixed>  $formData
     */
    public function quickAddOtherStockOutLine(DberpOtherStockOutOrder $order, array $formData): string
    {
        $lookup = $this->barcodeLookupService->lookup(
            (string) ($formData[DberpDocumentLineBarcodeQuickAddFormFields::FIELD_BARCODE] ?? ''),
            DberpProductSkuSelectOptions::PRODUCT_SCOPE_INVENTORY,
        );
        $quantity = DberpDocumentLineBarcodeQuickAddFormFields::normalizeQuantity(
            $formData[DberpDocumentLineBarcodeQuickAddFormFields::FIELD_QUANTITY] ?? '1',
        );
        $mode = DberpDocumentLineBarcodeQuickAddFormFields::duplicateModeFromData($formData);
        $remark = filled($formData[DberpDocumentLineBarcodeQuickAddFormFields::FIELD_REMARK] ?? null)
            ? (string) $formData[DberpDocumentLineBarcodeQuickAddFormFields::FIELD_REMARK]
            : null;

        return DB::transaction(function () use ($order, $lookup, $quantity, $mode, $remark): string {
            $locked = DberpOtherStockOutOrder::query()->whereKey($order->getKey())->lockForUpdate()->firstOrFail();
            $this->assertDraft($locked);

            $existing = DberpDocumentLineBarcodeQuickAddSupport::findMergeableLine(
                $locked->items(),
                $lookup->productId,
                $lookup->productSkuId,
                $lookup->unitId,
                DberpDocumentLineBarcodeQuickAddSupport::constrainMergeableInventoryRefinementLine(...),
            );

            return DberpDocumentLineBarcodeQuickAddSupport::handleDuplicateMode(
                $mode,
                $existing,
                $quantity,
                'quantity',
                fn () => $this->createOtherStockOutLine($locked, $lookup, $quantity, $remark),
            );
        });
    }

    private function createStocktakeLine(
        DberpStocktakeOrder $order,
        DberpProductBarcodeLookupResult $lookup,
        string $quantity,
        ?string $remark,
    ): DberpStocktakeOrderItem {
        $product = DberpProduct::query()->find($lookup->productId);
        if ($product !== null) {
            $order->assertLineProductWithinScope($product);
        }

        $nextSort = (int) ($order->items()->max('sort_order') ?? 0) + 1;

        $payload = DberpLineProductSkuFormFields::finalizeLineSkuFormData([
            'product_id' => $lookup->productId,
            'product_sku_id' => $lookup->productSkuId,
            'unit_id' => $lookup->unitId,
            'actual_quantity' => $quantity,
            'sort_order' => $nextSort,
            'remark' => $remark,
        ]);

        /** @var DberpStocktakeOrderItem $line */
        $line = $order->items()->create($payload);

        app(StocktakeOrderService::class)->syncDraftLineInventorySnapshot($order, $line);

        return $line->fresh() ?? $line;
    }

    private function createOtherStockOutLine(
        DberpOtherStockOutOrder $order,
        DberpProductBarcodeLookupResult $lookup,
        string $quantity,
        ?string $remark,
    ): DberpOtherStockOutOrderItem {
        $nextSort = (int) ($order->items()->max('sort_order') ?? 0) + 1;

        $payload = DberpLineProductSkuFormFields::finalizeLineSkuFormData([
            'product_id' => $lookup->productId,
            'product_sku_id' => $lookup->productSkuId,
            'unit_id' => $lookup->unitId,
            'quantity' => $quantity,
            'sort_order' => $nextSort,
            'remark' => $remark,
        ]);

        /** @var DberpOtherStockOutOrderItem $line */
        $line = $order->items()->create($payload);

        return $line;
    }

    private function assertDraft(DberpStocktakeOrder|DberpOtherStockOutOrder $order): void
    {
        if ($order->status !== InventoryDocumentStatus::Draft->value) {
            throw ValidationException::withMessages([
                'status' => __('dberp.errors.inventory_delete_only_draft'),
            ]);
        }
    }
}
