<?php

namespace Shop\Application\Shopping;


use Shop\Application\Shopping\ChangeOrderItemState\ChangeOrderItemStateCommand;
use Shop\Application\Shopping\ChangeOrderState\ChangeOrderStateCommand;
use Shop\Application\Shopping\CreateCustomerOrder\CreateCustomerOrderCommand;
use Shop\Application\Shopping\CreateCustomerOrder\OrderResult;
use Shop\Application\Shopping\CreateProviderOrder\CreateProviderOrderCommand;
use Shop\Application\Shopping\CreateProviderOrdersFromCustomerOrderItem\CreateProviderOrdersFromCustomerOrderItemCommand;
use Shop\Application\Shopping\CreateProviderOrdersFromCustomerOrderItem\OrderRequest;
use Shop\Application\Shopping\Exceptions\CustomerOrderNotFoundException;
use Shop\Application\Shopping\Exceptions\ProviderOrderNotFoundException;
use Shop\Application\Shopping\MergeCustomerOrders\MergeCustomerOrdersCommand;
use Shop\Application\Shopping\MergeProviderOrders\MergeProviderOrdersCommand;
use Shop\Application\Shopping\MergeProviderOrders\Result;
use Shop\Application\Shopping\PrepareCustomerOrderDataForReport\PrepareCustomerOrderDataForReportCommand;
use Shop\Application\Shopping\PrepareCustomerOrderDataForReport\ResultData;
use Shop\Application\Shopping\PrepareProviderOrderDataForReport\PrepareProviderOrderDataForReportCommand;
use Shop\Application\Shopping\ResetOrderState\ResetOrderStateCommand;
use Shop\Application\Shopping\SendCustomerOrders\ResultOrders;
use Shop\Application\Shopping\SendCustomerOrders\SendCustomerOrdersCommand;
use Shop\Application\Shopping\SendNotifyAboutPostCode\SendNotifyAboutPostCodeCommand;
use Shop\Application\Shopping\SendNotifyAboutState\SendNotifyAboutStateCommand;
use Shop\Application\Shopping\SendNotifyOrderChangeState\SendNotifyOrderChangeStateCommand;
use Shop\Application\Shopping\SendProviderOrders\SendProviderOrdersCommand;
use Shop\Domain\CustomerOrder\Contracts\CustomerOrderEntityInterface;
use Shop\Domain\CustomerOrder\CustomerOrderCollection;
use Shop\Domain\OrderItem\Contracts\OrderItemEntityInterface;
use Shop\Domain\ProviderOrder\Contracts\ProviderOrderEntityInterface;
use Shop\Domain\ProviderOrder\ProviderOrderCollection;
use Shop\Domain\State\Contracts\StateEntityInterface;
use Shop\Models\Order\Order;
use Shop\Models\Order\OrderQuery;
use Shop\Models\OrderType\OrderType;
use Shop\Repositories\CustomerOrder\CustomerOrderCriteria;
use Shop\Repositories\CustomerOrder\CustomerOrderRepository;
use Shop\Repositories\OrderItem\OrderItemRepository;
use Shop\Repositories\ProviderOrder\ProviderOrderRepository;
use spaceonfire\CommandBus\CommandBus;

class ShoppingService
{
    protected $customerOrderRepository;
    protected $itemRepository;
    protected $providerOrderRepository;
    protected $commandBus;
    protected $customerRepository;

    /**
     * ShoppingService constructor.
     * @param CustomerOrderRepository $customerOrderRepository
     * @param ProviderOrderRepository $providerOrderRepository
     * @param OrderItemRepository $itemRepository
     * @param CommandBus $commandBus
     */
    public function __construct(CustomerOrderRepository $customerOrderRepository,
                                ProviderOrderRepository $providerOrderRepository,
                                OrderItemRepository $itemRepository,
                                CommandBus $commandBus)
    {
        $this->customerOrderRepository = $customerOrderRepository;
        $this->providerOrderRepository = $providerOrderRepository;
        $this->itemRepository = $itemRepository;
        $this->commandBus = $commandBus;
    }


    /**
     * @return int
     */
    public function getCustomerOrdersCount(): int
    {
        return $this->customerOrderRepository->count(CustomerOrderCriteria::create());
    }

    /**
     * @param array $ignoreStates
     * @return float
     * @throws \Propel\Runtime\Exception\PropelException
     */
    public function getSumCustomerOrdersByIgnoreStates(array $ignoreStates): float
    {
        if (!$order = OrderQuery::create()
            ->useOrderItemQuery()
            ->withColumn('SUM(price*amount)', 'sum')
            ->filterByStateId($ignoreStates, \Propel\Runtime\ActiveQuery\Criteria::NOT_IN)
            ->endUse()
            ->filterByTypeId(OrderType::TYPE_CUSTOMER_ORDER)
            ->findOne()) {
            return 0;
        }

        return (float)$order->getVirtualColumn("sum");
    }

    /**
     * @param int $id
     * @return CustomerOrderEntityInterface
     */
    public function getCustomerOrderById(int $id): CustomerOrderEntityInterface
    {
        if (!$order = $this->customerOrderRepository->findById($id)) {
            throw new CustomerOrderNotFoundException('order not found by id ' . $id);
        }

        return $order;
    }


    /**
     * Объединение заказов поставщиков
     * @param array $ids
     * @return ProviderOrderEntityInterface
     */
    public function mergeProviderOrdersByIds(array $ids): ProviderOrderEntityInterface
    {
        $result = new Result();

        $this->commandBus->handle(new MergeProviderOrdersCommand($ids, $result));

        if (!$order = $this->providerOrderRepository->findById($result->getOrderId())) {
            throw new ProviderOrderNotFoundException('merge success but provider order not found by id ' . $result->getOrderId());
        }

        return $order;
    }

    /**
     * Объединение клиентских заказов
     * @param array $ids
     * @return CustomerOrderEntityInterface
     */
    public function mergeCustomerOrdersByIds(array $ids): CustomerOrderEntityInterface
    {
        $result = new \Shop\Application\Shopping\MergeCustomerOrders\Result();

        $this->commandBus->handle(new MergeCustomerOrdersCommand($ids, $result));

        if (!$order = $this->customerOrderRepository->findById($result->getOrderId())) {
            throw new CustomerOrderNotFoundException('merge success but customer order not found by id ' . $result->getOrderId());
        }

        return $order;
    }

    /**
     * смена состояния позиции заказа
     * @param int $itemId
     * @param StateEntityInterface $stateEntity
     */
    public function changeOrderItemState(int $itemId, StateEntityInterface $stateEntity): void
    {
        $this->commandBus->handle(new ChangeOrderItemStateCommand($itemId, $stateEntity->getId()));
        $item = $this->itemRepository->findById($itemId);
        $this->resetOrderState($item);
    }

    /**
     * смена состояний позиций заказа
     * @param Order $order
     * @param int $stateId
     * @throws \Propel\Runtime\Exception\PropelException
     */
    public function changeOrderItemsState(Order $order, int $stateId): void
    {
        foreach ($order->getOrderItems() as $item) {
            $this->commandBus->handle(new ChangeOrderItemStateCommand($item->getId(), $stateId));
        }
    }


    /**
     * смена состояния заказа
     * @param CustomerOrderEntityInterface $order
     * @param int $stateId
     */
    public function changeOrderState(CustomerOrderEntityInterface $order, int $stateId): void
    {
        $this->commandBus->handle(new ChangeOrderStateCommand($order, $stateId));
        /**
         * @var \Propel\Runtime\Collection\ObjectCollection $items
         */
        $items = $order->getOrderItems();
        $item = $items->getFirst();
        $this->resetOrderState($item);
    }

    /**
     * переустановить состояние заказа
     * @param OrderItemEntityInterface|OrderItem $orderItemEntity
     */
    public function resetOrderState(OrderItemEntityInterface $orderItemEntity): void
    {
        $this->commandBus->handle(new ResetOrderStateCommand($orderItemEntity));
    }

    /**
     * Создание заказу поставщику на основе ids позиций заказа клиента
     * @param array $ids
     * @return ProviderOrderCollection
     */
    public function createProviderOrdersFromCustomerOrderItemIds(array $ids): ProviderOrderCollection
    {
        $result = new \Shop\Application\Shopping\CreateProviderOrdersFromCustomerOrderItem\Result();
        $this->commandBus->handle(new CreateProviderOrdersFromCustomerOrderItemCommand($ids, $result));
        return $result->getProviderOrderCollection();
    }

    /**
     * Уведомление о смене состояния
     * @param CustomerOrderEntityInterface $order
     */
    public function sendNotifyOrderChangeState(CustomerOrderEntityInterface $order): void
    {
        $this->commandBus->handle(new SendNotifyOrderChangeStateCommand($order));
    }

    /**
     * @param CreateCustomerOrder\OrderRequest $orderRequest
     * @return CustomerOrderEntityInterface
     */
    public function createCustomerOrder(\Shop\Application\Shopping\CreateCustomerOrder\OrderRequest $orderRequest): CustomerOrderEntityInterface
    {
        $result = new OrderResult();
        $this->commandBus->handle(new CreateCustomerOrderCommand($orderRequest, $result));
        return $result->getOrder();
    }


    /**
     * Создание заказа поставщику от торговой точки
     * @param OrderRequest $orderRequest
     * @return ProviderOrderEntityInterface
     */
    public function createProviderOrder(OrderRequest $orderRequest): ProviderOrderEntityInterface
    {
        $result = new \Shop\Application\Shopping\CreateProviderOrder\Result();
        $this->commandBus->handle(new CreateProviderOrderCommand($orderRequest, $result));
        return $result->getOrder();
    }

    /**
     * Отправить заказы поставщику по ids
     * @param array $ids
     * @return ProviderOrderCollection
     */
    public function sendProviderOrdersByIds(array $ids): ProviderOrderCollection
    {
        $this->commandBus->handle(new SendProviderOrdersCommand($ids));
    }

    /**
     * подготовка данных заказа поставщику для уведомления
     * @param ProviderOrderEntityInterface $order
     * @return array
     */
    public function prepareProviderOrderDataForReport(ProviderOrderEntityInterface $order): array
    {
        $result = new \Shop\Application\Shopping\PrepareProviderOrderDataForReport\Result();
        $this->commandBus->handle(new PrepareProviderOrderDataForReportCommand($order, $result));
        return $result->getData();
    }


    /**
     * подгтоовить даненые заказа клиента для отправки уведомления
     * @param CustomerOrderEntityInterface $order
     * @return array
     */
    public function prepareCustomerOrderDataForReport(CustomerOrderEntityInterface $order): array
    {
        $result = new ResultData();
        $this->commandBus->handle(new PrepareCustomerOrderDataForReportCommand($order, $result));
        return $result->getData();
    }

    /**
     * отправка клиентских заказов на почту
     * @param array $ids
     * @param bool $notifyCustomer
     * @param bool $notifyManager
     * @return CustomerOrderCollection
     */
    public function sendCustomerOrdersByIds(array $ids, bool $notifyCustomer = true, bool $notifyManager = true): CustomerOrderCollection
    {
        $result = new ResultOrders();
        $this->commandBus->handle(new SendCustomerOrdersCommand($ids, $notifyCustomer, $notifyManager, $result));
        return $result->getSuccessOrders();
    }


    /**
     * @param CustomerOrderEntityInterface $order
     */
    public function sendNotifyAboutPostCode(CustomerOrderEntityInterface $order): void
    {
        $this->commandBus->handle(new SendNotifyAboutPostCodeCommand($order));
    }

    /**
     * @param CustomerOrderEntityInterface $order
     */
    public function sendNotifyAboutState(CustomerOrderEntityInterface $order): void
    {
        $this->commandBus->handle(new SendNotifyAboutStateCommand($order));
    }

    /**
     * @param CustomerOrderEntityInterface $order
     */
    public function sendNotifyAboutPostCodeAndState(CustomerOrderEntityInterface $order): void
    {
        $this->commandBus->handle(new SendNotifyAboutPostCodeCommand($order));
    }


    /**
     * @param int $userId
     * @return CustomerOrderEntityInterface|null
     */
    public function getLast(int $userId): ?CustomerOrderEntityInterface
    {
        $orders = $this->customerOrderRepository->findByCriteria(
            CustomerOrderCriteria::create()->setFilterByCustomerId($userId)->setSortById('DESC')->setLimit(1)
        );

        if(!$current = $orders->current()){
            return null;
        }

        return $current;
    }

}