<?php

namespace App\Http\Requests\Concerns;

use App\Models\Purchase;
use App\Support\PurchaseCurrency;
use App\Support\SupplierAccountLedger;
use App\Support\SupplierPaymentExchange;
use App\Support\SupplierPaymentMethod;
use Illuminate\Validation\Rule;
use Illuminate\Validation\Validator;

trait ValidatesSupplierPaymentFields
{
    /**
     * @return array<string, array<int, mixed>>
     */
    protected function supplierPaymentFieldRules(): array
    {
        return [
            'currency' => ['required', 'string', Rule::in(PurchaseCurrency::values())],
            'amount' => ['required', 'numeric', 'gt:0'],
            'paid_at' => ['required', 'date'],
            'payment_method' => ['nullable', 'string', Rule::in(SupplierPaymentMethod::values())],
            'reference' => ['nullable', 'string', 'max:100'],
            'notes' => ['nullable', 'string', 'max:2000'],
            'exchange_rate' => ['nullable', 'numeric', 'gt:0'],
            'bill_payment_amount' => ['nullable', 'numeric', 'gt:0'],
        ];
    }

    protected function validateSupplierPaymentExchange(
        Validator $validator,
        string $paymentCurrency,
        ?float $exchangeRate,
        string $billCurrency,
    ): void {
        if (SupplierPaymentExchange::requiresExchangeRate($paymentCurrency, $billCurrency)) {
            if ($exchangeRate === null || $exchangeRate <= 0) {
                $validator->errors()->add('exchange_rate', __('Exchange rate is required when payment currency differs from the bill.'));
            }
        }
    }

    protected function validateBillPaymentAmountRequired(
        Validator $validator,
        string $paymentCurrency,
        string $billCurrency,
        mixed $billPaymentAmount,
        string $errorKey = 'bill_payment_amount',
    ): void {
        if (! SupplierPaymentExchange::requiresExchangeRate($paymentCurrency, $billCurrency)) {
            return;
        }

        if ($billPaymentAmount === null || $billPaymentAmount === '' || (float) $billPaymentAmount <= 0) {
            $validator->errors()->add(
                $errorKey,
                __('Bill payment amount is required when payment currency differs from the bill.'),
            );
        }
    }

    protected function validateSupplierPaymentAgainstBill(
        Validator $validator,
        string $paymentCurrency,
        float $paymentAmount,
        ?float $exchangeRate,
        string $billCurrency,
        float $maxBillAllocation,
        string $amountErrorKey = 'amount',
    ): void {
        $this->validateSupplierPaymentExchange($validator, $paymentCurrency, $exchangeRate, $billCurrency);

        if ($validator->errors()->has('exchange_rate')) {
            return;
        }

        $billEquivalent = SupplierPaymentExchange::toBillCurrency(
            $paymentAmount,
            $paymentCurrency,
            $billCurrency,
            $exchangeRate,
        );

        if ($billEquivalent > $maxBillAllocation + 0.001) {
            $validator->errors()->add(
                $amountErrorKey,
                __('Payment amount exceeds the bill due amount after exchange.'),
            );
        }
    }

    /**
     * @param  list<array{purchase_id: int, amount: float|int|string}>  $allocations
     */
    protected function validateSupplierPaymentAllocations(
        Validator $validator,
        int $supplierId,
        string $paymentCurrency,
        float $paymentAmount,
        ?float $exchangeRate,
        array $allocations,
    ): void {
        if ($allocations === []) {
            return;
        }

        $allocationTotal = round(collect($allocations)->sum(fn (array $row): float => (float) $row['amount']), 2);

        $billCurrency = null;

        foreach ($allocations as $index => $allocation) {
            $purchase = Purchase::query()->find((int) $allocation['purchase_id']);

            if ($purchase === null || $purchase->supplier_id !== $supplierId) {
                continue;
            }

            if ($billCurrency === null) {
                $billCurrency = $purchase->currency;
            } elseif ($purchase->currency !== $billCurrency) {
                $validator->errors()->add('allocations', __('All allocated bills must use the same currency.'));

                return;
            }

            $due = SupplierAccountLedger::dueAmountForPurchase($purchase);

            if ((float) $allocation['amount'] > $due + 0.001) {
                $validator->errors()->add(
                    "allocations.{$index}.amount",
                    __('Allocation cannot exceed the bill due amount.'),
                );
            }
        }

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

        if (SupplierPaymentExchange::requiresExchangeRate($paymentCurrency, $billCurrency)) {
            if ($exchangeRate === null || $exchangeRate <= 0) {
                $validator->errors()->add('exchange_rate', __('Exchange rate is required when payment currency differs from the bill.'));

                return;
            }
        }

        $billLimit = SupplierPaymentExchange::toBillCurrency(
            $paymentAmount,
            $paymentCurrency,
            $billCurrency,
            $exchangeRate,
        );

        if ($allocationTotal > $billLimit + 0.001) {
            $validator->errors()->add('allocations', __('Allocations cannot exceed the payment amount.'));
        }
    }
}
