<?php

declare(strict_types=1);

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

    /**
     * @var OrderDetail[]
     */
    protected $details;

    /**
     * @var OrderCartRule[]
     */
    protected $discounts;

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

    /**
     * @var bool
     */
    protected $repairable;

    /**
     * @return array<string, float>
     */
    protected function getOrderSubtotals()
    {
        return [
            // Montant des produits HT
            'product_total_tax_excl' => round(array_reduce(
                $this->details,
                fn ($sum, OrderDetail $orderDetail) => $sum + $orderDetail->total_price_tax_excl,
                0
            ), 2),
            // Montant des produits TTC
            'product_total_tax_incl' => round(array_reduce(
                $this->details,
                fn ($sum, OrderDetail $orderDetail) => $sum + $orderDetail->total_price_tax_incl,
                0
            ), 2),
            // Montant total des réductions (HT)
            'discount_total_tax_excl' => round(array_reduce(
                $this->discounts,
                fn ($sum, OrderCartRule $orderCartRule) => $sum + $orderCartRule->value_tax_excl,
                0
            ), 2),
            // Montant total des réductions (TTC)
            'discount_total_tax_incl' => round(array_reduce(
                $this->discounts,
                fn ($sum, OrderCartRule $orderCartRule) => $sum + $orderCartRule->value,
                0
            ), 2),
        ];
    }

    /**
     * @param bool $repairable
     *
     * @return string[]
     */
    protected function computeErrors(&$repairable): array
    {
        $ret = [];
        $repairable = true;

        $subTotals = $this->getOrderSubtotals();

        $productDiffHt = round(abs(round((float) $this->order->total_products, 2) - $subTotals['product_total_tax_excl']), 2);
        if ($productDiffHt > 0.02) {
            $left = (float) $this->order->total_products;
            $right = $subTotals['product_total_tax_excl'];
            $ret[] = 'Commande : o.total_products != sum(od.total_price_tax_excl) [' . $left . ' != ' . $right . ']';
        }

        $productDiffttc = round(abs(round((float) $this->order->total_products_wt, 2) - $subTotals['product_total_tax_incl']), 2);
        if ($productDiffttc > 0.02) {
            $left = (float) $this->order->total_products_wt;
            $right = $subTotals['product_total_tax_incl'];
            $ret[] = 'Commande : o.total_products_wt != sum(od.total_price_tax_incl) [' . $left . ' != ' . $right . ']';
        }

        if (round((float) $this->order->total_discounts_tax_excl, 2) != $subTotals['discount_total_tax_excl']) {
            $left = (float) $this->order->total_discounts_tax_excl;
            $right = $subTotals['discount_total_tax_excl'];
            $ret[] = 'Commande : o.total_discounts_tax_excl != sum(ocr.value_tax_excl) [' . $left . ' != ' . $right . ']';
        }

        if (round((float) $this->order->total_discounts_tax_incl, 2) != $subTotals['discount_total_tax_incl']) {
            $left = (float) $this->order->total_discounts_tax_incl;
            $right = $subTotals['discount_total_tax_incl'];
            $ret[] = 'Commande : o.total_discounts_tax_incl != sum(ocr.value) [' . $left . ' != ' . $right . ']';
        }

        // Si le total HT n'est pas bon, erreur
        $orderTotalHt = round(
            round((float) $this->order->total_products, 2)
            + round((float) $this->order->total_shipping_tax_excl, 2)
            - round((float) $this->order->total_discounts_tax_excl, 2), 2);
        if ($orderTotalHt != round((float) $this->order->total_paid_tax_excl, 2)) {
            $right = $orderTotalHt;
            $left = round((float) $this->order->total_paid_tax_excl, 2);
            $ret[] = 'Commande : o.total_paid_tax_excl != sum(o.total_products) + o.total_shipping_tax_excl - o.total_discounts_tax_excl [' . $left . ' != ' . $right . ']';

            if (round(abs($right - $left), 2) > 0.02) {
                $repairable = false;
            }
        }

        // Si le total TTC n'et pas bon, erreur
        $orderTotalTtc = round(
            round((float) $this->order->total_products_wt, 2)
            + round((float) $this->order->total_shipping_tax_incl, 2)
            - round((float) $this->order->total_discounts_tax_incl, 2), 2
        );
        if ($orderTotalTtc != round((float) $this->order->total_paid_tax_incl, 2)) {
            $right = $orderTotalTtc;
            $left = round((float) $this->order->total_paid_tax_incl, 2);

            $ret[] = 'Commande : o.total_paid_tax_incl != o.total_products_wt + o.total_shipping_tax_incl - o.total_paid_tax_incl [' . $left . ' != ' . $right . ']';

            if (round(abs($right - $left), 2) > 0.02) {
                $repairable = false;
            }
        }

        return $ret;
    }

    public function __construct(Order $order)
    {
        $this->order = $order;
        $this->details = ObjectModel::hydrateCollection(OrderDetail::class, $order->getOrderDetailList());
        $this->discounts = ObjectModel::hydrateCollection(OrderCartRule::class, $order->getCartRules());
        $this->errors = $this->computeErrors($this->repairable);
    }

    public function getErrorMessage(): string
    {
        return 'Commande : Les sommes des produits et fdp ne correspondent pas au total de la commande';
    }

    /**
     * @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() && $this->repairable;
    }

    public function repair(): bool
    {
        $subTotals = $this->getOrderSubtotals();

        $this->order->total_products = $subTotals['product_total_tax_excl'];
        $this->order->total_products_wt = $subTotals['product_total_tax_incl'];
        $this->order->total_discounts_tax_excl = $subTotals['discount_total_tax_excl'];
        $this->order->total_discounts_tax_incl = $subTotals['discount_total_tax_incl'];

        // Si le total HT n'est pas bon, erreur
        $orderTotalHt = round(
            round((float) $this->order->total_products, 2)
            + round((float) $this->order->total_shipping_tax_excl, 2)
            - round((float) $this->order->total_discounts_tax_excl, 2), 2);
        if ($orderTotalHt != round((float) $this->order->total_paid_tax_excl, 2)) {
            $correction = round($this->order->total_paid_tax_excl - $orderTotalHt, 2);

            if ($correction <= 0.02) {
                $this->order->total_products += $correction;
            }
        }

        // Si le total TTC n'et pas bon, erreur
        $orderTotalTtc = round(
            round((float) $this->order->total_products_wt, 2)
            + round((float) $this->order->total_shipping_tax_incl, 2)
            - round((float) $this->order->total_discounts_tax_incl, 2), 2
        );
        if ($orderTotalTtc != round((float) $this->order->total_paid_tax_incl, 2)) {
            $correction = round($this->order->total_paid_tax_incl - $orderTotalTtc, 2);
            if ($correction <= 0.02) {
                $this->order->total_products_wt += $correction;
            }
        }

        return true;
    }
}
