<?php

declare(strict_types=1);

namespace App\Business\Settlement;

use App\Business\Settlement\Enums\SettlementAdjustmentDirection;
use App\Business\Settlement\Enums\SettlementAdjustmentStatus;
use App\Models\DberpPayableRecord;
use App\Models\DberpReceivableRecord;
use App\Models\DberpSettlementAdjustment;
use App\Services\DberpAuditService;
use Illuminate\Support\Facades\DB;
use Illuminate\Validation\ValidationException;

final class SettlementAdjustmentService
{
    public function __construct(
        private readonly SettlementRuleService $ruleService,
        private readonly ReceivableService $receivableService,
        private readonly PayableService $payableService,
        private readonly DberpAuditService $auditService,
    ) {}

    public function apply(DberpSettlementAdjustment $adjustment): void
    {
        DB::transaction(function () use ($adjustment): void {
            /** @var DberpSettlementAdjustment $locked */
            $locked = DberpSettlementAdjustment::query()->lockForUpdate()->findOrFail($adjustment->getKey());

            if ($locked->status !== SettlementAdjustmentStatus::Draft->value) {
                throw ValidationException::withMessages([
                    'status' => '仅未提交状态的结算调整允许生效。',
                ]);
            }

            $record = $this->resolveTargetRecord($locked);
            $signedAmount = $this->resolveSignedAmount($locked);

            if ((float) $record->remaining_amount + $signedAmount < -0.00005) {
                throw ValidationException::withMessages([
                    'amount' => '本次减少后会导致未核销余额为负，暂不允许直接生效。',
                ]);
            }

            $before = $this->auditService->snapshot($locked, $locked->getFillable());
            $effectiveAt = now();

            $this->auditService->logSemanticSave(
                'dberp.settlement_adjustment.applied',
                $locked,
                function () use ($locked, $record, $signedAmount, $effectiveAt): void {
                    $locked->forceFill([
                        'counterparty_type' => $locked->settlement_side === 'payable' ? 'supplier' : 'customer',
                        'counterparty_id' => $locked->settlement_side === 'payable'
                            ? (int) $record->supplier_id
                            : (int) $record->customer_id,
                        'effective_amount' => number_format($signedAmount, 4, '.', ''),
                        'status' => SettlementAdjustmentStatus::Effective->value,
                        'effective_at' => $effectiveAt,
                    ])->save();
                },
                $before,
                $this->auditService->snapshot($locked, $locked->getFillable()),
            );

            $this->refreshTargetRecord($locked, 'adjustment_effective', $effectiveAt);
        });
    }

    public function void(DberpSettlementAdjustment $adjustment): void
    {
        if (! $this->ruleService->allowsSettlementVoid()) {
            throw ValidationException::withMessages([
                'status' => __('dberp.errors.settlement_void_policy_blocked'),
            ]);
        }

        DB::transaction(function () use ($adjustment): void {
            /** @var DberpSettlementAdjustment $locked */
            $locked = DberpSettlementAdjustment::query()->lockForUpdate()->findOrFail($adjustment->getKey());

            if ($locked->status !== SettlementAdjustmentStatus::Effective->value) {
                throw ValidationException::withMessages([
                    'status' => '仅已生效的结算调整允许作废。',
                ]);
            }

            $before = $this->auditService->snapshot($locked, $locked->getFillable());
            $voidedAt = now();

            $this->auditService->logSemanticSave(
                'dberp.settlement_adjustment.voided',
                $locked,
                function () use ($locked, $voidedAt): void {
                    $locked->forceFill([
                        'effective_amount' => '0.0000',
                        'status' => SettlementAdjustmentStatus::Voided->value,
                        'voided_at' => $voidedAt,
                    ])->save();
                },
                $before,
                $this->auditService->snapshot($locked, $locked->getFillable()),
            );

            $this->refreshTargetRecord($locked, 'adjustment_voided', $voidedAt);
        });
    }

    private function refreshTargetRecord(DberpSettlementAdjustment $adjustment, string $changeType, mixed $businessTime): void
    {
        if ($adjustment->settlement_side === 'payable') {
            $record = $adjustment->payableRecord()->with('purchaseOrder')->first();

            if ($record?->purchaseOrder === null) {
                throw ValidationException::withMessages([
                    'payable_record_id' => '当前结算调整未找到可刷新的采购应付来源单。',
                ]);
            }

            $this->payableService->refreshForPurchaseOrder(
                $record->purchaseOrder,
                $changeType,
                $adjustment->remark,
                $adjustment,
                (string) $adjustment->code,
                $businessTime,
            );

            return;
        }

        $record = $adjustment->receivableRecord()->with('salesOrder')->first();

        if ($record?->salesOrder === null) {
            throw ValidationException::withMessages([
                'receivable_record_id' => '当前结算调整未找到可刷新的销售应收来源单。',
            ]);
        }

        $this->receivableService->refreshForSalesOrder(
            $record->salesOrder,
            $changeType,
            $adjustment->remark,
            $adjustment,
            (string) $adjustment->code,
            $businessTime,
        );
    }

    private function resolveSignedAmount(DberpSettlementAdjustment $adjustment): float
    {
        $direction = SettlementAdjustmentDirection::from((string) $adjustment->adjustment_direction);

        return $direction->signedAmount((float) $adjustment->amount);
    }

    private function resolveTargetRecord(DberpSettlementAdjustment $adjustment): DberpReceivableRecord|DberpPayableRecord
    {
        if ($adjustment->settlement_side === 'payable') {
            $record = $adjustment->payableRecord;

            if ($record === null) {
                throw ValidationException::withMessages([
                    'payable_record_id' => '请选择要调整的应付记录。',
                ]);
            }

            return $record;
        }

        $record = $adjustment->receivableRecord;

        if ($record === null) {
            throw ValidationException::withMessages([
                'receivable_record_id' => '请选择要调整的应收记录。',
            ]);
        }

        return $record;
    }
}
