<?php

declare(strict_types=1);

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

namespace App\Business\Settlement;

use App\Business\Settlement\Enums\SettlementDocumentStatus;
use App\Models\DberpPaymentOrder;
use App\Models\DberpPurchaseOrder;
use App\Models\DberpReceiptOrder;
use App\Models\DberpSalesOrder;
use App\Services\DberpAuditService;
use Illuminate\Validation\ValidationException;

final class SettlementOverLimitGuard
{
    private const EPSILON = 0.00005;

    public function __construct(
        private readonly SettlementRuleService $ruleService,
        private readonly DberpAuditService $auditService,
    ) {}

    /**
     * @param  array<string, mixed>|null  $blockedAuditPayload
     */
    public function assertReceiptWithinPolicy(DberpReceiptOrder $order, ?array &$blockedAuditPayload = null): void
    {
        if ($order->sales_order_id === null) {
            return;
        }

        $policy = $this->ruleService->receiptOverLimitPolicy();

        if ($policy === 'allow') {
            return;
        }

        $salesOrder = $order->relationLoaded('salesOrder')
            ? $order->salesOrder
            : DberpSalesOrder::query()->find($order->sales_order_id);

        if ($salesOrder === null) {
            return;
        }

        $cap = $this->resolveSalesOrderCollectionCap($salesOrder);

        if ($cap === null) {
            return;
        }

        $registered = $this->sumNonVoidReceiptAmountForSalesOrder($salesOrder, $order);

        if ($registered <= $cap + self::EPSILON) {
            return;
        }

        $auditPayload = [
            'sales_order_id' => $salesOrder->getKey(),
            'receipt_order_id' => $order->getKey(),
            'cap' => number_format($cap, 4, '.', ''),
            'registered' => number_format($registered, 4, '.', ''),
            'policy' => $policy,
        ];

        if ($policy === 'warn_allow') {
            $this->auditService->log('dberp.receipt_order.over_limit_warned', $order, null, $auditPayload);

            return;
        }

        // 禁止超额时审计必须写在事务外，否则 ValidationException 回滚会丢掉拦截记录。
        $blockedAuditPayload = [
            'action' => 'dberp.receipt_order.over_limit_blocked',
            ...$auditPayload,
        ];

        throw ValidationException::withMessages([
            'amount' => __('dberp.errors.receipt_amount_exceeds_remaining'),
        ]);
    }

    /**
     * @param  array<string, mixed>|null  $blockedAuditPayload
     */
    public function assertPaymentWithinPolicy(DberpPaymentOrder $order, ?array &$blockedAuditPayload = null): void
    {
        if ($order->purchase_order_id === null) {
            return;
        }

        $policy = $this->ruleService->paymentOverLimitPolicy();

        if ($policy === 'allow') {
            return;
        }

        $purchaseOrder = $order->relationLoaded('purchaseOrder')
            ? $order->purchaseOrder
            : DberpPurchaseOrder::query()->find($order->purchase_order_id);

        if ($purchaseOrder === null) {
            return;
        }

        $cap = $this->resolvePurchaseOrderPaymentCap($purchaseOrder);

        if ($cap === null) {
            return;
        }

        $registered = $this->sumNonVoidPaymentAmountForPurchaseOrder($purchaseOrder, $order);

        if ($registered <= $cap + self::EPSILON) {
            return;
        }

        $auditPayload = [
            'purchase_order_id' => $purchaseOrder->getKey(),
            'payment_order_id' => $order->getKey(),
            'cap' => number_format($cap, 4, '.', ''),
            'registered' => number_format($registered, 4, '.', ''),
            'policy' => $policy,
        ];

        if ($policy === 'warn_allow') {
            $this->auditService->log('dberp.payment_order.over_limit_warned', $order, null, $auditPayload);

            return;
        }

        // 禁止超额时审计必须写在事务外，否则 ValidationException 回滚会丢掉拦截记录。
        $blockedAuditPayload = [
            'action' => 'dberp.payment_order.over_limit_blocked',
            ...$auditPayload,
        ];

        throw ValidationException::withMessages([
            'amount' => __('dberp.errors.payment_amount_exceeds_remaining'),
        ]);
    }

    private function resolveSalesOrderCollectionCap(DberpSalesOrder $order): ?float
    {
        $order->loadMissing('receivableRecord');

        if ($order->receivableRecord !== null) {
            return (float) $order->receivableRecord->adjusted_amount;
        }

        $receivableAmount = (float) $order->receivable_amount;

        return $receivableAmount > self::EPSILON ? $receivableAmount : null;
    }

    private function resolvePurchaseOrderPaymentCap(DberpPurchaseOrder $order): ?float
    {
        $order->loadMissing('payableRecord');

        if ($order->payableRecord !== null) {
            return (float) $order->payableRecord->adjusted_amount;
        }

        $payableAmount = (float) $order->payable_amount;

        return $payableAmount > self::EPSILON ? $payableAmount : null;
    }

    private function sumNonVoidReceiptAmountForSalesOrder(DberpSalesOrder $salesOrder, DberpReceiptOrder $excluding): float
    {
        $query = DberpReceiptOrder::query()
            ->where('sales_order_id', $salesOrder->getKey())
            ->whereNot('status', SettlementDocumentStatus::Voided->value)
            // 未确认草稿不占用额度，避免拦截失败的超额草稿挡住后续合法收款。
            ->whereNot('status', SettlementDocumentStatus::Draft->value);

        if ($excluding->exists) {
            $query->whereKeyNot($excluding->getKey());
        }

        $other = (float) $query->sum('amount');

        return $other + (float) $excluding->amount;
    }

    private function sumNonVoidPaymentAmountForPurchaseOrder(DberpPurchaseOrder $purchaseOrder, DberpPaymentOrder $excluding): float
    {
        $query = DberpPaymentOrder::query()
            ->where('purchase_order_id', $purchaseOrder->getKey())
            ->whereNot('status', SettlementDocumentStatus::Voided->value)
            // 未确认草稿不占用额度，避免拦截失败的超额草稿挡住后续合法收款。
            ->whereNot('status', SettlementDocumentStatus::Draft->value);

        if ($excluding->exists) {
            $query->whereKeyNot($excluding->getKey());
        }

        $other = (float) $query->sum('amount');

        return $other + (float) $excluding->amount;
    }
}
