<?php

declare(strict_types=1);

namespace App\Jobs\Eboekhouden;

use App\Actions\Merchant\GetAmountOfTransactionsPerPaymentMethodAction;
use App\Enums\Subscription;
use App\Models\Merchant;
use App\Models\MerchantPaymentMethod;
use App\Models\Transaction;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Arr;
use IntVent\EBoekhouden\Client;
use IntVent\EBoekhouden\Exceptions\EboekhoudenException;
use IntVent\EBoekhouden\Exceptions\EboekhoudenSoapException;
use IntVent\EBoekhouden\Models\EboekhoudenInvoice;
use IntVent\EBoekhouden\Models\EboekhoudenInvoiceLine;
use Psr\Log\LoggerInterface;

final class GenerateInvoiceForMerchant implements ShouldQueue
{
    use Queueable;
    use InteractsWithQueue;
    use Dispatchable;
    use SerializesModels;

    public function __construct(
        protected readonly Merchant $merchant
    ) {
    }

    public function handle(
        Client $client,
        GetAmountOfTransactionsPerPaymentMethodAction $getAmountOfTransactionsPerPaymentMethod,
        LoggerInterface $logger,
    ): void {
        $invoiceLines = [];
        $transactionsPerPaymentMethod = $getAmountOfTransactionsPerPaymentMethod->handle($this->merchant);

        $invoicedTransactions = collect();

        /**
         * @var int $key
         * @var Collection<int, Transaction> $transactions
         */
        foreach ($transactionsPerPaymentMethod as $key => $transactions) {
            $amountOfTransactions = $transactions->sum('amount_of_transactions');
            $transactionsAreRefunded = false;
            $isExcludedPaymentMethod = false;
            $paymentMethod = $this->merchant->paymentMethods()->firstWhere('payment_method_id', $key);

            $transaction = $transactions->first();
            $lowercasePaymentMethod = strtolower($transaction->payment_method_name);

            // Check if the payment method name contains "refund"
            if (str_contains(strtolower($transaction->payment_method_name), 'refund')) {
                $transactionsAreRefunded = true;
            }

            $invoiceLines[] = (new EboekhoudenInvoiceLine())
                ->setDescription($transaction->payment_method_name)
                ->setPrice(floatval($paymentMethod->fee ?? MerchantPaymentMethod::DEFAULT_FEES[$transaction->payment_method_name]))
                ->setAmount($amountOfTransactions)
                ->setCode(MerchantPaymentMethod::DEFAULT_CODES[$transaction->payment_method_name])
                ->setTaxCode($this->merchant->tax_code->value)
                ->setLedgerCode(strval(config('services.eboekhouden.ledger_code')));

            $exceptedPaymentMethods = [
                'credit (terminal) - pin (mastercard)' => 1.90,
                'credit (terminal) - pin (visa)' => 1.90,
                'credit (terminal) - pin (american express)' => 1.90,
            ];

            if (Arr::exists($exceptedPaymentMethods, $transaction->payment_method_name)) {
                $isExcludedPaymentMethod = true;
            }

            if (!$transactionsAreRefunded && (!empty($paymentMethod->provision) || $isExcludedPaymentMethod)) {
                $provision = $isExcludedPaymentMethod
                    ? $exceptedPaymentMethods[$transaction->payment_method_name]
                    : $paymentMethod->provision;
                $turnover = $transactions->sum('turnover');
                $turnoverProvision = ($turnover / 100) * $provision;

                $invoiceLines[] = (new EboekhoudenInvoiceLine())
                    ->setDescription("Provisie {$turnover} x {$provision}%")
                    ->setPrice(floatval($turnoverProvision))
                    ->setAmount(1)
                    ->setCode(MerchantPaymentMethod::DEFAULT_CODES['provision'])
                    ->setTaxCode($this->merchant->tax_code->value)
                    ->setLedgerCode(strval(config('services.eboekhouden.ledger_code')));
            }

            $invoicedTransactions->push(...$transactions);
        }

        if ($this->merchant->amount_of_terminals > 0) {
            $invoiceLines[] = (new EboekhoudenInvoiceLine())
                ->setDescription("Abonnement pay terminal per kwartaal")
                ->setPrice(floatval(37.50))
                ->setAmount($this->merchant->amount_of_terminals)
                ->setCode(MerchantPaymentMethod::DEFAULT_CODES['terminal'])
                ->setTaxCode($this->merchant->tax_code->value)
                ->setLedgerCode(strval(config('services.eboekhouden.ledger_code')));
        }

        if ($this->merchant->contract_package !== Subscription::ALLIANCE) {
            $invoiceLines[] = (new EboekhoudenInvoiceLine())
                ->setDescription("Abonnement Pay plus per kwartaal")
                ->setPrice(floatval(60.00))
                ->setAmount(1)
                ->setCode(MerchantPaymentMethod::DEFAULT_CODES['pay plus'])
                ->setTaxCode($this->merchant->tax_code->value)
                ->setLedgerCode(strval(config('services.eboekhouden.ledger_code')));
        }

        if (count($invoiceLines) === 0) {
            return;
        }

        if ($this->merchant->eboekhouden_debtor_id === null) {
            return;
        }

        try {
            $invoice = (new EboekhoudenInvoice())
                ->setRelationCode(strval($this->merchant->eboekhouden_debtor_id))
                ->setLines($invoiceLines)
                ->setInvoiceTemplate(config('services.eboekhouden.invoice_template'));

            $client->addInvoice($invoice);

            if ($invoicedTransactions->count() > 0) {
                $invoicedTransactions->each(fn (Transaction $transaction) => $transaction->update([
                    'invoiced' => true,
                ]));
            }
        } catch (EboekhoudenException|EboekhoudenSoapException $exception) {
            $logger->error($exception->getMessage(), [
                'code' => $exception->getCode(),
                'trace' => $exception->getTraceAsString(),
            ]);

            $this->fail($exception);
        }
    }
}
