<?php

namespace domain\Distribution\Distribution;

use common\helpers\GlobalParam;
use domain\Distribution\Distribution\Event\DistributionActiveEvent;
use domain\Distribution\PointSaleDistribution\PointSaleDistribution;
use domain\Distribution\PointSaleDistribution\PointSaleDistributionBuilder;
use domain\Distribution\PointSaleDistribution\PointSaleDistributionRepository;
use domain\Distribution\ProductDistribution\ProductDistribution;
use domain\Distribution\ProductDistribution\ProductDistributionBuilder;
use domain\Distribution\ProductDistribution\ProductDistributionRepository;
use domain\Order\Order\OrderRepository;
use domain\Order\ProductOrder\ProductOrderBuilder;
use domain\PointSale\PointSale\PointSale;
use domain\PointSale\PointSale\PointSaleRepository;
use domain\Product\Product\Product;
use domain\Product\Product\ProductRepository;
use domain\Product\Product\ProductSolver;
use domain\User\User\User;
use domain\User\UserProducer\UserProducerRepository;
use domain\_\AbstractBuilder;

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;
    protected ProductSolver $productSolver;
    protected PointSaleRepository $pointSaleRepository;

    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);
        $this->productSolver = $this->loadService(ProductSolver::class);
        $this->pointSaleRepository = $this->loadService(PointSaleRepository::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;
    }

    public function createDistribution(string $date, bool $delivery = true): Distribution
    {
        $distribution = $this->instanciateDistribution($date, $delivery);
        $this->create($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
    {
        if($this->productSolver->isStatusOnlineOrOffline($product)) {
            $productDistribution = $this->productDistributionBuilder->createProductDistributionIfNotExist($distribution, $product);
            $this->productDistributionBuilder->updateProductDistribution($productDistribution);
            $this->updateOrderProductPrices($distribution, $product);
            return $productDistribution;
        }

        return null;
    }

    /**
     * Lie un produit aux distributions futures.
     */
    public function addProductIncomingDistributions(Product $product): void
    {
        $distributionArray = $this->distributionRepository->findDistributionsIncoming(true);
        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(true);
        foreach ($distributionArray as $distribution) {
            $this->addPointSale($distribution, $pointSale);
        }
    }

    public function updateOrderProductPrices(Distribution $distribution, Product $product): void
    {
        $ordersArray = $this->orderRepository->findOrdersByDistribution($distribution, false, '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);
                        }

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

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

        $this->pointSaleDistributionBuilder->createAllPointSaleDistributions($distribution, true);

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

    public function activeDistributionByDate(\DateTime $date, bool $active = true): void
    {
        $distribution = $this->createDistributionIfNotExist($date->format('Y-m-d'));
        $this->activeDistribution($distribution, $active);
    }

    public function activeDistributionByDateIfDelivery(\DateTime $date, bool $active = true): void
    {
        $activeDay = $this->pointSaleRepository->isDayOfWeekWithDelivery($date->format('l'));
        if($activeDay || !$active) {
            $this->activeDistributionByDate($date, $active);
        }
    }

    public function activeDistributionByDayWeek(string $date, string $dayWeek, bool $active = true): void
    {
        $week = sprintf('%02d', date('W', strtotime($date)));
        $start = strtotime(date('Y', strtotime($date)) . 'W' . $week);
        $dateDay = new \DateTime(date('Y-m-d', strtotime($dayWeek, $start)));
        $this->activeDistributionByDateIfDelivery($dateDay, $active);
    }

    public function activeDistributionsOfWeek(string $date, bool $active = true): void
    {
        $daysWeekArray = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday'];

        foreach($daysWeekArray as $dayWeek) {
            $this->activeDistributionByDayWeek($date, $dayWeek, $active);
        }
    }
}