<?php

declare(strict_types=1);

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,
    ) {}

    public function assertReceiptWithinPolicy(DberpReceiptOrder $order): 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;
        }

        $this->auditService->log('dberp.receipt_order.over_limit_blocked', $order, null, $auditPayload);

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

    public function assertPaymentWithinPolicy(DberpPaymentOrder $order): 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;
        }

        $this->auditService->log('dberp.payment_order.over_limit_blocked', $order, null, $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);

        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);

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

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

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