<?php

declare(strict_types=1);

namespace App\Business\Settlement;

use App\Business\Settlement\Enums\AdvanceAccountFlowType;
use App\Business\Settlement\Enums\AdvanceAccountStatus;
use App\Models\DberpAdvanceAccount;
use App\Models\DberpAdvanceAccountFlow;
use App\Models\DberpPaymentOrder;
use App\Models\DberpReceiptOrder;
use App\Models\DberpSettlementWriteOffLine;
use App\Support\DberpCurrencySelectOptions;

final class AdvanceAccountService
{
    public function confirmReceipt(DberpReceiptOrder $order): void
    {
        $account = $this->ensureAccount('customer', (int) $order->customer_id, 'advance_receipt', (string) ($order->source_system ?? 'manual'), (string) ($order->external_reference_id ?? ''));
        $amount = (float) $order->amount;

        $account->forceFill([
            'total_amount' => number_format((float) $account->total_amount + $amount, 4, '.', ''),
            'available_amount' => number_format((float) $account->available_amount + $amount, 4, '.', ''),
            'last_business_time' => $order->business_time,
            'account_status' => AdvanceAccountStatus::Active->value,
        ])->save();

        $this->recordFlow(
            account: $account,
            flowType: AdvanceAccountFlowType::ReceiptConfirmed->value,
            changeAmount: number_format($amount, 4, '.', ''),
            businessDate: $order->business_time,
            balanceAfter: (string) $account->available_amount,
            receiptOrder: $order,
            remark: $order->remark,
        );
    }

    public function confirmPayment(DberpPaymentOrder $order): void
    {
        $account = $this->ensureAccount('supplier', (int) $order->supplier_id, 'advance_payment', (string) ($order->source_system ?? 'manual'), (string) ($order->external_reference_id ?? ''));
        $amount = (float) $order->amount;

        $account->forceFill([
            'total_amount' => number_format((float) $account->total_amount + $amount, 4, '.', ''),
            'available_amount' => number_format((float) $account->available_amount + $amount, 4, '.', ''),
            'last_business_time' => $order->business_time,
            'account_status' => AdvanceAccountStatus::Active->value,
        ])->save();

        $this->recordFlow(
            account: $account,
            flowType: AdvanceAccountFlowType::PaymentConfirmed->value,
            changeAmount: number_format($amount, 4, '.', ''),
            businessDate: $order->business_time,
            balanceAfter: (string) $account->available_amount,
            paymentOrder: $order,
            remark: $order->remark,
        );
    }

    public function voidReceipt(DberpReceiptOrder $order): void
    {
        $account = $this->findAccount('customer', (int) $order->customer_id, 'advance_receipt');

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

        $amount = (float) $order->amount;

        $account->forceFill([
            'total_amount' => number_format((float) $account->total_amount - $amount, 4, '.', ''),
            'available_amount' => number_format((float) $account->available_amount - $amount, 4, '.', ''),
            'last_business_time' => now(),
            'account_status' => $this->resolveAccountStatus((float) $account->total_amount - $amount),
        ])->save();

        $this->recordFlow(
            account: $account,
            flowType: AdvanceAccountFlowType::ReceiptVoided->value,
            changeAmount: number_format(-$amount, 4, '.', ''),
            businessDate: now(),
            balanceAfter: (string) $account->available_amount,
            receiptOrder: $order,
            remark: $order->remark,
        );
    }

    public function voidPayment(DberpPaymentOrder $order): void
    {
        $account = $this->findAccount('supplier', (int) $order->supplier_id, 'advance_payment');

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

        $amount = (float) $order->amount;

        $account->forceFill([
            'total_amount' => number_format((float) $account->total_amount - $amount, 4, '.', ''),
            'available_amount' => number_format((float) $account->available_amount - $amount, 4, '.', ''),
            'last_business_time' => now(),
            'account_status' => $this->resolveAccountStatus((float) $account->total_amount - $amount),
        ])->save();

        $this->recordFlow(
            account: $account,
            flowType: AdvanceAccountFlowType::PaymentVoided->value,
            changeAmount: number_format(-$amount, 4, '.', ''),
            businessDate: now(),
            balanceAfter: (string) $account->available_amount,
            paymentOrder: $order,
            remark: $order->remark,
        );
    }

    public function consumeReceipt(DberpReceiptOrder $order, DberpSettlementWriteOffLine $line, string $amount): void
    {
        $account = $this->ensureAccount('customer', (int) $order->customer_id, 'advance_receipt', (string) ($order->source_system ?? 'manual'), (string) ($order->external_reference_id ?? ''));
        $value = (float) $amount;

        $account->forceFill([
            'used_amount' => number_format((float) $account->used_amount + $value, 4, '.', ''),
            'available_amount' => number_format((float) $account->available_amount - $value, 4, '.', ''),
            'last_business_time' => $line->business_date,
        ])->save();

        $this->recordFlow(
            account: $account,
            flowType: AdvanceAccountFlowType::ReceiptConsumed->value,
            changeAmount: number_format(-$value, 4, '.', ''),
            businessDate: $line->business_date,
            balanceAfter: (string) $account->available_amount,
            receiptOrder: $order,
            writeOffLine: $line,
            remark: $line->remark,
        );
    }

    public function consumePayment(DberpPaymentOrder $order, DberpSettlementWriteOffLine $line, string $amount): void
    {
        $account = $this->ensureAccount('supplier', (int) $order->supplier_id, 'advance_payment', (string) ($order->source_system ?? 'manual'), (string) ($order->external_reference_id ?? ''));
        $value = (float) $amount;

        $account->forceFill([
            'used_amount' => number_format((float) $account->used_amount + $value, 4, '.', ''),
            'available_amount' => number_format((float) $account->available_amount - $value, 4, '.', ''),
            'last_business_time' => $line->business_date,
        ])->save();

        $this->recordFlow(
            account: $account,
            flowType: AdvanceAccountFlowType::PaymentConsumed->value,
            changeAmount: number_format(-$value, 4, '.', ''),
            businessDate: $line->business_date,
            balanceAfter: (string) $account->available_amount,
            paymentOrder: $order,
            writeOffLine: $line,
            remark: $line->remark,
        );
    }

    public function releaseReceipt(DberpReceiptOrder $order, DberpSettlementWriteOffLine $line, string $amount): void
    {
        $account = $this->ensureAccount('customer', (int) $order->customer_id, 'advance_receipt', (string) ($order->source_system ?? 'manual'), (string) ($order->external_reference_id ?? ''));
        $value = (float) $amount;

        $account->forceFill([
            'used_amount' => number_format((float) $account->used_amount - $value, 4, '.', ''),
            'available_amount' => number_format((float) $account->available_amount + $value, 4, '.', ''),
            'last_business_time' => now(),
        ])->save();

        $this->recordFlow(
            account: $account,
            flowType: AdvanceAccountFlowType::ReceiptReleased->value,
            changeAmount: number_format($value, 4, '.', ''),
            businessDate: now(),
            balanceAfter: (string) $account->available_amount,
            receiptOrder: $order,
            writeOffLine: $line,
            remark: $line->remark,
        );
    }

    public function releasePayment(DberpPaymentOrder $order, DberpSettlementWriteOffLine $line, string $amount): void
    {
        $account = $this->ensureAccount('supplier', (int) $order->supplier_id, 'advance_payment', (string) ($order->source_system ?? 'manual'), (string) ($order->external_reference_id ?? ''));
        $value = (float) $amount;

        $account->forceFill([
            'used_amount' => number_format((float) $account->used_amount - $value, 4, '.', ''),
            'available_amount' => number_format((float) $account->available_amount + $value, 4, '.', ''),
            'last_business_time' => now(),
        ])->save();

        $this->recordFlow(
            account: $account,
            flowType: AdvanceAccountFlowType::PaymentReleased->value,
            changeAmount: number_format($value, 4, '.', ''),
            businessDate: now(),
            balanceAfter: (string) $account->available_amount,
            paymentOrder: $order,
            writeOffLine: $line,
            remark: $line->remark,
        );
    }

    private function ensureAccount(string $counterpartyType, int $counterpartyId, string $direction, string $sourceSystem, string $externalReferenceId): DberpAdvanceAccount
    {
        return DberpAdvanceAccount::query()->firstOrCreate(
            [
                'counterparty_type' => $counterpartyType,
                'counterparty_id' => $counterpartyId,
                'advance_direction' => $direction,
                'currency_code' => DberpCurrencySelectOptions::defaultCode(),
            ],
            [
                'source_system' => $sourceSystem !== '' ? $sourceSystem : 'manual',
                'external_reference_id' => $externalReferenceId !== '' ? $externalReferenceId : null,
                'account_status' => AdvanceAccountStatus::Active->value,
            ],
        );
    }

    private function findAccount(string $counterpartyType, int $counterpartyId, string $direction): ?DberpAdvanceAccount
    {
        return DberpAdvanceAccount::query()
            ->where('counterparty_type', $counterpartyType)
            ->where('counterparty_id', $counterpartyId)
            ->where('advance_direction', $direction)
            ->where('currency_code', DberpCurrencySelectOptions::defaultCode())
            ->first();
    }

    private function resolveAccountStatus(float $totalAmount): string
    {
        return abs($totalAmount) <= 0.00005
            ? AdvanceAccountStatus::Closed->value
            : AdvanceAccountStatus::Active->value;
    }

    private function recordFlow(
        DberpAdvanceAccount $account,
        string $flowType,
        string $changeAmount,
        mixed $businessDate,
        string $balanceAfter,
        ?DberpReceiptOrder $receiptOrder = null,
        ?DberpPaymentOrder $paymentOrder = null,
        ?DberpSettlementWriteOffLine $writeOffLine = null,
        ?string $remark = null,
    ): void {
        DberpAdvanceAccountFlow::query()->create([
            'account_id' => $account->getKey(),
            'flow_type' => $flowType,
            'receipt_order_id' => $receiptOrder?->getKey(),
            'payment_order_id' => $paymentOrder?->getKey(),
            'write_off_line_id' => $writeOffLine?->getKey(),
            'change_amount' => $changeAmount,
            'balance_after' => $balanceAfter,
            'business_date' => $businessDate ?? now(),
            'remark' => $remark,
        ]);
    }
}
