<?php

declare(strict_types=1);

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

namespace App\Business\Sales;

use App\Business\Product\DberpDocumentLineUnitService;
use App\Business\Sales\Enums\SalesQuoteStatus;
use App\Models\DberpProductSku;
use App\Models\DberpSalesQuote;
use App\Models\DberpSalesQuoteItem;
use App\Services\DberpAuditService;
use App\Support\DberpDocumentLineBulkProductAddFormFields;
use App\Support\DberpLineProductSkuFormFields;
use App\Support\DberpProductSkuSelectOptions;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\DB;
use Illuminate\Validation\ValidationException;

final class SalesQuoteService
{
    public function __construct(
        private readonly DberpAuditService $auditService,
        private readonly SalesDocumentPricingService $salesDocumentPricingService,
    ) {}

    /**
     * 批量手工添加报价明细：按客户价格规则初始化单价，不写库存。
     *
     * @param  list<int>  $productSkuIds
     * @return list<DberpSalesQuoteItem>
     */
    public function createManualItemsFromSkus(
        DberpSalesQuote $quote,
        array $productSkuIds,
        string $quantity,
        ?string $remark = null,
        bool $skipExistingSkuLines = false,
    ): array {
        if ($productSkuIds === []) {
            throw ValidationException::withMessages([
                DberpDocumentLineBulkProductAddFormFields::FIELD_PRODUCT_SKU_IDS => (string) __('dberp.errors.bulk_add_product_skus_required'),
            ]);
        }

        $formattedQuantity = number_format((float) $quantity, 4, '.', '');
        if ((float) $formattedQuantity <= 0) {
            throw ValidationException::withMessages([
                DberpDocumentLineBulkProductAddFormFields::FIELD_DEFAULT_QUANTITY => (string) __('dberp.errors.bulk_add_quantity_must_be_positive'),
            ]);
        }

        return DB::transaction(function () use ($quote, $productSkuIds, $formattedQuantity, $remark, $skipExistingSkuLines): array {
            $lockedQuote = DberpSalesQuote::query()->whereKey($quote->getKey())->lockForUpdate()->firstOrFail();

            if (! in_array($this->resolveCurrentStatus($lockedQuote), [SalesQuoteStatus::Draft, SalesQuoteStatus::PendingEffective], true)) {
                throw ValidationException::withMessages([
                    'status' => __('dberp.errors.sales_quote_not_editable'),
                ]);
            }

            $lockedQuote->loadMissing('customer');

            $existingSkuIds = $skipExistingSkuLines
                ? $lockedQuote->items()
                    ->whereNotNull('product_sku_id')
                    ->pluck('product_sku_id')
                    ->map(fn (mixed $id): int => (int) $id)
                    ->all()
                : [];

            $nextSort = (int) ($lockedQuote->items()->max('sort_order') ?? 0);
            $created = [];

            foreach ($productSkuIds as $skuId) {
                if ($skipExistingSkuLines && in_array($skuId, $existingSkuIds, true)) {
                    continue;
                }

                $sku = DberpProductSkuSelectOptions::querySalableSkus()
                    ->whereKey($skuId)
                    ->first();

                if (! $sku instanceof DberpProductSku) {
                    throw ValidationException::withMessages([
                        DberpDocumentLineBulkProductAddFormFields::FIELD_PRODUCT_SKU_IDS => (string) __('dberp.errors.bulk_add_product_sku_not_salable'),
                    ]);
                }

                $sku->loadMissing(['product.unit']);
                $product = $sku->product;
                if ($product === null) {
                    continue;
                }

                $nextSort++;

                $basePayload = [
                    'product_id' => (int) $product->getKey(),
                    'product_sku_id' => (int) $sku->getKey(),
                    'unit_id' => DberpDocumentLineUnitService::defaultUnitIdForProduct($product),
                    'quantity' => $formattedQuantity,
                    'discount_amount' => '0.0000',
                    'remark' => $remark,
                    'sort_order' => $nextSort,
                ];

                $payload = DberpLineProductSkuFormFields::finalizeLineSkuFormData($basePayload);
                $payload = $this->salesDocumentPricingService->prepareQuoteItemData($lockedQuote, $payload);

                $created[] = $lockedQuote->items()->create($payload);
                $existingSkuIds[] = $skuId;
            }

            return $created;
        });
    }

    /**
     * @param  array<string, mixed>  $data
     */
    public function createQuote(array $data): DberpSalesQuote
    {
        return DB::transaction(function () use ($data): DberpSalesQuote {
            $payload = $this->normalizePayload($data);

            /** @var DberpSalesQuote $quote */
            $quote = DberpSalesQuote::query()->create($payload);

            return $quote;
        });
    }

    /**
     * @param  array<string, mixed>  $data
     */
    public function updateQuote(DberpSalesQuote $quote, array $data): DberpSalesQuote
    {
        return DB::transaction(function () use ($quote, $data): DberpSalesQuote {
            $locked = DberpSalesQuote::query()->whereKey($quote->getKey())->lockForUpdate()->firstOrFail();

            if (! in_array($this->resolveCurrentStatus($locked), [SalesQuoteStatus::Draft, SalesQuoteStatus::PendingEffective], true)) {
                throw ValidationException::withMessages([
                    'status' => __('dberp.errors.sales_quote_not_editable'),
                ]);
            }

            $locked->fill($this->normalizePayload($data, $locked));
            $locked->save();

            if ($locked->status !== SalesQuoteStatus::Draft->value) {
                $this->syncLifecycleStatus($locked);
            }

            return $locked;
        });
    }

    public function submit(DberpSalesQuote $quote): void
    {
        DB::transaction(function () use ($quote): void {
            $locked = DberpSalesQuote::query()->whereKey($quote->getKey())->lockForUpdate()->firstOrFail();

            if (! in_array($this->resolveCurrentStatus($locked), [SalesQuoteStatus::Draft, SalesQuoteStatus::PendingEffective], true)) {
                throw ValidationException::withMessages([
                    'status' => __('dberp.errors.sales_quote_not_submittable'),
                ]);
            }

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

            if ($locked->valid_from === null || $locked->valid_until === null) {
                throw ValidationException::withMessages([
                    'valid_from' => __('dberp.errors.sales_quote_validity_required'),
                ]);
            }

            if ((string) $locked->valid_until < (string) $locked->valid_from) {
                throw ValidationException::withMessages([
                    'valid_until' => __('dberp.errors.sales_quote_validity_invalid'),
                ]);
            }

            $before = ['status' => $locked->status];

            $this->auditService->logSemanticSave(
                'dberp.sales_quote.submitted',
                $locked,
                function () use ($locked): void {
                    $locked->forceFill(['status' => $this->resolveLifecycleStatusFromDates($locked)->value])->save();
                },
                $before,
                ['status' => $locked->status],
            );
        });
    }

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

            if (in_array($locked->status, [SalesQuoteStatus::Converted->value, SalesQuoteStatus::Voided->value], true)) {
                throw ValidationException::withMessages([
                    'status' => __('dberp.errors.sales_quote_not_voidable'),
                ]);
            }

            $before = ['status' => $locked->status];

            $this->auditService->logSemanticSave(
                'dberp.sales_quote.voided',
                $locked,
                function () use ($locked): void {
                    $locked->forceFill(['status' => SalesQuoteStatus::Voided->value])->save();
                },
                $before,
                ['status' => $locked->status],
            );
        });
    }

    public function copyQuote(DberpSalesQuote $quote): DberpSalesQuote
    {
        return DB::transaction(function () use ($quote): DberpSalesQuote {
            $locked = DberpSalesQuote::query()->with('items')->whereKey($quote->getKey())->lockForUpdate()->firstOrFail();

            /** @var DberpSalesQuote $newQuote */
            $newQuote = DberpSalesQuote::query()->create([
                'customer_id' => $locked->customer_id,
                'handler_employee_id' => $locked->handler_employee_id,
                'quote_date' => now()->toDateString(),
                'valid_from' => $locked->valid_from,
                'valid_until' => $locked->valid_until,
                'status' => SalesQuoteStatus::Draft->value,
                'suggested_payment_term_days' => $locked->suggested_payment_term_days,
                'source_system' => $locked->source_system,
                'external_reference_id' => $locked->external_reference_id,
                'remark' => trim((string) ($locked->remark ? $locked->remark.'；' : '').'复制自报价单 '.$locked->code),
            ]);

            foreach ($locked->items as $item) {
                $newQuote->items()->create($this->replicateQuoteItemPayload($item));
            }

            $this->auditService->log('dberp.sales_quote.copied', $newQuote, null, $this->auditService->snapshot($newQuote, $newQuote->getDberpAuditFields()));

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

    public function resolveCurrentStatus(DberpSalesQuote $quote): SalesQuoteStatus
    {
        $stored = SalesQuoteStatus::tryFrom((string) $quote->status) ?? SalesQuoteStatus::Draft;
        $today = now()->toDateString();
        $validFrom = $quote->valid_from === null ? null : substr((string) $quote->valid_from, 0, 10);
        $validUntil = $quote->valid_until === null ? null : substr((string) $quote->valid_until, 0, 10);

        if (in_array($stored, [SalesQuoteStatus::Draft, SalesQuoteStatus::Converted, SalesQuoteStatus::Voided], true)) {
            return $stored;
        }

        if ($validFrom !== null && $validFrom > $today) {
            return SalesQuoteStatus::PendingEffective;
        }

        if ($validUntil !== null && $validUntil < $today) {
            return SalesQuoteStatus::Expired;
        }

        return SalesQuoteStatus::Effective;
    }

    public function syncLifecycleStatus(DberpSalesQuote $quote): DberpSalesQuote
    {
        $status = $this->resolveLifecycleStatusFromDates($quote);

        if ($quote->status !== $status->value) {
            $quote->forceFill(['status' => $status->value])->save();
        }

        return $quote->refresh();
    }

    private function resolveLifecycleStatusFromDates(DberpSalesQuote $quote): SalesQuoteStatus
    {
        $today = now()->toDateString();
        $validFrom = $quote->valid_from === null ? null : substr((string) $quote->valid_from, 0, 10);
        $validUntil = $quote->valid_until === null ? null : substr((string) $quote->valid_until, 0, 10);

        if ($validFrom !== null && $validFrom > $today) {
            return SalesQuoteStatus::PendingEffective;
        }

        if ($validUntil !== null && $validUntil < $today) {
            return SalesQuoteStatus::Expired;
        }

        return SalesQuoteStatus::Effective;
    }

    /**
     * @param  array<string, mixed>  $data
     * @return array<string, mixed>
     */
    private function normalizePayload(array $data, ?DberpSalesQuote $quote = null): array
    {
        $payload = Arr::only($data, [
            'code',
            'customer_id',
            'handler_employee_id',
            'quote_date',
            'valid_from',
            'valid_until',
            'suggested_payment_term_days',
            'source_system',
            'external_reference_id',
            'remark',
        ]);

        $payload['quote_date'] ??= now()->toDateString();
        $payload['source_system'] = $payload['source_system'] ?? $quote?->source_system ?? 'dberp';
        $payload['status'] = $quote?->status ?? SalesQuoteStatus::Draft->value;

        return $payload;
    }

    /**
     * @return array<string, mixed>
     */
    private function replicateQuoteItemPayload(DberpSalesQuoteItem $item): array
    {
        return Arr::only($item->getAttributes(), [
            'product_id',
            'product_code_snapshot',
            'product_name_snapshot',
            'product_spec_snapshot',
            'external_product_id',
            'external_sku',
            'quantity',
            'unit_price',
            'line_amount',
            'discount_amount',
            'final_amount',
            'price_source_type',
            'price_source_label',
            'price_rule_id',
            'matched_unit_price',
            'reference_unit_price',
            'recent_transaction_unit_price',
            'recent_transaction_date',
            'sort_order',
            'remark',
        ]);
    }
}
