<?php

namespace common\logic\Order\Order\Service;

use common\helpers\GlobalParam;
use common\helpers\MeanPayment;
use common\helpers\Price;
use common\logic\AbstractBuilder;
use common\logic\Config\TaxRate\Model\TaxRate;
use common\logic\Distribution\Distribution\Model\Distribution;
use common\logic\Distribution\Distribution\Repository\DistributionRepository;
use common\logic\Document\Document\Model\Document;
use common\logic\Order\Order\Model\Order;
use common\logic\Order\Order\Repository\OrderRepository;
use common\logic\Order\OrderStatusHistory\Service\OrderStatusHistoryBuilder;
use common\logic\Order\ProductOrder\Model\ProductOrder;
use common\logic\Order\ProductOrder\Service\ProductOrderBuilder;
use common\logic\Order\ProductOrder\Service\ProductOrderSolver;
use common\logic\PointSale\PointSale\Model\PointSale;
use common\logic\PointSale\PointSale\Service\PointSaleBuilder;
use common\logic\PointSale\PointSale\Repository\PointSaleRepository;
use common\logic\PointSale\UserPointSale\Repository\UserPointSaleRepository;
use common\logic\Producer\Producer\Model\Producer;
use common\logic\Producer\Producer\Repository\ProducerRepository;
use common\logic\Product\Product\Service\ProductSolver;
use common\logic\Subscription\Subscription\Model\Subscription;
use common\logic\Subscription\Subscription\Service\SubscriptionBuilder;
use common\logic\Subscription\Subscription\Repository\SubscriptionRepository;
use common\logic\Subscription\Subscription\Service\SubscriptionSolver;
use common\logic\User\CreditHistory\Model\CreditHistory;
use common\logic\User\CreditHistory\Service\CreditHistoryBuilder;
use common\logic\User\CreditHistory\Repository\CreditHistoryRepository;
use common\logic\User\User\Service\UserSolver;
use common\logic\User\UserProducer\Repository\UserProducerRepository;
use yii\web\NotFoundHttpException;

class OrderBuilder extends AbstractBuilder
{
    protected UserSolver $userSolver;
    protected OrderSolver $orderSolver;
    protected CreditHistoryRepository $creditHistoryRepository;
    protected ProducerRepository $producerRepository;
    protected CreditHistoryBuilder $creditHistoryBuilder;
    protected ProductOrderBuilder $productOrderBuilder;
    protected OrderStatusHistoryBuilder $orderStatusHistoryBuilder;
    protected OrderRepository $orderRepository;
    protected DistributionRepository $distributionRepository;
    protected SubscriptionBuilder $subscriptionBuilder;
    protected PointSaleRepository $pointSaleRepository;
    protected UserProducerRepository $userProducerRepository;
    protected UserPointSaleRepository $userPointSaleRepository;
    protected PointSaleBuilder $pointSaleBuilder;
    protected ProductOrderSolver $productOrderSolver;
    protected SubscriptionRepository $subscriptionRepository;
    protected SubscriptionSolver $subscriptionSolver;
    protected ProductSolver $productSolver;

    public function loadDependencies(): void
    {
        $this->userSolver = $this->loadService(UserSolver::class);
        $this->orderSolver = $this->loadService(OrderSolver::class);
        $this->creditHistoryRepository = $this->loadService(CreditHistoryRepository::class);
        $this->producerRepository = $this->loadService(ProducerRepository::class);
        $this->creditHistoryBuilder = $this->loadService(CreditHistoryBuilder::class);
        $this->productOrderBuilder = $this->loadService(ProductOrderBuilder::class);
        $this->orderStatusHistoryBuilder = $this->loadService(OrderStatusHistoryBuilder::class);
        $this->orderRepository = $this->loadService(OrderRepository::class);
        $this->distributionRepository = $this->loadService(DistributionRepository::class);
        $this->subscriptionBuilder = $this->loadService(SubscriptionBuilder::class);
        $this->pointSaleRepository = $this->loadService(PointSaleRepository::class);
        $this->userProducerRepository = $this->loadService(UserProducerRepository::class);
        $this->userPointSaleRepository = $this->loadService(UserPointSaleRepository::class);
        $this->pointSaleBuilder = $this->loadService(PointSaleBuilder::class);
        $this->productOrderSolver = $this->loadService(ProductOrderSolver::class);
        $this->subscriptionRepository = $this->loadService(SubscriptionRepository::class);
        $this->subscriptionSolver = $this->loadService(SubscriptionSolver::class);
        $this->productSolver = $this->loadService(ProductSolver::class);
    }

    public function instanciateOrder(Distribution $distribution): Order
    {
        $order = new Order();
        $order->populateDistribution($distribution);
        $order->date = date('Y-m-d H:i:s');
        $order->status = 'tmp-order';
        $order->id_user = 0;

        return $order;
    }

    public function createOrder(Distribution $distribution): Order
    {
        $order = $this->instanciateOrder($distribution);

        $this->createUserPointSale($order);
        $this->initOrderCommentPointSale($order);
        $this->generateOrderReference($order);

        $order->save();

        return $order;
    }

    /**
     * Ajoute l'abonnement' pour une date donnée.
     */
    // add
    public function createOrderFromSubscription(Subscription $subscription, string $date, bool $force = false): ?Order
    {
        $now = date('Y-m-d');
        $distributionDate = date('Y-m-d', strtotime($date));
        $distribution = $this->distributionRepository->findOneDistributionByDate($distributionDate);

        if ($distribution
            && $distribution->active
            && ($distributionDate > $now || $force)
            && count($subscription->productSubscription)
            && $subscription->pointSale) {

            $order = $this->createOrder($distribution);
            $this->updateOrderFromSubscription($order, $subscription);

            return $order;
        }

        return null;
    }

    /**
     * Ajoute les commandes pour une date donnée à partir des abonnements.
     */
    // addAll
    // createAllFromSubscriptions
    public function createAllOrdersFromSubscriptions(string $date, bool $force = false): array
    {
        $orderArray = [];
        $distribution = $this->distributionRepository->findOneDistributionByDate(date('Y-m-d', strtotime($date)));

        if ($distribution) {
            $orderArray = $this->orderRepository->findOrdersByDistribution($distribution);
            $subscriptionArray = $this->subscriptionRepository->findSubscriptionsByDate($date);
            foreach ($subscriptionArray as $subscription) {
                if (!$this->subscriptionSolver->hasOrderAlreadyExist($subscription, $orderArray)) {
                    $this->createOrderFromSubscription($subscription, $date, $force);
                }
            }
        }

        return $orderArray;
    }

    // updateIncomingDistributions
    public function updateOrdersIncomingDistributionsFromSubscription(Subscription $subscription, $update = false): array
    {
        $orderArray = [];
        $matchedDistributionsArray = $this->distributionRepository->findDistributionsIncomingMatchWithSubscrtiption($subscription);

        if ($update) {
            $this->deleteOrdersIncomingDistributionsFromSubscription($subscription);
        }

        if (count($matchedDistributionsArray)) {
            foreach ($matchedDistributionsArray as $distribution) {
                $orderArray[] = $this->createOrderFromSubscription($subscription, $distribution->date);
            }
        }

        return $orderArray;
    }

    public function deleteOrdersIncomingDistributionsFromSubscription(Subscription $subscription, bool $deleteAfterDateEnd = false): int
    {
        $dateStart = $subscription->date_begin;
        $comparatorDateStart = '>=';
        if ($deleteAfterDateEnd) {
            $dateStart = $subscription->date_end;
            $comparatorDateStart = '>';
        }

        $params = [
            ':id_producer' => GlobalParam::getCurrentProducerId(),
            ':date_today' => date('Y-m-d'),
            ':date_start' => $dateStart,
            ':id_subscription' => $subscription->id
        ];

        $orderDeadline = $this->producerRepository->getConfig('order_deadline');
        $hour = date('G');

        if ($hour >= $orderDeadline) {
            $conditionDistributionDate = 'distribution.date > :date_today';
        } else {
            $conditionDistributionDate = 'distribution.date >= :date_today';
        }

        $orders = Order::find()
            ->joinWith('distribution')
            ->where('distribution.id_producer = :id_producer')
            ->andWhere($conditionDistributionDate)
            ->andWhere('distribution.date ' . $comparatorDateStart . ' :date_start')
            ->andWhere('order.id_subscription = :id_subscription');

        $orders->params($params);

        $ordersArray = $orders->all();
        $configCredit = $this->producerRepository->getConfig('credit');

        $countOrdersDeleted = 0;
        if ($ordersArray && count($ordersArray)) {
            foreach ($ordersArray as $order) {

                $theOrder = $this->orderRepository->findOneOrderById($order->id);
                $this->initOrder($theOrder);

                // remboursement de la commande
                if ($theOrder->id_user && $this->orderSolver->getOrderAmount($theOrder, Order::AMOUNT_PAID) && $configCredit) {

                    $this->creditHistoryBuilder->createCreditHistory(
                        CreditHistory::TYPE_REFUND,
                        $this->orderSolver->getOrderAmount($theOrder, Order::AMOUNT_PAID),
                        $theOrder->distribution->producer,
                        $theOrder->user,
                        GlobalParam::getCurrentUser()
                    );
                }

                $this->deleteOrder($order, true);

                $countOrdersDeleted++;
            }
        }

        return $countOrdersDeleted;
    }

    public function addProductOrdersFromSubscription(Order $order, Subscription $subscription): bool
    {
        $productsAdd = false;
        $user = $subscription->user ?? null;
        foreach ($subscription->productSubscription as $productSubscription) {

            $this->productOrderBuilder->createProductOrder(
                $order,
                $productSubscription->product,
                $productSubscription->quantity,
                $this->productSolver->getPrice($productSubscription->product, [
                    'user' => $user,
                    'point_sale' => $subscription->pointSale,
                    'quantity' => $productSubscription->quantity
                ])
            );
            $productsAdd = true;
        }

        if (!$productsAdd) {
            $this->deleteOrder($order);
        }

        return $productsAdd;
    }

    // updateFromSubscription
    public function updateOrderFromSubscription(Order $order, Subscription $subscription): void
    {
        $this->initOrderBaseFromSubscription($order, $subscription);
        $this->initOrderAutoPaymentFromSubscription($order, $subscription);
        $this->addProductOrdersFromSubscription($order, $subscription);

        $this->saveUpdate($order);
    }

    public function initOrderBaseFromSubscription(Order $order, Subscription $subscription): void
    {
        $order->origin = Order::ORIGIN_AUTO;
        $order->populatePointSale($subscription->pointSale);
        $order->populateSubscription($subscription);

        if (strlen($subscription->comment)) {
            $order->comment = $subscription->comment;
        }

        if (strlen($subscription->username)) {
            $order->username = $subscription->username;
            $order->id_user = 0;
        } else {
            $order->populateUser($subscription->user);
        }
    }

    public function initOrderAutoPaymentFromSubscription(Order $order, Subscription $subscription): void
    {
        $pointSale = $subscription->pointSale;
        if ($pointSale) {
            $creditFunctioning = $this->producerRepository->getPointSaleCreditFunctioning($pointSale);

            $order->auto_payment = 0;
            if ($subscription->auto_payment == Subscription::AUTO_PAYMENT_DEDUCTED) {
                if ($order->id_user && $this->producerRepository->getConfig('credit') && $pointSale->credit) {
                    if ($creditFunctioning == Producer::CREDIT_FUNCTIONING_OPTIONAL) {
                        $order->auto_payment = 0;
                    } elseif ($creditFunctioning == Producer::CREDIT_FUNCTIONING_MANDATORY) {
                        $order->auto_payment = 1;
                    } elseif ($creditFunctioning == Producer::CREDIT_FUNCTIONING_USER) {

                        $userProducer = $this->userProducerRepository->findOneUserProducer($order->user, $subscription->producer);

                        if ($userProducer) {
                            $order->auto_payment = $userProducer->credit_active;
                        }
                    }
                }
            } elseif ($subscription->auto_payment == Subscription::AUTO_PAYMENT_YES) {
                $order->auto_payment = 1;
            } elseif ($subscription->auto_payment == Subscription::AUTO_PAYMENT_NO) {
                $order->auto_payment = 0;
            }
        }

        $order->tiller_synchronization = $order->auto_payment;
    }

    // addUserPointSale
    public function createUserPointSale(Order $order): void
    {
        if ($order->user) {
            $this->pointSaleBuilder->addUser($order->user, $order->pointSale);
        }
    }

    // initCommentPointSale
    public function initOrderCommentPointSale(Order $order): void
    {
        if ($order->user && $order->pointSale) {
            $userPointSale = $this->userPointSaleRepository->findOneUserPointSale($order->user, $order->pointSale);

            if ($userPointSale && strlen($userPointSale->comment)) {
                $order->comment_point_sale = $userPointSale->comment;
            }
        }
    }

    /**
     * Initialise le montant total, le montant déjà payé et le poids de la commande.
     */
    // init
    public function initOrder(Order $order, string $taxCalculationMethod = Document::TAX_CALCULATION_METHOD_DEFAULT): void
    {
        $this->initOrderAmount($order, $taxCalculationMethod);
        $this->initOrderPaidAmount($order);
    }

    /**
     * Initialise le montant de la commande.
     */
    // initAmount
    public function initOrderAmount(Order $order, string $taxCalculationMethod = Document::TAX_CALCULATION_METHOD_DEFAULT): void
    {
        $order->amount = 0;
        $order->amount_with_tax = 0;
        $order->amount_vat = [];
        $order->invoice_amount = 0;
        $order->invoice_amount_with_tax = 0;
        $order->invoice_amount_vat = [];
        $order->weight = 0;

        if (isset($order->productOrder)) {
            foreach ($order->productOrder as $productOrder) {
                $this->addProductOrderAmount($order, Order::AMOUNT_TOTAL, $productOrder, $taxCalculationMethod);
                $this->addProductOrderAmount($order, Order::INVOICE_AMOUNT_TOTAL, $productOrder, $taxCalculationMethod);
                $this->addProductOrderWeight($order, $productOrder);
            }
        }
    }

    public function addProductOrderWeight(Order $order, ProductOrder $productOrder): void
    {
        if ($productOrder->unit == 'piece') {
            if (isset($productOrder->product)) {
                $order->weight += ($productOrder->quantity * $productOrder->product->weight) / 1000;
            }
        } else {
            $order->weight += $productOrder->quantity;
        }
    }

    public function addProductOrderAmount(Order $order, string $typeTotal, ProductOrder $productOrder, string $taxCalculationMethod): void
    {
        $fieldNameAmount = $this->orderSolver->getFieldNameAmount($typeTotal);
        $fieldNameAmountWithTax = $this->orderSolver->getFieldNameAmount($typeTotal, 'with_tax');
        $price = $this->productOrderSolver->getPriceByTypeTotal($productOrder, $typeTotal);
        $order->$fieldNameAmount += $price * $productOrder->quantity;
        $order->$fieldNameAmountWithTax += Price::getPriceWithTax(
                $price,
                $productOrder->taxRate->value,
                $taxCalculationMethod
            ) * $productOrder->quantity;
        $this->addProductOrderVat($order, $typeTotal, $price * $productOrder->quantity, $productOrder->taxRate, $taxCalculationMethod);
    }

    // addVat
    public function addProductOrderVat(
        Order   $order,
        string  $typeTotal,
        float   $priceTotalWithoutTax,
        TaxRate $taxRate,
        string  $taxCalculationMethod): void
    {
        $fieldName = $this->orderSolver->getFieldNameAmount($typeTotal, 'vat');

        if (!isset($order->$fieldName[$taxRate->id])) {
            $order->$fieldName[$taxRate->id] = 0;
        }

        $order->$fieldName[$taxRate->id] += Price::getVat($priceTotalWithoutTax, $taxRate->value, $taxCalculationMethod);
    }

    /**
     * Initialise le montant payé de la commande et le retourne.
     */
    public function initOrderPaidAmount(Order $order): void
    {
        // Attention : $order->creditHistory pas à jour si un paiement vient d'avoir lieu
        /*if (isset($order->creditHistory)) {
            $history = $order->creditHistory;
        } else {
            $history = $this->creditHistoryRepository->getByOrder($order);
        }*/

        $history = $this->creditHistoryRepository->getByOrder($order);

        $order->paid_amount = 0;

        if (count($history)) {
            foreach ($history as $ch) {
                if ($ch->type == CreditHistory::TYPE_PAYMENT) {
                    $order->paid_amount += $ch->amount;
                } elseif ($ch->type == CreditHistory::TYPE_REFUND) {
                    $order->paid_amount -= $ch->amount;
                }
            }
        }
    }

    // delete
    public function deleteOrder(Order $order, bool $force = false): bool
    {
        $this->initOrder($order);

        // remboursement si l'utilisateur a payé pour cette commande
        $amountPaid = $this->orderSolver->getOrderAmount($order, Order::AMOUNT_PAID);

        if ($amountPaid >= 0.01) {
            $this->creditHistoryBuilder->createCreditHistory(
                CreditHistory::TYPE_REFUND,
                $amountPaid,
                GlobalParam::getCurrentProducer(),
                $order->user,
                $this->userSolver->getCurrent(),
                MeanPayment::CREDIT,
                $order
            );
        }

        // delete
        if ($this->producerRepository->getConfig('option_behavior_cancel_order') == Producer::BEHAVIOR_DELETE_ORDER_DELETE ||
            ($this->producerRepository->getConfig('option_behavior_cancel_order') == Producer::BEHAVIOR_DELETE_ORDER_STATUS && strlen($order->date_delete))
            || $force) {

            $this->productOrderBuilder->deleteProductOrdersByOrder($order);

            return $this->delete($order);
        } // status 'delete'
        elseif ($this->producerRepository->getConfig('option_behavior_cancel_order') == Producer::BEHAVIOR_DELETE_ORDER_STATUS) {
            $order->date_delete = date('Y-m-d H:i:s');

            return $this->saveUpdate($order);
        }

        return false;
    }

    /**
     * Ajuste le crédit pour que la commande soit payée.
     */
    public function processCredit(Order $order): void
    {
        if ($order->id_user) {
            $paymentStatus = $this->orderSolver->getPaymentStatus($order);

            if ($paymentStatus == Order::PAYMENT_PAID) {
                return;
            } elseif ($paymentStatus == Order::PAYMENT_SURPLUS) {
                $type = CreditHistory::TYPE_REFUND;
                $amount = $this->orderSolver->getOrderAmount($order, Order::AMOUNT_SURPLUS);
            } elseif ($paymentStatus == Order::PAYMENT_UNPAID) {
                $type = CreditHistory::TYPE_PAYMENT;
                $amount = $this->orderSolver->getOrderAmount($order, Order::AMOUNT_REMAINING);
            }

            $this->creditHistoryBuilder->createCreditHistory(
                $type,
                $amount,
                GlobalParam::getCurrentProducer(),
                $order->user,
                $this->userSolver->getCurrent(),
                MeanPayment::CREDIT,
                $order
            );
        }
    }

    // setTillerSynchronization
    // updateTillerSynchronization
    public function updateOrderTillerSynchronization(Order $order, int $synchroTiller = null): void
    {
        if (!is_null($synchroTiller)) {
            $order->tiller_synchronization = $synchroTiller;
        } else {
            $amountPaid = $this->orderSolver->getOrderAmount($order, Order::AMOUNT_PAID);

            if ($amountPaid >= 0.01) {
                $order->tiller_synchronization = 1;
            } else {
                $order->tiller_synchronization = 0;
            }
        }

        $this->saveUpdate($order);
    }

    /**
     * Changement de statut d'une commande.
     */
    // changeOrderStatus
    public function updateOrderStatus(Order $order, string $newStatus, string $origin): void
    {
        $orderStatusArray = GlobalParam::get('orderStatus');
        $userCurrent = $this->userSolver->getCurrent();

        switch ($newStatus) {
            case 'new-order' :
                //$this->orderStatusHistoryBuilder->createOrderStatusHistory($order, $userCurrent, $newStatus, $origin);
                $order->status = $newStatus;
                $order->save();
                break;
            case 'waiting-paiement-on-delivery':
            case 'waiting-paiement-by-credit':
            case 'paid-by-credit':
            case 'waiting-delevery' :
            case 'delivered':
            case 'refunded':
            case 'cancel':
                if (in_array($newStatus, $orderStatusArray[$order->status]['nextStatusAllow'])) {
                    //$this->orderStatusHistoryBuilder->createOrderStatusHistory($order, $userCurrent, $newStatus, $origin);
                    $order->status = $newStatus;
                    $order->save();
                }
                break;
            default:
                throw new NotFoundHttpException('Statut de commande inconnu.');
        }
    }

    // initReference
    // generateReference
    public function generateOrderReference(Order $order): void
    {
        $distribution = $this->distributionRepository->findOneDistributionById($order->id_distribution);
        $producer = $this->producerRepository->findOneProducerById($distribution->id_producer);

        if (!$order->reference && $producer->option_order_reference_type == Producer::ORDER_REFERENCE_TYPE_YEARLY) {
            $lastOrder = $this->orderRepository->findOneOrderLastOfYear($producer);

            if ($lastOrder && $lastOrder->reference && strlen($lastOrder->reference) > 0) {
                $pattern = '#A([0-9]+)C([0-9]+)#';
                preg_match($pattern, $lastOrder->reference, $matches, PREG_OFFSET_CAPTURE);
                $sizeNumReference = strlen($matches[2][0]);
                $numReference = ((int)$matches[2][0]) + 1;
                $numReference = str_pad($numReference, $sizeNumReference, '0', STR_PAD_LEFT);

                $order->reference = 'A' . $matches[1][0] . 'C' . $numReference;
            } else {
                $order->reference = 'A' . date('y') . 'C0001';
            }

            $this->saveUpdate($order);
        }
    }

    // initInvoicePrices
    // updateInvoicePrices
    public function updateOrderInvoicePrices(Order $order, array $params = []): void
    {
        foreach ($order->productOrder as $productOrder) {
            if ($productOrder->product) {
                $this->productOrderBuilder->updateProductOrderInvoicePrice($productOrder, $params);
            }
        }
    }

    /**
     * Initialise les commandes liées au point de vente.
     */
    public function initPointSaleOrders(PointSale $pointSale, array $ordersArray): void
    {
        $pointSale->orders = [];
        $pointSale->revenues = 0;
        $pointSale->revenues_with_tax = 0;

        if ($ordersArray) {
            foreach ($ordersArray as $order) {
                $this->initOrder($order);

                if ($pointSale->id == $order->id_point_sale) {
                    $pointSale->orders[] = $order;

                    if (is_null($order->date_delete)) {
                        $pointSale->revenues += (float)$order->amount;
                        $pointSale->revenues_with_tax += (float)$order->amount_with_tax;
                    }
                }
            }
        }
    }

}