<?php

declare(strict_types=1);

/**
 * 本文件属于 DBErp 进销存系统。
 *
 * 版权所有 © 2026 北京珑大钜商科技有限公司。保留所有权利。
 *
 * 未经官方授权，不得删除、篡改或隐藏本文件中的版权声明、产品标识与来源信息。
 * 商业授权、官方更新与技术支持服务，请以珑大官方支持中心记录为准。
 *
 * @link https://support.loongdom.cn
 * @see  https://www.dberp.com.cn
 */

namespace App\Business\Purchase;

use App\Business\Purchase\Enums\PurchaseInquiryQuoteStatus;
use App\Models\DberpPurchaseInquiryItem;
use App\Models\DberpPurchaseInquiryQuote;
use App\Models\DberpPurchaseRequestItem;
use App\Models\DberpSupplier;
use App\Support\DberpLineProductSkuFormFields;
use Illuminate\Support\Facades\DB;
use Illuminate\Validation\ValidationException;

final class PurchaseInquiryQuoteService
{
    public function __construct(
        private readonly PurchaseSourceDocumentService $sourceDocumentService,
    ) {}

    public function createQuote(array $data): DberpPurchaseInquiryQuote
    {
        return DB::transaction(function () use ($data): DberpPurchaseInquiryQuote {
            $payload = $this->normalizePayload($data);

            $this->sourceDocumentService->assertNoDuplicateActiveQuote(
                (int) $payload['purchase_inquiry_item_id'],
                (int) $payload['supplier_id'],
            );

            $quote = DberpPurchaseInquiryQuote::query()->create($payload);

            $this->syncRecommendedFlag($quote, (bool) $quote->is_recommended);

            return $quote->refresh();
        });
    }

    /**
     * 为询价明细创建供应商报价占位（待后续录入单价等信息）。
     */
    public function createPlaceholderForInquiryItem(
        DberpPurchaseInquiryItem $inquiryItem,
        int $supplierId,
        mixed $quoteDate = null,
    ): DberpPurchaseInquiryQuote {
        return DB::transaction(function () use ($inquiryItem, $supplierId, $quoteDate): DberpPurchaseInquiryQuote {
            $inquiryItem->loadMissing(['inquiry', 'purchaseRequestItem.request', 'product']);

            $requestItem = $inquiryItem->purchaseRequestItem;

            if ($requestItem === null) {
                throw ValidationException::withMessages([
                    'purchase_request_item_id' => __('dberp.errors.invalid_purchase_source_document'),
                ]);
            }

            if ($supplierId <= 0) {
                throw ValidationException::withMessages([
                    'supplier_id' => __('dberp.errors.purchase_inquiry_quote_supplier_required'),
                ]);
            }

            $this->sourceDocumentService->assertNoDuplicateActiveQuote(
                (int) $inquiryItem->getKey(),
                $supplierId,
            );

            $supplier = DberpSupplier::query()->find($supplierId);

            $quote = DberpPurchaseInquiryQuote::query()->create([
                'purchase_inquiry_id' => $inquiryItem->inquiry_id,
                'purchase_inquiry_item_id' => $inquiryItem->getKey(),
                'purchase_request_id' => $requestItem->request_id,
                'purchase_request_code' => $requestItem->request?->code,
                'source_system' => (string) ($requestItem->request?->source_system ?? 'dberp'),
                'external_reference_id' => $requestItem->request?->external_reference_id,
                'purchase_request_item_id' => $requestItem->getKey(),
                'product_id' => $requestItem->product_id,
                'product_sku_id' => $inquiryItem->product_sku_id,
                'product_sku_code' => $inquiryItem->product_sku_code,
                'product_sku_name' => $inquiryItem->product_sku_name,
                'product_sku_spec' => $inquiryItem->product_sku_spec,
                'external_product_id' => $requestItem->external_product_id ?? $requestItem->product?->external_product_id,
                'external_sku' => $requestItem->external_sku ?? $requestItem->product?->external_sku,
                'product_name_snapshot' => $requestItem->product?->name,
                'product_spec_snapshot' => $requestItem->product?->spec_model,
                'supplier_id' => $supplierId,
                'supplier_name_snapshot' => $supplier?->name,
                'quote_date' => $quoteDate ?? now()->toDateString(),
                'valid_until' => null,
                'unit_price' => '0.0000',
                'min_order_quantity' => '0.0000',
                'lead_time_days' => null,
                'tax_rate' => '0.0000',
                'is_tax_included' => false,
                'is_recommended' => false,
                'status' => PurchaseInquiryQuoteStatus::Active->value,
                'invalidated_at' => null,
                'quote_note' => null,
                'remark' => null,
            ]);

            return $quote->refresh();
        });
    }

    public function updateQuote(DberpPurchaseInquiryQuote $quote, array $data): DberpPurchaseInquiryQuote
    {
        return DB::transaction(function () use ($quote, $data): DberpPurchaseInquiryQuote {
            $locked = DberpPurchaseInquiryQuote::query()->whereKey($quote->getKey())->lockForUpdate()->firstOrFail();

            if ($locked->status === PurchaseInquiryQuoteStatus::Invalidated->value) {
                throw ValidationException::withMessages([
                    'status' => __('dberp.errors.purchase_inquiry_quote_not_editable'),
                ]);
            }

            $payload = $this->normalizePayload($data, $locked);

            $this->sourceDocumentService->assertNoDuplicateActiveQuote(
                (int) $payload['purchase_inquiry_item_id'],
                (int) $payload['supplier_id'],
                $locked->getKey(),
            );

            $locked->fill($payload)->save();

            $this->syncRecommendedFlag($locked, (bool) $locked->is_recommended);

            return $locked->refresh();
        });
    }

    public function markRecommended(DberpPurchaseInquiryQuote $quote): DberpPurchaseInquiryQuote
    {
        return DB::transaction(function () use ($quote): DberpPurchaseInquiryQuote {
            $locked = DberpPurchaseInquiryQuote::query()->whereKey($quote->getKey())->lockForUpdate()->firstOrFail();

            if (! $locked->isUsable()) {
                throw ValidationException::withMessages([
                    'status' => __('dberp.errors.purchase_inquiry_quote_not_recommendable'),
                ]);
            }

            $locked->forceFill(['is_recommended' => true])->save();
            $this->syncRecommendedFlag($locked, true);

            return $locked->refresh();
        });
    }

    public function invalidate(DberpPurchaseInquiryQuote $quote, ?string $reason = null): DberpPurchaseInquiryQuote
    {
        return DB::transaction(function () use ($quote, $reason): DberpPurchaseInquiryQuote {
            $locked = DberpPurchaseInquiryQuote::query()->whereKey($quote->getKey())->lockForUpdate()->firstOrFail();

            if ($locked->status === PurchaseInquiryQuoteStatus::Invalidated->value) {
                return $locked;
            }

            $quoteNote = trim(implode("\n", array_filter([
                (string) $locked->quote_note,
                $reason !== null && $reason !== '' ? '作废说明：'.$reason : null,
            ])));

            $locked->forceFill([
                'status' => PurchaseInquiryQuoteStatus::Invalidated->value,
                'invalidated_at' => now(),
                'is_recommended' => false,
                'quote_note' => $quoteNote !== '' ? $quoteNote : null,
            ])->save();

            return $locked->refresh();
        });
    }

    /**
     * @param  array<string, mixed>  $data
     * @return array<string, mixed>
     */
    private function normalizePayload(array $data, ?DberpPurchaseInquiryQuote $quote = null): array
    {
        $value = static fn (string $key, mixed $default = null): mixed => array_key_exists($key, $data) ? $data[$key] : $default;

        $requestItemId = (int) $value('purchase_request_item_id', $quote?->purchase_request_item_id);

        $requestItem = DberpPurchaseRequestItem::query()
            ->with(['request.suggestedSupplier', 'product'])
            ->findOrFail($requestItemId);

        $supplierId = (int) ($value('supplier_id') ?? $quote?->supplier_id ?? $requestItem->request?->suggested_supplier_id);

        if ($supplierId <= 0) {
            throw ValidationException::withMessages([
                'supplier_id' => __('dberp.errors.purchase_inquiry_quote_supplier_required'),
            ]);
        }

        $validUntil = $value('valid_until') ?? ($quote?->getRawOriginal('valid_until') ?: null);
        $status = PurchaseInquiryQuoteStatus::Active;

        if ($validUntil !== null && now()->toDateString() > (string) $validUntil) {
            $status = PurchaseInquiryQuoteStatus::Expired;
        }

        if (($quote?->status ?? null) === PurchaseInquiryQuoteStatus::Invalidated->value) {
            $status = PurchaseInquiryQuoteStatus::Invalidated;
        }

        $supplier = DberpSupplier::query()->find($supplierId);

        $explicitInquiryItemId = (int) $value('purchase_inquiry_item_id', $quote?->purchase_inquiry_item_id ?? 0);

        if ($explicitInquiryItemId > 0) {
            $inquiryItem = DberpPurchaseInquiryItem::query()
                ->with(['inquiry', 'purchaseRequestItem.request', 'product'])
                ->findOrFail($explicitInquiryItemId);

            if ((int) $inquiryItem->purchase_request_item_id !== (int) $requestItem->getKey()) {
                throw ValidationException::withMessages([
                    'purchase_inquiry_item_id' => __('dberp.errors.invalid_purchase_source_document'),
                ]);
            }
        } else {
            $inquiryItem = $this->sourceDocumentService->ensureInquiryItemForRequestItem($requestItem);
        }

        $skuSanitized = DberpLineProductSkuFormFields::finalizeLineSkuFormData(array_merge($data, [
            'product_id' => $requestItem->product_id,
        ]));

        return [
            'purchase_inquiry_id' => $inquiryItem->inquiry_id,
            'purchase_inquiry_item_id' => $inquiryItem->getKey(),
            'purchase_request_id' => $requestItem->request_id,
            'purchase_request_code' => $requestItem->request?->code,
            'source_system' => (string) ($value('source_system') ?? $quote?->source_system ?? $requestItem->request?->source_system ?? 'dberp'),
            'external_reference_id' => $value('external_reference_id') ?? $quote?->external_reference_id ?? $requestItem->request?->external_reference_id,
            'purchase_request_item_id' => $requestItem->getKey(),
            'product_id' => $requestItem->product_id,
            'product_sku_id' => $skuSanitized['product_sku_id'] ?? null,
            'product_sku_code' => $skuSanitized['product_sku_code'] ?? null,
            'product_sku_name' => $skuSanitized['product_sku_name'] ?? null,
            'product_sku_spec' => $skuSanitized['product_sku_spec'] ?? null,
            'external_product_id' => $value('external_product_id') ?? $quote?->external_product_id ?? $requestItem->external_product_id ?? $requestItem->product?->external_product_id,
            'external_sku' => $value('external_sku') ?? $quote?->external_sku ?? $requestItem->external_sku ?? $requestItem->product?->external_sku,
            'product_name_snapshot' => $requestItem->product?->name,
            'product_spec_snapshot' => $requestItem->product?->spec_model,
            'supplier_id' => $supplierId,
            'supplier_name_snapshot' => $supplier?->name,
            'quote_date' => $value('quote_date') ?? ($quote?->getRawOriginal('quote_date') ?: now()->toDateString()),
            'valid_until' => $validUntil,
            'unit_price' => number_format((float) ($value('unit_price') ?? $quote?->unit_price ?? 0), 4, '.', ''),
            'min_order_quantity' => number_format((float) ($value('min_order_quantity') ?? $quote?->min_order_quantity ?? 0), 4, '.', ''),
            'lead_time_days' => $value('lead_time_days', $quote?->lead_time_days),
            'tax_rate' => number_format((float) ($value('tax_rate') ?? $quote?->tax_rate ?? 0), 4, '.', ''),
            'is_tax_included' => (bool) ($value('is_tax_included') ?? $quote?->is_tax_included ?? false),
            'is_recommended' => (bool) ($value('is_recommended') ?? $quote?->is_recommended ?? false),
            'status' => $status->value,
            'invalidated_at' => $status === PurchaseInquiryQuoteStatus::Invalidated ? ($quote?->invalidated_at ?? now()) : null,
            'quote_note' => $value('quote_note', $quote?->quote_note),
            'remark' => $value('remark', $quote?->remark),
        ];
    }

    private function syncRecommendedFlag(DberpPurchaseInquiryQuote $quote, bool $isRecommended): void
    {
        if (! $isRecommended) {
            return;
        }

        DberpPurchaseInquiryQuote::query()
            ->where('purchase_inquiry_item_id', $quote->purchase_inquiry_item_id)
            ->whereKeyNot($quote->getKey())
            ->update([
                'is_recommended' => false,
                'updated_at' => now(),
            ]);
    }
}
