<?php

namespace domain\Order\Order;

use common\helpers\Price;
use domain\Document\Document\Document;
use domain\Document\Document\DocumentSolver;
use domain\Order\OrderStatus\OrderStatus;
use domain\Order\OrderStatus\OrderStatusSolver;
use domain\Order\OrderStatusHistory\OrderStatusHistory;
use domain\Order\OrderStatusHistory\OrderStatusHistorySolver;
use domain\Order\ProductOrder\ProductOrderSolver;
use domain\Payment\Payment;
use domain\Payment\PaymentSolver;
use domain\Producer\Producer\Producer;
use domain\Producer\Producer\ProducerSolver;
use domain\Product\Product\Product;
use domain\User\User\User;
use domain\User\User\UserSolver;
use domain\_\AbstractService;
use domain\_\SolverInterface;
use yii\helpers\Html;

class OrderSolver extends AbstractService implements SolverInterface
{
    protected UserSolver $userSolver;
    protected DocumentSolver $documentSolver;
    protected PaymentSolver $paymentSolver;
    protected ProducerSolver $producerSolver;
    protected ProductOrderSolver $productOrderSolver;
    protected OrderStatusHistorySolver $orderStatusHistorySolver;
    protected OrderStatusSolver $orderStatusSolver;

    public function loadDependencies(): void
    {
        $this->documentSolver = $this->loadService(DocumentSolver::class);
        $this->userSolver = $this->loadService(UserSolver::class);
        $this->paymentSolver = $this->loadService(PaymentSolver::class);
        $this->producerSolver = $this->loadService(ProducerSolver::class);
        $this->productOrderSolver = $this->loadService(ProductOrderSolver::class);
        $this->orderStatusHistorySolver = $this->loadService(OrderStatusHistorySolver::class);
        $this->orderStatusSolver = $this->loadService(OrderStatusSolver::class);
    }

    public function getFieldNameAmount($typeTotal = Order::AMOUNT_TOTAL, string $typeField = ''): string
    {
        $fieldName = 'amount';
        if ($typeTotal == Order::INVOICE_AMOUNT_TOTAL) {
            $fieldName = 'invoice_amount';
        }

        if ($typeField == 'vat') {
            $fieldName = $fieldName . '_vat';
        } elseif ($typeField == 'with_tax') {
            $fieldName = $fieldName . '_with_tax';
        }

        return $fieldName;
    }

    public function getOrderTotalVat(Order $order, string $typeTotal = Order::AMOUNT_TOTAL): float
    {
        $fieldName = $this->getFieldNameAmount($typeTotal, 'vat');
        $totalVat = 0;

        foreach ($order->$fieldName as $vat) {
            $totalVat += $vat;
        }

        if($this->getProducerContext()->option_tax_calculation_method == Document::TAX_CALCULATION_METHOD_ROUNDING_OF_THE_SUM) {
            $totalVat = Price::round($totalVat);
        }

        return $totalVat;
    }

    /**
     * Retourne le statut de paiement de la commande (payée, surplus, ou impayée).
     */
    public function getPaymentStatus(Order $order): ?string
    {
        // payé
        if ($this->getOrderAmountWithtax($order) - $this->getOrderAmount($order, Order::AMOUNT_PAID) < 0.01 &&
            $this->getOrderAmountWithtax($order) - $this->getOrderAmount($order, Order::AMOUNT_PAID) > -0.01) {
            return Order::PAYMENT_PAID;
        } // à rembourser
        elseif ($this->getOrderAmountWithtax($order) - $this->getOrderAmount($order, Order::AMOUNT_PAID) <= -0.01) {
            return Order::PAYMENT_SURPLUS;
        } // reste à payer
        elseif ($this->getOrderAmountWithtax($order) - $this->getOrderAmount($order, Order::AMOUNT_PAID) >= 0.01) {
            return Order::PAYMENT_UNPAID;
        }

        return null;
    }

    /**
     * Retourne le résumé du point de vente lié à la commande au format HTML.
     */
    public function getPointSaleSummary(Order $order): string
    {
        $html = '';

        if (isset($order->pointSale)) {
            $html .= '<span class="name-point-sale">' .
                Html::encode($order->pointSale->name) .
                '</span>' .
                '<br /><span class="locality">'
                . Html::encode($order->pointSale->locality)
                . '</span>';

            if (strlen($order->comment_point_sale)) {
                $html .= '<div class="comment"><span>'
                    . Html::encode($order->comment_point_sale)
                    . '</span></div>';
            }
        } else {
            $html .= 'Point de vente supprimé';
        }

        return $html;
    }

    /**
     * Retourne une chaine de caractère décrivant l'utilisateur lié à la commande.
     */
    public function getOrderUsername(Order $order): string
    {
        if (isset($order->user)) {
            return Html::encode($this->userSolver->getUsername($order->user));
        } elseif (strlen($order->username)) {
            return Html::encode($order->username);
        } else {
            return 'Client introuvable';
        }
    }

    public function isLinkedToValidDocument(Order $order): bool
    {
        return ($this->isLinkedToValidDeliveryNote($order)
            || $this->isLinkedToValidQuotation($order)
            || $this->isLinkedToValidInvoice($order));
    }

    public function isLinkedToValidDeliveryNote(Order $order): bool
    {
        return $order->deliveryNote && $this->documentSolver->isStatusValid($order->deliveryNote);
    }

    public function isLinkedToValidQuotation(Order $order): bool
    {
        return $order->quotation && $this->documentSolver->isStatusValid($order->quotation);
    }

    public function isLinkedToValidInvoice(Order $order): bool
    {
        return $order->invoice && $this->documentSolver->isStatusValid($order->invoice);
    }

    public function getCommentReport(Order $order): string
    {
        $comment = '';
        $hasComment = false;

        if ($order->comment && strlen($order->comment) > 0) {
            $hasComment = true;
            $comment .= $order->comment;
        }

        if ($order->delivery_home && $order->delivery_address && strlen($order->delivery_address) > 0) {
            if ($hasComment) {
                $comment .= '<br /><br />';
            }

            $comment .= '<strong>Adresse :</strong><br />';
            $comment .= nl2br($order->delivery_address);
        }

        return $comment;
    }

    /**
     * Retourne un bloc html présentant une date.
     */
    public function getDateAsHtml(Order $order): string
    {
        return '<div class="block-date">
                    <div class="day">' . strftime('%A', strtotime($order->distribution->date)) . '</div>
                    <div class="num">' . date('d', strtotime($order->distribution->date)) . '</div>
                    <div class="month">' . strftime('%B', strtotime($order->distribution->date)) . '</div>
                </div>';
    }

    /**
     * Retourne le nombre de produits commandés.
     */
    public function countProducts(Order $order): int
    {
        if ($order->productOrder && is_array($order->productOrder)) {
            return count($order->productOrder);
        }

        return 0;
    }

    public function getProductQuantityPiecesByOrders(Product $product, array $ordersArray): float
    {
        $quantity = 0;

        if (count($ordersArray)) {
            foreach ($ordersArray as $order) {
                if($this->isOrderStatusValid($order)) {
                    foreach ($order->productOrder as $productOrder) {
                        if ($productOrder->id_product == $product->id) {
                            $quantity += $this->productOrderSolver->getQuantityPieces($productOrder);
                        }
                    }
                }
            }
        }

        return $quantity;
    }

    /**
     * Retourne la quantité d'un produit donné de plusieurs commandes.
     */
    public function getProductQuantity(Product $product, array $ordersArray, bool $ignoreCancel = false, string $unit = null): float
    {
        $quantity = 0;

        if (count($ordersArray)) {
            foreach ($ordersArray as $order) {
                if ($this->isOrderStatusValid($order) || $ignoreCancel) {
                    foreach ($order->productOrder as $productOrder) {
                        if ($productOrder->id_product == $product->id &&
                            ((is_null($unit) && $productOrder->product->unit == $productOrder->unit)
                                || (!is_null($unit) && strlen($unit) && $productOrder->unit == $unit))) {
                            $quantity += $productOrder->quantity;
                        }
                    }
                }
            }
        }

        return $quantity;
    }

    /**
     * Retourne une classe identifiant l'historique de la commande (ajoutée,
     * modifiée, supprimée).
     */
    // getClassHistory
    public function getHistoryClass(Order $order): string
    {
        if($this->isOrderStatusCanceled($order)) {
            return 'commande-delete';
        }

        if (!is_null($order->date_update)) {
            return 'commande-update';
        }

        return 'commande-create';
    }

    public function getPaymentLabelAmountRemainingSurplus(Order $order): string
    {
        $amountPaid = $this->getOrderAmountPaid($order);

        if($amountPaid) {
            $amountRemaining = $this->getOrderAmountWithTax($order, Order::AMOUNT_REMAINING);
            if($amountRemaining > 0) {
                return $this->getHtmlPaymentLabelAmountRemainingSurplus('Reste <strong>'.Price::format($amountRemaining).'</strong> à payer');
            }

            $amountSurplus = $this->getOrderAmountWithTax($order, Order::AMOUNT_SURPLUS);
            if($amountSurplus > 0) {
                return $this->getHtmlPaymentLabelAmountRemainingSurplus('Surplus : <strong>'.Price::format($amountSurplus).'</strong> à rembourser');
            }
        }

        return '';
    }

    private function getHtmlPaymentLabelAmountRemainingSurplus(string $text): string
    {
        return '<br><span class="payment-detail-remaining-surplus">'.$text.'</span>';
    }

    public function getLabelOrderStatus(Order $order, bool $withDateLastOrderStatus = false): string
    {
        $orderStatusCssClass = $this->getOrderStatusCssClass($order);
        $orderStatusLabel = $this->getOrderStatusLabel($order);
        $orderStatusHistorySummaryTitleTag = $this->getOrderStatusHistorySummaryTitleTag($order);
        $html = '<span class="label label-'.$orderStatusCssClass.'" title="'.$orderStatusHistorySummaryTitleTag.'">'.$orderStatusLabel.'</span>';

        $lastOrderStatusHistory = $this->getLastOrderStatusHistory($order);
        if($withDateLastOrderStatus && $lastOrderStatusHistory) {
            $html .= '<br /><span class="small gray">'.$lastOrderStatusHistory->getDate()->format('d/m/Y à H:i').'</span>';
        }

        return $html;
    }

    /**
     * Retourne l'origine de la commande (client, automatique ou admin) sous forme texte ou HTML.
     */
    public function getLabelOrigin(Order $order, bool $withLabel = false): string
    {
        $classLabel = '';
        $str = '';

        if ($order->origin == Order::ORIGIN_USER) {
            $classLabel = 'success';
            $str = $this->userSolver->isTypeGuest($order->user) ? 'Visiteur' : 'Utilisateur';
        } elseif ($order->origin == Order::ORIGIN_AUTO) {
            $classLabel = 'default';
            $str = 'Abonnement';
        } elseif ($order->origin == Order::ORIGIN_ADMIN) {
            $classLabel = 'warning';
            $str = 'Producteur';
        }

        if ($withLabel) {
            return '<span class="label label-' . $classLabel . ' outline">' . $str . '</span>';
        } else {
            return $str;
        }
    }

    public function getOrderAmountPaidByCredit(Order $order): float
    {
        $amount = 0;

        foreach($order->payment as $payment) {
            if($this->paymentSolver->isMeanPaymentCredit($payment)) {
                $amount = $this->paymentSolver->sumAmountPaid($payment, $amount);
            }
        }

        return $amount;
    }

    public function getOrderAmountPaid(Order $order): float
    {
        $amount = 0;

        foreach($order->payment as $payment) {
            $amount = $this->paymentSolver->sumAmountPaid($payment, $amount);
        }

        return $amount;
    }

    /**
     * Retourne le montant de la commande (total, payé, restant, ou en surplus).
     */
    public function getOrderAmount(Order $order, string $type = Order::AMOUNT_TOTAL, bool $format = false)
    {
        $amount = $order->amount;
        if ($type == Order::INVOICE_AMOUNT_TOTAL && $order->invoice_amount) {
            $amount = $order->invoice_amount;
        }

        return $this->_getAmountGeneric($order, $type, $amount, $format);
    }

    public function getOrderAmountWithTax(Order $order, string $type = Order::AMOUNT_TOTAL, bool $format = false)
    {
        $amount = $order->amount + $this->getOrderTotalVat($order, $type);
        if ($type == Order::INVOICE_AMOUNT_TOTAL && $order->invoice_amount) {
            $amount = $order->invoice_amount + $this->getOrderTotalVat($order, $type);
        }

        $amount = round($amount, 2);

        return $this->_getAmountGeneric($order, $type, $amount, $format);
    }

    protected function _getAmountGeneric(Order $order, string $type, float $amountOrder, bool $format)
    {
        switch ($type) {
            case Order::AMOUNT_TOTAL :
            case Order::INVOICE_AMOUNT_TOTAL :
                $amount = $amountOrder;
                break;
            case Order::AMOUNT_PAID :
                $amount = $order->paid_amount;
                break;
            case Order::AMOUNT_REMAINING :
                $amount = $this->getOrderAmountWithTax($order, Order::AMOUNT_TOTAL)
                    - $this->getOrderAmountWithTax($order,Order::AMOUNT_PAID);
                break;
            case Order::AMOUNT_SURPLUS :
                $amount = $this->getOrderAmountWithTax($order,Order::AMOUNT_PAID)
                    - $this->getOrderAmountWithTax($order,Order::AMOUNT_TOTAL);
                break;
        }

        if ($format) {
            return Price::format($amount);
        } else {
            return $amount;
        }
    }

    public function isOrderBelongsToUser(Order $order, User $user = null): bool
    {
        if($user) {
            return $order->id_user == $user->id;
        }

        return false;
    }

    public function isOrderFromProducer(Order $order = null): bool
    {
        return $order && $order->distribution->id_producer == $this->getProducerContextId();
    }

    /**
     * Retourne les informations relatives à la commande au format JSON.
     */
    public function getDataJson(Order $order): string
    {
        $jsonOrder = [];
        if ($order) {
            $jsonOrder = [
                'products' => [],
                'amount' => $order->amount,
                'str_amount' => $this->getOrderAmountWithTax($order, Order::AMOUNT_TOTAL, true),
                'paid_amount' => $this->getOrderAmount($order, Order::AMOUNT_PAID),
                'comment' => $order->comment,
            ];

            foreach ($order->productOrder as $productOrder) {
                $jsonOrder['products'][$productOrder->id_product] = $productOrder->quantity;
            }
        }

        return json_encode($jsonOrder);
    }

    /**
     * Retourne les informations à ajouter au commentaire du CreditHistorique
     * (libellé, montant, client, action) au format HTML.
     *
     */
    public function getPaymentComment(Payment $payment): string
    {
        $str = '';
        if (strlen($payment->getComment())) {
            $str .= '<br />';
        }

        $str .= $this->paymentSolver->getStrWording($payment);

        $order = $payment->getOrderObject();
        if ($order) {
            $str .= '<br />Montant de la commande : ' . $this->getOrderAmountWithTax($order, Order::AMOUNT_TOTAL, true);
        }

        $user = $payment->getUserObject();
        if ($user) {
            $str .= '<br />Client : ' . Html::encode($user->getName() . ' ' . $user->getLastname());
        }

        $userAction = $payment->getUserActionObject();
        if ($userAction) {
            $str .= '<br />Action : ' . Html::encode($userAction->getName() . ' ' . $userAction->getLastname());
        }

        return $str;
    }

    public function getUserForInvoicing(Order $order): ?User
    {
        if($order->invoice && $order->invoice->user) {
            return $order->invoice->user;
        }

        if($order->deliveryNote && $order->deliveryNote->user) {
            return $order->deliveryNote->user;
        }

        if($order->user) {
            return $order->user;
        }

        return null;
    }

    public function hasPhone(Order $order): bool
    {
        if($order->user && $order->user->phone) {
            return true;
        }

        return false;
    }

    public function getPhone(Order $order): string
    {
        if($this->hasPhone($order)) {
            return $order->user->phone;
        }

        return '';
    }

    public function getAmountTotalSpentByMeanPayment(Order $order, string $meanPayment)
    {
        $amount = 0;

        foreach($order->payment as $payment) {
            if($this->paymentSolver->isMeanPayment($payment, $meanPayment)) {
                if($this->paymentSolver->isTypeDebit($payment)) {
                    $amount += $payment->getAmount();
                }
                else {
                    $amount -= $payment->getAmount();
                }
            }
        }

        return $amount;
    }

    public function getOrderStatusHistoryByOrderStatusAlias(Order $order, string $orderStatusAlias): ?OrderStatusHistory
    {
        $orderStatusHistoryReturn = null;
        foreach($order->getOrderStatusHistory() as $orderStatusHistory) {
            if($orderStatusHistory->getOrderStatusAlias() == $orderStatusAlias) {
                $orderStatusHistoryReturn = $orderStatusHistory;
            }
        }

        return $orderStatusHistoryReturn;
    }

    public function isOrderStatusValid(Order $order): bool
    {
        return $this->isOrderStatusOrdered($order)
            || $this->isOrderStatusUpdated($order);
    }

    public function isOrderStatusCanceledOrDeleted(Order $order): bool
    {
        return $this->isOrderStatusCanceled($order)
            || $this->isOrderStatusDeleted($order);
    }

    public function isOrderStatusOrdered(Order $order): bool
    {
        return $order->getOrderStatusAlias() == OrderStatus::ALIAS_ORDERED;
    }

    public function isOrderStatusUpdated(Order $order): bool
    {
        return $order->getOrderStatusAlias() == OrderStatus::ALIAS_UPDATED;
    }

    public function isOrderStatusCanceled(Order $order): bool
    {
        return $order->getOrderStatusAlias() == OrderStatus::ALIAS_CANCELED;
    }

    public function isOrderStatusDeleted(Order $order): bool
    {
        return $order->getOrderStatusAlias() == OrderStatus::ALIAS_DELETED;
    }

    public function getLabelDeleteAction(Order $order): string
    {
        $optionBehaviorCancelOrder = $this->producerSolver->getConfig('option_behavior_cancel_order');

        if($optionBehaviorCancelOrder == Producer::BEHAVIOR_DELETE_ORDER_STATUS && $this->isOrderStatusValid($order)) {
            return 'Annuler';
        }

        return 'Supprimer';
    }

    public function getOrderStatusCssClass(Order $order): string
    {
        if($this->isOrderStatusOrdered($order)) {
            return 'success';
        }
        elseif($this->isOrderStatusUpdated($order)) {
            return 'warning';
        }
        elseif($this->isOrderStatusCanceled($order)) {
            return 'danger';
        }
        elseif($this->isOrderStatusDeleted($order)) {
            return 'danger';
        }

        return '';
    }

    public function getOrderStatusLabel(Order $order): string
    {
        return $this->orderStatusSolver->getLabel($order->getOrderStatusAlias());
    }

    public function getOrderStatusHistorySummaryTitleTag(Order $order, string $lineBreak = '&#013;'): string
    {
        $titleTagString = '';

        foreach($order->getOrderStatusHistory() as $orderStatusHistory) {
            $titleTagString .= $this->orderStatusHistorySolver->getOrderStatusHistoryLabel($orderStatusHistory).' '
                .date('\l\e d/m/Y à H:i', $orderStatusHistory->getDate()->getTimestamp()).' '
                .' par '.$this->userSolver->getUsername($orderStatusHistory->getUser()).' (#'.$orderStatusHistory->getUser()->getId().')'
                .$lineBreak;
        }

        return $titleTagString;
    }

    public function getLastOrderStatusHistory(Order $order): ?OrderStatusHistory
    {
        $lastOrderStatusHistory = null;

        foreach($order->getOrderStatusHistory() as $orderStatusHistory) {
            if(!$lastOrderStatusHistory || $orderStatusHistory->date > $lastOrderStatusHistory->date) {
                $lastOrderStatusHistory = $orderStatusHistory;
            }
        }

        return $lastOrderStatusHistory;
    }
}