<?php

namespace Myvetshop\Module\Clinique\Accounting\Export\Exporter\Provider\Vat;

use Myvetshop\Module\Clinique\Accounting\Export\Exporter\Provider\OrderInvoiceLineProviderInterface;
use Myvetshop\Module\Clinique\Accounting\Export\Exporter\Provider\OrderSlipLineProviderInterface;
use Myvetshop\Module\Clinique\Accounting\Export\Model\SimpleAccountingLine;
use Myvetshop\Module\Clinique\Accounting\Export\Repository\OrderDetailTaxRepository;
use Myvetshop\Module\Clinique\Accounting\Export\Repository\OrderInvoiceTaxRepository;
use Myvetshop\Module\Clinique\Accounting\Export\Repository\OrderSlipDetailRepository;

class VatProvider implements OrderInvoiceLineProviderInterface, OrderSlipLineProviderInterface
{
    /**
     * @var array<numeric-string|string, string>
     */
    public const VAT_ACCOUNT_BY_RATE
        = [
            '5.500' => '4457111',
            '10.000' => '4457113',
            '20.000' => '445711',
        ];

    protected OrderDetailTaxRepository $orderDetailTaxRepository;

    protected OrderInvoiceTaxRepository $orderInvoiceTaxRepository;

    protected OrderSlipDetailRepository $orderSlipDetailRepository;

    /**
     * @param array<numeric-string|string, float> $amountsByRate
     *
     * @return list<SimpleAccountingLine>
     */
    protected function mapAmountsToAccount(array $amountsByRate, bool $asCredit): array
    {
        $ret = [];

        foreach ($amountsByRate as $rate => $amount) {
            if (!isset(self::VAT_ACCOUNT_BY_RATE[$rate])) {
                throw new \Exception('VAT Rate not supported');
            }

            $account = self::VAT_ACCOUNT_BY_RATE[$rate];

            $ret[] = new SimpleAccountingLine(
                $account,
                $asCredit ? $amount : 0.00,
                $asCredit ? 0.00 : $amount,
            );
        }

        return $ret;
    }

    public function __construct(
        OrderDetailTaxRepository $orderDetailTaxRepository,
        OrderInvoiceTaxRepository $orderInvoiceTaxRepository,
        OrderSlipDetailRepository $orderSlipDetailRepository
    ) {
        $this->orderDetailTaxRepository = $orderDetailTaxRepository;
        $this->orderInvoiceTaxRepository = $orderInvoiceTaxRepository;
        $this->orderSlipDetailRepository = $orderSlipDetailRepository;
    }

    public function getInvoiceAccountingLines(\OrderInvoice $orderInvoice, \Order $order): array
    {
        // Get VAT on Products (include VAT reduction by discounts)
        $vatByRate = \array_reduce(
            $this->orderDetailTaxRepository->getByOrder($order),
            function (array $carry, array $row) {
                $rate = \number_format($row['rate'], 3, '.', '');

                $carry[$rate] = ($carry[$rate] ?? 0.0) + $row['amount'];

                return $carry;
            },
            []
        );

        $totalProduct = \round(\array_sum($vatByRate), 2);

        // Get VAT on Shipping
        $shippingVat = \array_reduce(
            $this->orderInvoiceTaxRepository->getByOrderInvoice($orderInvoice),
            function (array $carry, array $row) {
                if ($row['type'] === 'shipping') {
                    $rate = \number_format($row['rate'], 3, '.', '');

                    $carry[$rate] = ($carry[$rate] ?? 0.0) + $row['amount'];
                }

                return $carry;
            },
            []
        );

        foreach ($shippingVat as $rate => $amount) {
            $vatByRate[$rate] = ($vatByRate[$rate] ?? 0.0) + $amount;
        }

        $totalVat = \round(\array_sum($vatByRate), 2);
        $discountVat = \round($orderInvoice->total_discount_tax_incl - $orderInvoice->total_discount_tax_excl, 2);
        $totalVatFinal = \round($orderInvoice->total_paid_tax_incl - $orderInvoice->total_paid_tax_excl, 2);

        if ($discountVat > 0 && \round($totalVat - $totalVatFinal - $discountVat, 2) === 0.0) {
            // Correction 1 : Discount VAT is not accounted by PrestaShop
            // TODO : Proper Discount tax rate handling handling
            if (!isset($vatByRate['20.000']) || \round($vatByRate['20.000'], 2) < $discountVat) {
                throw new \Exception("Can't process discount VAT. OrderInvoice #" . $orderInvoice->id);
            }

            $vatByRate['20.000'] -= $discountVat;
            $totalVat = \round($totalVat - $discountVat, 2);
        }

        $missingProductVat = \round($totalVatFinal - $totalVat, 2);
        if ($totalProduct == 0.0 && $missingProductVat > 0.0) {
            // Correction 2 : TVA was not computed at all by PrestaShop
            $vatByRate['20.000'] += $missingProductVat;
        }

        return $this->mapAmountsToAccount($vatByRate, true);
    }

    public function getSlipAccountingLines(\OrderSlip $orderSlip, \Order $order): array
    {
        $orderDetailTax = $this->orderDetailTaxRepository->getByOrder($order);

        $vatByRate = \array_reduce(
            $this->orderSlipDetailRepository->getByOrderSlip($orderSlip),
            function (array $carry, array $row) use ($orderDetailTax) {
                $taxDetail = $orderDetailTax[$row['id_order_detail']] ?? null;

                if (!$taxDetail) {
                    throw new \Exception("Can't get order detail tax for OrderDetail#" . $row['id_order_detail']);
                }

                $rate = \number_format($taxDetail['rate'], 3, '.', '');

                $carry[$rate] = ($carry[$rate] ?? 0.0) + ($row['amount_tax_incl'] - $row['amount_tax_excl']);

                return $carry;
            },
            []
        );

        if ($orderSlip->total_shipping_tax_incl != $orderSlip->total_shipping_tax_excl) {
            $carrierTaxRate = \number_format($order->carrier_tax_rate ?: 20.0, 3, '.', '');
            $vatByRate[$carrierTaxRate] = ($vatByRate[$carrierTaxRate] ?? 0) + \round(
                $orderSlip->total_shipping_tax_incl - $orderSlip->total_shipping_tax_excl,
                2
            );
        }

        // TODO : Handle proper tax rate for discount
        if ((int) $orderSlip->order_slip_type === 1) {
            $discountAmount = \round(
                $order->total_discounts_tax_incl - $order->total_discounts_tax_excl,
                2
            );
            if ($discountAmount) {
                if (!isset($vatByRate['20.000']) || \round($vatByRate['20.000'], 2) < $discountAmount) {
                    throw new \Exception('Error with discount VAT computation, aborting. Slip#' . $orderSlip->id);
                }
                $vatByRate['20.000'] -= $discountAmount;
            }
        }

        return $this->mapAmountsToAccount($vatByRate, false);
    }

    /**
     * @param \OrderInvoice|\OrderSlip $document
     */
    public function getAccountingLines($document, \Order $order): array
    {
        return $document instanceof \OrderInvoice
            ? $this->getInvoiceAccountingLines($document, $order)
            : $this->getSlipAccountingLines($document, $order);
    }
}
