<?php

declare(strict_types=1);

namespace App\Business\MasterData;

use App\Business\Product\DberpDocumentLineUnitService;
use App\Models\DberpProduct;
use App\Models\DberpProductSku;
use App\Support\DberpProductSkuSelectOptions;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Validation\ValidationException;

/**
 * 按条码解析商品与规格：支持商品级与 SKU 级条码，拒绝空条码与歧义匹配。
 */
final class DberpProductBarcodeLookupService
{
    /**
     * @param  string|null  $productScope  {@see DberpProductSkuSelectOptions::PRODUCT_SCOPE_*}，null 表示不限业务范围
     */
    public function lookup(string $barcode, ?string $productScope = null): DberpProductBarcodeLookupResult
    {
        $normalized = trim($barcode);
        if ($normalized === '') {
            throw ValidationException::withMessages([
                'barcode' => (string) __('dberp.errors.barcode_quick_add_empty'),
            ]);
        }

        $candidates = [];

        foreach ($this->scopedSkus($productScope)->where('barcode', $normalized)->get() as $sku) {
            $candidates[] = $this->resultFromSku($sku);
        }

        foreach ($this->scopedProducts($productScope)->where('barcode', $normalized)->get() as $product) {
            $scopedSkus = $this->scopedSkus($productScope)
                ->where('product_id', $product->getKey())
                ->get();

            if ($scopedSkus->isEmpty()) {
                continue;
            }

            if ($scopedSkus->count() === 1) {
                $candidates[] = $this->resultFromSku($scopedSkus->first());

                continue;
            }

            throw ValidationException::withMessages([
                'barcode' => (string) __('dberp.errors.barcode_lookup_ambiguous_product'),
            ]);
        }

        $candidates = $this->dedupeCandidates($candidates);

        if ($candidates === []) {
            throw ValidationException::withMessages([
                'barcode' => (string) __('dberp.errors.barcode_lookup_not_found'),
            ]);
        }

        if (count($candidates) > 1) {
            throw ValidationException::withMessages([
                'barcode' => (string) __('dberp.errors.barcode_lookup_duplicate'),
            ]);
        }

        return $candidates[0];
    }

    /**
     * @return Builder<DberpProductSku>
     */
    private function scopedSkus(?string $productScope): Builder
    {
        return match ($productScope) {
            DberpProductSkuSelectOptions::PRODUCT_SCOPE_SALABLE => DberpProductSkuSelectOptions::querySalableSkus(),
            DberpProductSkuSelectOptions::PRODUCT_SCOPE_PURCHASABLE => DberpProductSkuSelectOptions::queryPurchasableSkus(),
            DberpProductSkuSelectOptions::PRODUCT_SCOPE_INVENTORY => DberpProductSku::query()
                ->with(['product.unit'])
                ->where('is_enabled', true)
                ->whereHas('product', fn ($query) => $query
                    ->where('is_enabled', true)
                    ->where('manages_inventory', true)),
            default => DberpProductSku::query()
                ->with(['product.unit'])
                ->where('is_enabled', true)
                ->whereHas('product', fn ($query) => $query->where('is_enabled', true)),
        };
    }

    /**
     * @return Builder<DberpProduct>
     */
    private function scopedProducts(?string $productScope): Builder
    {
        return match ($productScope) {
            DberpProductSkuSelectOptions::PRODUCT_SCOPE_SALABLE => DberpProduct::query()
                ->where('is_enabled', true)
                ->where('can_sale', true)
                ->where('manages_inventory', true),
            DberpProductSkuSelectOptions::PRODUCT_SCOPE_PURCHASABLE => DberpProduct::query()
                ->where('is_enabled', true)
                ->where('can_purchase', true)
                ->where('manages_inventory', true),
            DberpProductSkuSelectOptions::PRODUCT_SCOPE_INVENTORY => DberpProductSkuSelectOptions::queryInventoryProducts(),
            default => DberpProduct::query()->where('is_enabled', true),
        };
    }

    private function resultFromSku(DberpProductSku $sku): DberpProductBarcodeLookupResult
    {
        $sku->loadMissing(['product.unit']);
        $product = $sku->product;
        if ($product === null) {
            throw ValidationException::withMessages([
                'barcode' => (string) __('dberp.errors.barcode_lookup_not_found'),
            ]);
        }

        return new DberpProductBarcodeLookupResult(
            productId: (int) $product->getKey(),
            productSkuId: (int) $sku->getKey(),
            unitId: DberpDocumentLineUnitService::defaultUnitIdForProduct($product),
            displayLabel: DberpProductSkuSelectOptions::labelForSku($sku),
        );
    }

    /**
     * @param  list<DberpProductBarcodeLookupResult>  $candidates
     * @return list<DberpProductBarcodeLookupResult>
     */
    private function dedupeCandidates(array $candidates): array
    {
        $seen = [];
        $unique = [];

        foreach ($candidates as $candidate) {
            $key = $candidate->productId.':'.$candidate->productSkuId;
            if (isset($seen[$key])) {
                continue;
            }

            $seen[$key] = true;
            $unique[] = $candidate;
        }

        return $unique;
    }
}
