<?php

declare(strict_types=1);

namespace App\Business\Purchase;

use App\Business\Purchase\Enums\PurchaseInquiryQuoteStatus;
use App\Business\Purchase\Enums\PurchasePriceHitResult;
use App\Models\DberpProduct;
use App\Models\DberpProductSku;
use App\Models\DberpPurchaseInquiryQuote;
use App\Models\DberpPurchaseOrder;
use App\Models\DberpPurchaseRequestItem;
use App\Support\DberpNumberFormatter;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Arr;
use Illuminate\Validation\ValidationException;

final class PurchaseOrderPriceMatchService
{
    public function __construct(
        private readonly PurchasePriceReferenceService $referenceService,
    ) {}

    /**
     * @param  array<string, mixed>  $payload
     * @return array<string, mixed>
     */
    public function prepareItemPayload(DberpPurchaseOrder $order, array $payload): array
    {
        $skuId = (int) Arr::get($payload, 'product_sku_id', 0);
        $productId = (int) Arr::get($payload, 'product_id', 0);

        if ($skuId > 0) {
            $sku = DberpProductSku::query()->find($skuId);
            if (! $sku instanceof DberpProductSku) {
                throw ValidationException::withMessages([
                    'product_sku_id' => __('dberp.errors.purchase_order_item_sku_invalid'),
                ]);
            }

            $productId = (int) $sku->product_id;
            $payload['product_id'] = $productId;
        }

        if ($productId <= 0) {
            throw ValidationException::withMessages([
                'product_id' => __('dberp.errors.purchase_item_product_required'),
            ]);
        }

        $requestItemId = Arr::get($payload, 'purchase_request_item_id');
        $sourceInquiryQuoteId = Arr::get($payload, 'source_inquiry_quote_id');

        $context = $this->resolveContext(
            productId: $productId,
            supplierId: (int) $order->supplier_id,
            purchaseRequestItemId: $requestItemId !== null ? (int) $requestItemId : null,
            sourceInquiryQuoteId: $sourceInquiryQuoteId !== null ? (int) $sourceInquiryQuoteId : null,
        );

        $incomingUnitPrice = (float) Arr::get($payload, 'unit_price', 0);
        $resolvedUnitPrice = $incomingUnitPrice > 0
            ? $incomingUnitPrice
            : (float) ($context['suggested_unit_price'] ?? 0);

        $evaluation = $this->evaluatePrice($resolvedUnitPrice, $context);

        $merged = array_merge($payload, [
            'unit_price' => number_format($resolvedUnitPrice, 4, '.', ''),
            'source_inquiry_quote_id' => $context['source_inquiry_quote_id'],
            'purchase_request_item_id' => $context['purchase_request_item_id'] ?? Arr::get($payload, 'purchase_request_item_id'),
            'external_product_id' => $context['external_product_id'],
            'external_sku' => $context['external_sku'],
            'matched_reference_price' => $this->formatNullableDecimal($context['matched_reference_price']),
            'recent_supplier_transaction_unit_price' => $this->formatNullableDecimal($context['recent_supplier_transaction_unit_price']),
            'recent_product_transaction_unit_price' => $this->formatNullableDecimal($context['recent_product_transaction_unit_price']),
            'lowest_inquiry_unit_price' => $this->formatNullableDecimal($context['lowest_inquiry_unit_price']),
            'recent_supplier_transaction_date' => $context['recent_supplier_transaction_date'],
            'recent_product_transaction_date' => $context['recent_product_transaction_date'],
            'recent_supplier_lead_time_days' => $context['recent_supplier_lead_time_days'],
            'price_hit_result' => $evaluation['result'],
            'price_hit_label' => $evaluation['label'],
            'price_hit_summary' => $evaluation['summary'],
            'price_reference_snapshot' => $context['price_reference_snapshot'],
        ]);

        return $merged;
    }

    /**
     * @return array<string, mixed>
     */
    public function resolveContext(int $productId, int $supplierId, ?int $purchaseRequestItemId = null, ?int $sourceInquiryQuoteId = null): array
    {
        $referenceContext = $this->referenceService->getContext($productId, $supplierId);
        $usableQuoteQuery = $this->usableQuoteQuery($productId, $supplierId, $purchaseRequestItemId);
        $requestItem = $purchaseRequestItemId !== null
            ? DberpPurchaseRequestItem::query()->with('product')->find($purchaseRequestItemId)
            : null;

        $selectedQuote = $sourceInquiryQuoteId !== null
            ? DberpPurchaseInquiryQuote::query()->with('product')->whereKey($sourceInquiryQuoteId)->first()
            : null;

        if ($selectedQuote !== null && (
            (int) $selectedQuote->product_id !== $productId
            || (int) $selectedQuote->supplier_id !== $supplierId
            || ($purchaseRequestItemId !== null && (int) $selectedQuote->purchase_request_item_id !== $purchaseRequestItemId)
        )) {
            throw ValidationException::withMessages([
                'source_inquiry_quote_id' => __('dberp.errors.purchase_order_source_inquiry_quote_mismatch'),
            ]);
        }

        $recommendedQuote = (clone $usableQuoteQuery)
            ->where('is_recommended', true)
            ->orderByDesc('quote_date')
            ->orderByDesc('id')
            ->first();

        $lowestQuote = (clone $usableQuoteQuery)
            ->orderBy('unit_price')
            ->orderByDesc('quote_date')
            ->orderByDesc('id')
            ->first();

        $supplierSnapshot = $referenceContext['supplier_snapshot'];
        $productSnapshot = $referenceContext['product_snapshot'];
        $product = $requestItem?->product ?? $selectedQuote?->product ?? DberpProduct::query()->find($productId);

        $matchedReferencePrice = $selectedQuote?->unit_price
            ?? $recommendedQuote?->unit_price
            ?? $lowestQuote?->unit_price
            ?? $supplierSnapshot['latest_transaction_unit_price']
            ?? $productSnapshot['latest_transaction_unit_price']
            ?? $referenceContext['product_cost_price'];

        $suggestedUnitPrice = $matchedReferencePrice ?? '0.0000';

        return [
            'source_inquiry_quote_id' => $selectedQuote?->getKey() ?? $recommendedQuote?->getKey(),
            'purchase_request_item_id' => $purchaseRequestItemId
                ?? $selectedQuote?->purchase_request_item_id
                ?? $recommendedQuote?->purchase_request_item_id
                ?? $requestItem?->getKey(),
            'external_product_id' => $requestItem?->external_product_id ?? $selectedQuote?->external_product_id ?? $product?->external_product_id,
            'external_sku' => $requestItem?->external_sku ?? $selectedQuote?->external_sku ?? $product?->external_sku,
            'matched_reference_price' => $matchedReferencePrice,
            'suggested_unit_price' => $suggestedUnitPrice,
            'recent_supplier_transaction_unit_price' => $supplierSnapshot['latest_transaction_unit_price'] ?? null,
            'recent_product_transaction_unit_price' => $productSnapshot['latest_transaction_unit_price'] ?? null,
            'lowest_inquiry_unit_price' => $lowestQuote?->unit_price !== null ? (string) $lowestQuote->unit_price : null,
            'recent_supplier_transaction_date' => $supplierSnapshot['latest_transaction_date'] ?? null,
            'recent_product_transaction_date' => $productSnapshot['latest_transaction_date'] ?? null,
            'recent_supplier_lead_time_days' => $supplierSnapshot['latest_lead_time_days'] ?? null,
            'price_reference_snapshot' => [
                'selected_quote' => $this->quoteSnapshot($selectedQuote),
                'recommended_quote' => $this->quoteSnapshot($recommendedQuote),
                'lowest_quote' => $this->quoteSnapshot($lowestQuote),
                'supplier_snapshot' => $supplierSnapshot,
                'product_snapshot' => $productSnapshot,
                'product_cost_price' => $referenceContext['product_cost_price'],
                'purchase_request_item_id' => $purchaseRequestItemId,
            ],
        ];
    }

    /**
     * @return array<string, mixed>
     */
    private function evaluatePrice(float $unitPrice, array $context): array
    {
        $supplierPrice = $this->nullableFloat($context['recent_supplier_transaction_unit_price'] ?? null);
        $productPrice = $this->nullableFloat($context['recent_product_transaction_unit_price'] ?? null);
        $lowestQuotePrice = $this->nullableFloat($context['lowest_inquiry_unit_price'] ?? null);
        $matchedReferencePrice = $this->nullableFloat($context['matched_reference_price'] ?? null);

        $messages = [];
        $result = null;

        if ($supplierPrice !== null && $unitPrice > $supplierPrice * 1.03) {
            $messages[] = '当前录入单价高于该供应商最近成交价 '.$this->formatDisplayNumber($supplierPrice).'。';
            $result = PurchasePriceHitResult::HigherThanRecentSupplier;
        }

        if ($result === null && $productPrice !== null && $unitPrice > $productPrice * 1.03) {
            $messages[] = '当前录入单价高于该商品最近采购价 '.$this->formatDisplayNumber($productPrice).'。';
            $result = PurchasePriceHitResult::HigherThanRecentProduct;
        }

        if ($lowestQuotePrice !== null && $unitPrice <= $lowestQuotePrice * 1.02) {
            $messages[] = '当前录入单价接近本次询价最低有效价 '.$this->formatDisplayNumber($lowestQuotePrice).'。';
            $result ??= PurchasePriceHitResult::NearLowestQuote;
        }

        if ($result === null && $supplierPrice !== null && $unitPrice < $supplierPrice * 0.97) {
            $messages[] = '当前录入单价低于该供应商最近成交价 '.$this->formatDisplayNumber($supplierPrice).'。';
            $result = PurchasePriceHitResult::LowerThanRecentSupplier;
        }

        if ($result === null && $matchedReferencePrice !== null) {
            $messages[] = '当前录入单价处于参考区间内，参考价 '.$this->formatDisplayNumber($matchedReferencePrice).'。';
            $result = PurchasePriceHitResult::ReferenceAligned;
        }

        if ($result === null) {
            $messages[] = (string) __('dberp.messages.purchase_price_fallback_to_base_cost');
            $result = PurchasePriceHitResult::ReferenceMissing;
        }

        return [
            'result' => $result->value,
            'label' => $result->label(),
            'summary' => implode(' ', $messages),
        ];
    }

    /**
     * @param  array<string, mixed>  $context
     * @return array<string, mixed>
     */
    public function evaluatePriceInsight(float $unitPrice, array $context): array
    {
        return $this->evaluatePrice($unitPrice, $context);
    }

    private function usableQuoteQuery(int $productId, int $supplierId, ?int $purchaseRequestItemId): Builder
    {
        return DberpPurchaseInquiryQuote::query()
            ->where('product_id', $productId)
            ->when($purchaseRequestItemId !== null, function (Builder $query) use ($purchaseRequestItemId): Builder {
                return $query->where('purchase_request_item_id', $purchaseRequestItemId);
            }, function (Builder $query) use ($supplierId): Builder {
                return $query->where('supplier_id', $supplierId);
            })
            ->where('status', PurchaseInquiryQuoteStatus::Active->value)
            ->where(function (Builder $query): Builder {
                return $query
                    ->whereNull('valid_until')
                    ->orWhereDate('valid_until', '>=', now()->toDateString());
            });
    }

    /**
     * @return array<string, mixed>|null
     */
    private function quoteSnapshot(?DberpPurchaseInquiryQuote $quote): ?array
    {
        if ($quote === null) {
            return null;
        }

        return [
            'id' => $quote->getKey(),
            'code' => $quote->code,
            'supplier_id' => $quote->supplier_id,
            'supplier_name' => $quote->supplier_name_snapshot ?: $quote->supplier?->name,
            'unit_price' => (string) $quote->unit_price,
            'quote_date' => $quote->getRawOriginal('quote_date') ?: null,
            'valid_until' => $quote->getRawOriginal('valid_until') ?: null,
            'lead_time_days' => $quote->lead_time_days,
            'is_recommended' => (bool) $quote->is_recommended,
        ];
    }

    private function nullableFloat(mixed $value): ?float
    {
        return $value === null || $value === '' ? null : (float) $value;
    }

    private function formatNullableDecimal(mixed $value): ?string
    {
        $float = $this->nullableFloat($value);

        return $float === null ? null : number_format($float, 4, '.', '');
    }

    private function formatDisplayNumber(float $value): string
    {
        return DberpNumberFormatter::price($value);
    }
}
