<?php

namespace common\logic\Distribution\Distribution\Service;

use common\logic\AbstractBuilder;
use common\logic\Distribution\Distribution\Event\DistributionActiveEvent;
use common\logic\Distribution\Distribution\Model\Distribution;
use common\logic\Distribution\Distribution\Repository\DistributionRepository;
use common\logic\Distribution\PointSaleDistribution\Model\PointSaleDistribution;
use common\logic\Distribution\PointSaleDistribution\Repository\PointSaleDistributionRepository;
use common\logic\Distribution\PointSaleDistribution\Service\PointSaleDistributionBuilder;
use common\logic\Distribution\ProductDistribution\Model\ProductDistribution;
use common\logic\Distribution\ProductDistribution\Repository\ProductDistributionRepository;
use common\logic\Distribution\ProductDistribution\Service\ProductDistributionBuilder;
use common\logic\Order\Order\Repository\OrderRepository;
use common\logic\Order\ProductOrder\Service\ProductOrderBuilder;
use common\logic\PointSale\PointSale\Model\PointSale;
use common\logic\Product\Product\Model\Product;
use common\logic\Product\Product\Repository\ProductRepository;
use common\logic\User\UserProducer\Repository\UserProducerRepository;

class DistributionBuilder extends AbstractBuilder
{
    protected DistributionRepository $distributionRepository;
    protected DistributionSolver $distributionSolver;
    protected PointSaleDistributionRepository $pointSaleDistributionRepository;
    protected PointSaleDistributionBuilder $pointSaleDistributionBuilder;
    protected ProductRepository $productRepository;
    protected ProductDistributionRepository $productDistributionRepository;
    protected ProductDistributionBuilder $productDistributionBuilder;
    protected OrderRepository $orderRepository;
    protected UserProducerRepository $userProducerRepository;
    protected ProductOrderBuilder $productOrderBuilder;

    public function loadDependencies(): void
    {
        $this->distributionRepository = $this->loadService(DistributionRepository::class);
        $this->distributionSolver = $this->loadService(DistributionSolver::class);
        $this->pointSaleDistributionRepository = $this->loadService(PointSaleDistributionRepository::class);
        $this->pointSaleDistributionBuilder = $this->loadService(PointSaleDistributionBuilder::class);
        $this->productRepository = $this->loadService(ProductRepository::class);
        $this->productDistributionRepository = $this->loadService(ProductDistributionRepository::class);
        $this->productDistributionBuilder = $this->loadService(ProductDistributionBuilder::class);
        $this->orderRepository = $this->loadService(OrderRepository::class);
        $this->userProducerRepository = $this->loadService(UserProducerRepository::class);
        $this->productOrderBuilder = $this->loadService(ProductOrderBuilder::class);
    }

    public function instanciateDistribution(string $date, bool $delivery = true): Distribution
    {
        $distribution = new Distribution();

        $distribution->populateProducer($this->getProducerContext());
        $distribution->date = $date;
        $distribution->delivery = (int) $delivery;

        return $distribution;
    }

    // initDistribution
    public function createDistribution(string $date, bool $delivery = true): Distribution
    {
        $distribution = $this->instanciateDistribution($date, $delivery);

        $this->saveCreate($distribution);

        $this->createPointSaleDistributions($distribution);
        $this->createProductDistributions($distribution);

        return $distribution;
    }

    public function createDistributionIfNotExist(string $date, int $delivery = 1)
    {
        return $this->distributionRepository->findOneDistribution($date)
            ?? $this->createDistribution($date, $delivery);
    }

    public function createPointSaleDistributions(Distribution $distribution): void
    {
        $countPointSaleDistribution = $this->pointSaleDistributionRepository->countPointSaleDistributionsByDistribution($distribution);
        if (!$countPointSaleDistribution) {
            $this->pointSaleDistributionBuilder->createAllPointSaleDistributions($distribution, true);
        }
    }

    public function createProductDistributions(Distribution $distribution): void
    {
        $productArray = $this->productRepository->findProducts();
        $countProductDistribution = $this->productDistributionRepository->countProductDistributionsByDistribution($distribution);

        if (!$countProductDistribution) {
            foreach ($productArray as $product) {
                $this->addProduct($distribution, $product);
            }
        }
    }

    /**
     * Ajoute un produit à une distribution.
     */
    public function addProduct(Distribution $distribution, Product $product): ProductDistribution
    {
        $productDistribution = $this->productDistributionBuilder->createProductDistributionIfNotExist($distribution, $product);
        $this->updateOrderProductPrices($distribution, $product);

        return $productDistribution;
    }

    /**
     * Lie un produit aux distributions futures.
     */
    public function addProductIncomingDistributions(Product $product): void
    {
        $distributionArray = $this->distributionRepository->findDistributionsIncoming();
        foreach ($distributionArray as $distribution) {
            $this->addProduct($distribution, $product);
        }
    }

    /**
     * Ajoute un point de vente à une distribution
     */
    public function addPointSale(Distribution $distribution, PointSale $pointSale): PointSaleDistribution
    {
        $pointSaleDistribution = $this->pointSaleDistributionBuilder->createPointSaleDistributionIfNotExist($distribution, $pointSale);

        $dayString = $this->distributionSolver->getDistributionDayAsString($distribution);
        $pointSaleDistribution->delivery = 0;

        $deliveryDay = 'delivery_' . $dayString;
        if ($pointSale->$deliveryDay) {
            $pointSaleDistribution->delivery = 1;
        }

        $this->saveUpdate($pointSaleDistribution);

        return $pointSaleDistribution;
    }

    /**
     * Ajoute un point de vente aux distributions futures.
     */
    public function addPointSaleIncomingDistributions(PointSale $pointSale): void
    {
        $distributionArray = $this->distributionRepository->findDistributionsIncoming();
        foreach ($distributionArray as $distribution) {
            $this->addPointSale($distribution, $pointSale);
        }
    }

    public function updateOrderProductPrices(Distribution $distribution, Product $product): void
    {
        $ordersArray = $this->orderRepository->findOrdersByDistribution($distribution, 'AND origin != "user"');

        if ($ordersArray) {
            foreach ($ordersArray as $order) {
                foreach ($order->productOrder as $productOrder) {
                    if ($productOrder->id_product == $product->id) {

                        $user = null;
                        $userProducer = null;
                        if (isset($order->user) && $order->user) {
                            $user = $order->user;
                            $userProducer = $this->userProducerRepository->findOneUserProducer($user, $distribution->producer);
                        }

                        $this->productOrderBuilder->updateProductOrderPrice(
                            $productOrder,
                            $user,
                            $userProducer,
                            $order->pointSale,
                            $productOrder->quantity
                        );
                    }
                }
            }
        }
    }

    /**
     * Active ou désactive la distribution.
     */
    // active
    public function activeDistribution(Distribution $distribution, bool $active = true): void
    {
        $this->pointSaleDistributionBuilder->createAllPointSaleDistributions($distribution, true);
        $distribution->active = (int) $active;
        $this->saveUpdate($distribution);

        if ($active) {
            $distributionActiveEvent = new DistributionActiveEvent();
            $distributionActiveEvent->distribution = $distribution;
            $distribution->trigger(Distribution::EVENT_ACTIVE, $distributionActiveEvent);
        }
    }
}