<?php

declare(strict_types=1);

require_once __DIR__ . '/OrderProblemInterface.php';

class OrderProblemAvoir implements OrderProblemInterface
{
    /**
     * @var Order
     */
    protected $order;

    /**
     * @var OrderSlip[]
     */
    protected $orderSlips = [];

    /**
     * @var string[];
     */
    protected $errors;

    /**
     * @return string[] $errors
     */
    protected function isProblematicOrderSlip(OrderSlip $orderSlip): array
    {
        $ret = [];

        $assumedOrderSlipType = (7 == $this->order->current_state && $this->order->total_discounts_tax_excl > 0) ? 1 : $orderSlip->order_slip_type;

        // Mauvais type d'avoir pour une commande remboursée en intégralité
        if ((int) $orderSlip->order_slip_type != $assumedOrderSlipType) {
            $ret[] = "Avoir : Mauvais type d'avoir : " . $orderSlip->order_slip_type;
        }

        /** @var array<mixed> $orderSlipDetails */
        $orderSlipDetails = OrderSlip::getOrdersSlipDetail($orderSlip->id);

        // Vérifie la cohérence entre prix unitaire, quantité et prix total pour chaque ligne de l'avoir
        foreach ($orderSlipDetails as $orderSlipDetail) {
            if (
                round((float) $orderSlipDetail['total_price_tax_excl'], 2) != round((float) $orderSlipDetail['unit_price_tax_excl'] * (int) $orderSlipDetail['product_quantity'], 2)
                || round((float) $orderSlipDetail['total_price_tax_incl'], 2) != round((float) $orderSlipDetail['unit_price_tax_incl'] * (int) $orderSlipDetail['product_quantity'], 2)
            ) {
                $ret[] = 'Avoir : Erreur de cohérence sur la ligne ' . $orderSlipDetail['id_order_detail'];
            }
        }

        // Calcul de la somme des produits HT et TTC à partir du détail de l'avoir
        $total_product_tax_excl = array_reduce(
            $orderSlipDetails,
            fn ($sum, array $orderSlipDetail) => $sum + $orderSlipDetail['total_price_tax_excl'],
            0
        );
        $total_product_tax_excl = round($total_product_tax_excl, 2);

        // Vérification de la somme des produits TTC
        $total_product_tax_incl = array_reduce(
            $orderSlipDetails,
            fn ($sum, array $orderSlipDetail) => $sum + $orderSlipDetail['total_price_tax_incl'],
            0
        );
        $total_product_tax_incl = round($total_product_tax_incl, 2);

        if (0 == $assumedOrderSlipType) {
            // Avoir - remboursement partiel (ou total sans réduction) (type = 0)
            if (round($orderSlip->total_products_tax_excl, 2) != $total_product_tax_excl) {
                $ret[] = 'Avoir : total_products_tax_excl incoherent [' . $orderSlip->total_products_tax_excl . ' != ' . $total_product_tax_excl . ']';
            }

            if (round($orderSlip->total_products_tax_incl, 2) != $total_product_tax_incl) {
                $ret[] = 'Avoir : total_products_tax_incl incoherent [' . $orderSlip->total_products_tax_incl . ' != ' . $total_product_tax_incl . ']';
            }
        } elseif (1 == $assumedOrderSlipType) {
            // Avoir - remboursement avec réduction (type = 1)
            if (round($orderSlip->total_products_tax_excl + $this->order->total_discounts_tax_excl, 2) != $total_product_tax_excl) {
                $ret[] = 'Avoir : total_products_tax_excl incoherent [' . $total_product_tax_excl . ' != ' . ($orderSlip->total_products_tax_excl + $this->order->total_discounts_tax_excl) . ']';
            }

            if (round($orderSlip->total_products_tax_incl + $this->order->total_discounts_tax_incl, 2) != $total_product_tax_incl) {
                $ret[] = 'Avoir : total_products_tax_incl incoherent [' . $total_product_tax_incl . ' != ' . ($orderSlip->total_products_tax_incl + $this->order->total_discounts_tax_incl) . ']';
            }

            // Vérifie que tous les montants correspondent
            foreach ($orderSlipDetails as $orderSlipDetail) {
                $orderDetail = new OrderDetail($orderSlipDetail['id_order_detail']);

                if (
                    round($orderSlipDetail['total_price_tax_excl'], 2) != round($orderDetail->total_price_tax_excl, 2)
                    || round($orderSlipDetail['total_price_tax_incl'], 2) != round($orderDetail->total_price_tax_incl, 2)
                    || round($orderSlipDetail['unit_price_tax_excl'], 2) != round($orderDetail->unit_price_tax_excl, 2)
                    || round($orderSlipDetail['unit_price_tax_incl'], 2) != round($orderDetail->unit_price_tax_incl, 2)
                ) {
                    $ret[] = 'Avoir : la ligne produit n\'est pas en accord avec la commande ' . $orderSlipDetail['id_order_detail'];
                }

                if (
                    round((float) $orderSlipDetail['total_price_tax_excl'], 2) != round((float) $orderSlipDetail['unit_price_tax_excl'] * (int) $orderSlipDetail['product_quantity'], 2)
                    || round((float) $orderSlipDetail['total_price_tax_incl'], 2) != round((float) $orderSlipDetail['unit_price_tax_incl'] * (int) $orderSlipDetail['product_quantity'], 2)
                ) {
                    $ret[] = 'Avoir : Erreur de cohérence sur la ligne ' . $orderSlipDetail['id_order_detail'];
                }
            }
        }

        if (round((float) $orderSlip->amount, 2) != round((float) $orderSlip->total_products_tax_excl + (float) $orderSlip->total_shipping_tax_excl, 2)) {
            $left = round((float) $orderSlip->amount, 2);
            $right = round((float) $orderSlip->total_products_tax_excl + (float) $orderSlip->total_shipping_tax_excl, 2);
            $ret[] = 'Avoir : amount incorrect [' . $left . ' != ' . $right . ']';
        }

        return $ret;
    }

    protected function repairOrderSlip(OrderSlip $orderSlip): bool
    {
        if (0 === count($this->isProblematicOrderSlip($orderSlip))) {
            return false;
        }

        $assumedOrderSlipType = (7 == $this->order->current_state && $this->order->total_discounts_tax_excl > 0) ? 1 : $orderSlip->order_slip_type;

        // Corrige le type d'avoir pour une commande intégralement remboursée
        if ((int) $orderSlip->order_slip_type != $assumedOrderSlipType) {
            $orderSlip->order_slip_type = $assumedOrderSlipType;
        }

        /** @var array<mixed> $orderSlipDetails */
        $orderSlipDetails = OrderSlip::getOrdersSlipDetail($orderSlip->id);

        // Corrige la cohérence entre prix unitaire et quantité pour chaque ligne de l'avoir
        foreach ($orderSlipDetails as &$orderSlipDetail) {
            if (1 == $orderSlip->order_slip_type) {
                $orderDetail = new OrderDetail($orderSlipDetail['id_order_detail']);

                if (
                    $orderSlipDetail['total_price_tax_excl'] != $orderDetail->total_price_tax_excl
                    || $orderSlipDetail['total_price_tax_incl'] != $orderDetail->total_price_tax_incl
                    || $orderSlipDetail['unit_price_tax_excl'] != $orderDetail->unit_price_tax_excl
                    || $orderSlipDetail['unit_price_tax_incl'] != $orderDetail->unit_price_tax_incl
                ) {
                    $orderSlipDetail['total_price_tax_excl'] = $orderDetail->total_price_tax_excl;
                    $orderSlipDetail['total_price_tax_incl'] = $orderDetail->total_price_tax_incl;
                    $orderSlipDetail['unit_price_tax_excl'] = $orderDetail->unit_price_tax_excl;
                    $orderSlipDetail['unit_price_tax_incl'] = $orderDetail->unit_price_tax_incl;

                    // Mise à jour du OrderSlipDetail
                    Db::getInstance(true)
                        ->update(
                            'order_slip_detail',
                            [
                                'total_price_tax_excl' => $orderSlipDetail['total_price_tax_excl'],
                                'total_price_tax_incl' => $orderSlipDetail['total_price_tax_incl'],
                                'unit_price_tax_excl' => $orderSlipDetail['unit_price_tax_excl'],
                                'unit_price_tax_incl' => $orderSlipDetail['unit_price_tax_incl'],
                            ],
                            ' id_order_slip = ' . (int) $orderSlipDetail['id_order_slip'] . ' AND id_order_detail = ' . (int) $orderSlipDetail['id_order_detail']
                        );
                }
            }

            $detail_unit_price_tax_excl = round((float) $orderSlipDetail['total_price_tax_excl'] / (int) $orderSlipDetail['product_quantity'], 3);
            $detail_unit_price_tax_incl = round((float) $orderSlipDetail['total_price_tax_incl'] / (int) $orderSlipDetail['product_quantity'], 3);

            if (
                $detail_unit_price_tax_excl != (float) $orderSlipDetail['unit_price_tax_excl']
                || $detail_unit_price_tax_incl != (float) $orderSlipDetail['unit_price_tax_incl']
            ) {
                $orderSlipDetail['unit_price_tax_excl'] = $detail_unit_price_tax_excl;
                $orderSlipDetail['unit_price_tax_incl'] = $detail_unit_price_tax_incl;

                // Mise à jour du OrderSlipDetail
                Db::getInstance(true)
                    ->update(
                        'order_slip_detail',
                        [
                            'unit_price_tax_excl' => $detail_unit_price_tax_excl,
                            'unit_price_tax_incl' => $detail_unit_price_tax_incl,
                        ],
                        ' id_order_slip = ' . (int) $orderSlipDetail['id_order_slip'] . ' AND id_order_detail = ' . (int) $orderSlipDetail['id_order_detail']
                    );
            }
        }

        // Calcul de la somme des produits HT et TTC à partir du détail de l'avoir
        $total_product_tax_excl = array_reduce(
            $orderSlipDetails,
            fn ($sum, array $orderSlipDetail) => $sum + $orderSlipDetail['total_price_tax_excl'],
            0
        );

        // Vérification de la somme des produits TTC
        $total_product_tax_incl = array_reduce(
            $orderSlipDetails,
            fn ($sum, array $orderSlipDetail) => $sum + $orderSlipDetail['total_price_tax_incl'],
            0
        );

        if (0 == $orderSlip->order_slip_type) {
            // Avoir - remboursement partiel (ou total sans réduction) (type = 0)
            $orderSlip->total_products_tax_excl = $total_product_tax_excl;
            $orderSlip->total_products_tax_incl = $total_product_tax_incl;
        } elseif (1 == $orderSlip->order_slip_type) {
            // Avoir - remboursement total avec réduction (type = 1)
            $orderSlip->total_products_tax_excl = $total_product_tax_excl - $this->order->total_discounts_tax_excl;
            $orderSlip->total_products_tax_incl = $total_product_tax_incl - $this->order->total_discounts_tax_incl;
        }

        // Montant total de l'avoir
        $orderSlip->amount = round((float) $orderSlip->total_products_tax_excl + (float) $orderSlip->total_shipping_tax_excl, 2);

        $orderSlip->save();

        return true;
    }

    public function __construct(Order $order)
    {
        $this->order = $order;
        $this->orderSlips = $order->getOrderSlipsCollection()->getResults();
        $this->errors = [];

        foreach ($this->orderSlips as $orderSlip) {
            $this->errors += $this->isProblematicOrderSlip($orderSlip);
        }
    }

    public function getErrorMessage(): string
    {
        return "Les valeurs d'un avoir ne sont pas cohérentes";
    }

    /**
     * @return string[]
     */
    public function getErrors(): array
    {
        return $this->errors;
    }

    public function isProblematic(): bool
    {
        return count($this->errors) > 0;
    }

    public function isRepairable(): bool
    {
        return $this->isProblematic();
    }

    public function repair(): bool
    {
        $ret = false;

        foreach ($this->orderSlips as $orderSlip) {
            if ($this->repairOrderSlip($orderSlip)) {
                $ret = true;
            }
        }

        return $ret;
    }
}
