<?php

namespace domain\Order\Order;

use domain\_\AbstractResolver;
use domain\Distribution\Distribution\Distribution;
use domain\Distribution\ProductDistribution\ProductDistributionRepository;
use domain\Feature\Feature\Feature;
use domain\Feature\Feature\FeatureChecker;
use domain\Product\Accessory\Accessory;
use domain\Product\Product\Product;
use domain\Product\Product\ProductSolver;

class OrderResolver extends AbstractResolver
{
    protected ProductSolver $productSolver;
    protected OrderSolver $orderSolver;
    protected OrderRepository $orderRepository;
    protected ProductDistributionRepository $productDistributionRepository;
    protected FeatureChecker $featureChecker;
    protected OrderBuilder $orderBuilder;

    protected array $ordersArrayCachedByDistribution = [];

    public function loadDependencies(): void
    {
        $this->productSolver = $this->loadService(ProductSolver::class);
        $this->orderSolver = $this->loadService(OrderSolver::class);
        $this->orderRepository = $this->loadService(OrderRepository::class);
        $this->productDistributionRepository = $this->loadService(ProductDistributionRepository::class);
        $this->featureChecker = $this->loadService(FeatureChecker::class);
        $this->orderBuilder = $this->loadService(OrderBuilder::class);
    }

    public function getProductQuantityByDistribution(
        Product $product,
        Distribution $distribution,
        bool $inNumberOfPieces = false,
        Order $orderCurrent = null,
        bool $ignoreOrderCurrent = false
    ): float
    {
        if(!isset($this->ordersArrayCachedByDistribution[$distribution->id])) {
            $this->ordersArrayCachedByDistribution[$distribution->id] = $this->orderRepository->findOrdersByDistribution($distribution);
        }
        $ordersArray = $this->ordersArrayCachedByDistribution[$distribution->id];
        if($orderCurrent) {
            $ordersArray = $this->orderBuilder->addOrUpdateOrIgnoreOrderFromArray($orderCurrent, $ordersArray, $ignoreOrderCurrent);
        }

        $productQuantity = $this->orderSolver->getProductQuantity($product, $ordersArray);
        if($inNumberOfPieces) {
            $productQuantity = $this->productSolver->getNumberOfPieces($productQuantity, $product);
        }

        return $productQuantity;
    }

    public function getSmallestQuantityAccessory(Product $product): ?int
    {
        $smallestQuantity = null;

        foreach ($product->getProductAccessories() as $productAccessory) {
            $quantityAccessory = $productAccessory->getAccessory()->getQuantity();
            $smallestQuantity = is_null($smallestQuantity) ? $quantityAccessory
                : min($smallestQuantity, $quantityAccessory);
        }

        return $smallestQuantity;
    }

    public function getProductQuantityMax(Product $product, Distribution $distribution): ?float
    {
        $productDistribution = $this->productDistributionRepository->findOneProductDistribution($distribution, $product);
        if(!$productDistribution) {
            return null;
        }

        $quantityMax = $productDistribution->quantity_max;

        if($this->featureChecker->isEnabled(Feature::ALIAS_PRODUCT_ACCESSORY)) {
            $smallestQuantityAccessory = $this->getSmallestQuantityAccessory($product);
            if (!is_null($smallestQuantityAccessory)) {
                $smallestQuantityAccessory = $this->productSolver->getWeightOrNumberOfPieces($smallestQuantityAccessory, $product);
                $quantityMax = is_null($quantityMax) ? $smallestQuantityAccessory
                    : min($quantityMax, $smallestQuantityAccessory);
            }
        }

        return $quantityMax;
    }

    public function getProductQuantityMaxOrderable(Product $product, Distribution $distribution, Order $orderCurrent = null): ?float
    {
        $productQuantity = $this->orderSolver->getProductQuantity($product, $orderCurrent ? [$orderCurrent] : []);
        $productQuantityRemaining = $this->getProductQuantityRemaining($product, $distribution, $orderCurrent);

        if(is_null($productQuantityRemaining)) {
            return null;
        }

        return $productQuantity + $productQuantityRemaining;
    }

    public function getQuantityOfAccessoryAvailableInDistribution(
        Accessory $accessory,
        Distribution $distribution,
        Order $orderCurrent = null,
        bool $ignoreOrderCurrent = false
    ): int
    {
        $quantityOfAccessoryUsed = 0;

        foreach($accessory->getProductAccessories() as $productAccessory) {
            $quantityOfAccessoryUsed += $this->getProductQuantityByDistribution(
                $productAccessory->getProduct(),
                $distribution,
                true,
                $orderCurrent,
                $ignoreOrderCurrent
            );
        }

        return $accessory->getQuantity() - $quantityOfAccessoryUsed;
    }

    public function getSmallestQuantityAccessoryAvailable(
        Product $product,
        Distribution $distribution,
        Order $orderCurrent = null,
        bool $ignoreOrderCurrent = false
    ): ?int
    {
        $smallestQuantity = null;

        if($this->featureChecker->isEnabled(Feature::ALIAS_PRODUCT_ACCESSORY)) {
            foreach ($product->getProductAccessories() as $productAccessory) {
                $quantityAccessoryAvailableInDistribution = $this->getQuantityOfAccessoryAvailableInDistribution(
                    $productAccessory->getAccessory(),
                    $distribution,
                    $orderCurrent,
                    $ignoreOrderCurrent
                );
                $smallestQuantity = is_null($smallestQuantity) ? $quantityAccessoryAvailableInDistribution
                    : min($smallestQuantity, $quantityAccessoryAvailableInDistribution);
            }
        }

        return $smallestQuantity;
    }

    public function getProductQuantityRemaining(
        Product $product,
        Distribution $distribution,
        Order $orderCurrent = null,
        bool $ignoreOrderCurrent = false
    ): ?float
    {
        $productDistribution = $this->productDistributionRepository->findOneProductDistribution($distribution, $product);
        if(!$productDistribution) {
            return null;
        }

        $quantityOrder = $this->getProductQuantityByDistribution(
            $product,
            $distribution,
            false,
            $orderCurrent,
            $ignoreOrderCurrent
        );
        $quantityRemaining = is_null($productDistribution->quantity_max) ? null
            : ($productDistribution->quantity_max - $quantityOrder);

        // Limitation par nombre d'accessoires disponibles
        if($this->featureChecker->isEnabled(Feature::ALIAS_PRODUCT_ACCESSORY)) {
            $smallestQuantityAccessoryAvailable = $this->getSmallestQuantityAccessoryAvailable(
                $product,
                $distribution,
                $orderCurrent,
                $ignoreOrderCurrent
            );
            if (!is_null($smallestQuantityAccessoryAvailable)) {
                $smallestQuantityAccessoryAvailable = $this->productSolver->getWeightOrNumberOfPieces($smallestQuantityAccessoryAvailable, $product);
                $quantityRemaining = is_null($quantityRemaining) ? $smallestQuantityAccessoryAvailable
                    : min($quantityRemaining, $smallestQuantityAccessoryAvailable);
            }
        }

        return $quantityRemaining;
    }
}