<?php

namespace Shop\Repositories\ProductCategory;

use Diamond\Repositories\PropelAbstractRepository;
use Propel\Runtime\ActiveQuery\Criteria;
use Propel\Runtime\ActiveQuery\ModelCriteria;
use Propel\Runtime\Collection\ObjectCollection;
use Repo\CollectionInterface;
use Repo\CrudRepositoryBuilderInterface;
use Repo\EntityInterface;
use Repo\PaginationInterface;
use Shop\Domain\Customer\Contracts\CustomerEntityInterface;
use Shop\Domain\ProductCategory\Contracts\ProductCategoryEntityInterface;
use Shop\Domain\ProductCategory\Contracts\ProductCategoryRepositoryInterface;
use Shop\Domain\ProductCategory\ProductCategoryCollection;
use Shop\Models\Category\Category;
use Shop\Models\Category\CategoryQuery;
use Shop\Models\Product\ProductQuery;

class ProductCategoryRepository extends PropelAbstractRepository implements ProductCategoryRepositoryInterface
{

    protected static $CACHE_COUNTS = [];

    public static function createEntity(): EntityInterface
    {
        return new Category;
    }

    /**
     * @return ModelCriteria|CategoryQuery
     *
     */
    protected function createQuery(): ModelCriteria
    {
        return CategoryQuery::create();
    }

    /**
     * @param PaginationInterface|ProductCategoryCriteria $criteria
     * @param ModelCriteria|CategoryQuery $dbCriteria
     */
    protected function modifyCriteria(PaginationInterface $criteria, ModelCriteria $dbCriteria): void
    {
        $this->modifyCriteriaForFilterSort($criteria, $dbCriteria);

        $dbCriteria
            ->_if($criteria->getSearchByTitle() !== null)
            ->filterByTitle('%' . $criteria->getSearchByTitle() . '%')
            ->_endif()
            ->_if($criteria->getFilterByTitle() !== null)
            ->filterByTitle($criteria->getFilterByTitle())
            ->_endif()
            ->_if($criteria->getFilterByParentIds() !== null)
            ->filterByParentId($criteria->getFilterByParentIds())
            ->_endif()
            //--------------------
            ->_if($criteria->getFilterByParentIdIsNull() === true)
            ->filterByParentId(null)
            ->_endif()
            //--------------------
            ->_if($criteria->getSortByTitle() !== null)
            ->orderByTitle($criteria->getSortByTitle())
            ->_endif()
            ->_if($criteria->getIgnoreIds() !== null)
            ->filterById($criteria->getIgnoreIds(), Criteria::NOT_IN)
            ->_endif();

    }

    /**
     * @param PaginationInterface|ProductCategoryCriteria $criteria
     * @return CollectionInterface
     * @throws \Repo\Concrete\Exceptions\Collection
     */
    public function findByCriteria(PaginationInterface $criteria): CollectionInterface
    {
        $query = $this->createQuery();
        $this->modifyCriteria($criteria, $query);
        $result = $query->paginate($criteria->getPage(), $criteria->getLimit());

        $collection = new ProductCategoryCollection();
        $rows = $result->getResults();

        if ($criteria->asTreeView() === true) {
            $this->treeAdaptive($rows, $collection, $criteria);
        } else {
            foreach ($rows as $row) {
                $this->collectionPush($row, $collection, $criteria);
            }
        }

        return $collection;
    }

    /**
     * @param ObjectCollection $rows
     * @param ProductCategoryCollection $collection
     * @param ProductCategoryCriteria $criteria
     * @throws \Repo\Concrete\Exceptions\Collection
     */
    private function treeAdaptive(ObjectCollection $rows, ProductCategoryCollection $collection, ProductCategoryCriteria $criteria): void
    {
        $rowsFilter = [];

        /**
         * строим дерево чтобы отсчеь разделы которые были в игноре но седержать дочерние, дочерние должны быть убраны тоже
         * @var Category $row
         * @var Category $child
         */
        foreach ($rows as $row) {

            if ($row->getParentId() > 0) {
                $rowsFilter[$row->getParentId()]['childs'][$row->getId()] = $row;
            } else {
                $rowsFilter[$row->getId()]['model'] = $row;
            }
        }

        //убираем без моделей записи
        $rows = array_filter($rowsFilter, function ($el) {
            return isset($el['model']);
        });

        $this->calculateCountPositions($rows, $criteria);


        usort($rows, function ($el, $el2) {
            return $el['model']->getTitle() > $el2['model']->getTitle() ? 1 : -1;
        });

        foreach ($rows as $data) {

            $row = $data['model'];

            if (!isset($data['childs']) && $row) {
                $this->collectionPush($row, $collection, $criteria);
                continue;
            }

            $row->setCountChildrens(count($data['childs']));
            $this->collectionPush($row, $collection, $criteria);

            usort($data['childs'], function ($el, $el2) {
                /**
                 * @var Menu $el
                 * @var Menu $el2
                 */
                return $el->getTitle() > $el2->getTitle() ? 1 : -1;
            });

            foreach ($data['childs'] as $child) {
                $this->collectionPush($child, $collection, $criteria);
            }
        }
    }

    /**
     * @param array $rows
     * @param ProductCategoryCriteria $criteria
     * @param int $count
     * @return int
     */
    protected function calculateCountPositions(array $rows, ProductCategoryCriteria $criteria, int $count = 0): int
    {

        foreach ($rows as $data) {

            $row = is_object($data) ? $data : $data['model'];

            if ($criteria->getFindPositionCounts() === true) {

                $res = $this->getCountProducts($row, $data, $criteria);

                $row->setCountProducts($res);

                $count += $res;
            }
        }
        return $count;
    }

    /**
     * @param $row
     * @param $data
     * @param $criteria
     * @return int
     */
    public function getCountProducts($row, $data, $criteria): int
    {
        if (isset(self::$CACHE_COUNTS[$row->getId()])) {
            return self::$CACHE_COUNTS[$row->getId()];
        }

        $resCount = $this->createQuery()
            ->filterById($row->getId())
            ->_if($criteria->getFilterByPriceListIds() !== null)
            ->useProductQuery()
            ->usePositionQuery()
            ->filterByPricelistSheetId($criteria->getFilterByPriceListIds())
            ->endUse()
            ->groupById()
            ->endUse()
            ->_endif()
            ->count();

        if (!is_object($data) && isset($data['childs'])) {
            $res = $this->calculateCountPositions($data['childs'], $criteria, $resCount);
        } else {
            $res = $resCount;
        }

        self::$CACHE_COUNTS[$row->getId()] = $res;

        return $res;
    }


    /**
     * @param ProductCategoryEntityInterface $row
     * @param ProductCategoryCollection $collection
     * @param ProductCategoryCriteria $criteria
     * @throws \Repo\Concrete\Exceptions\Collection
     */
    protected function collectionPush(ProductCategoryEntityInterface $row, ProductCategoryCollection $collection,
                                      ProductCategoryCriteria $criteria): void
    {
        $collection->push($row);
    }


    /**
     * Получение разделов только с товаров который имеет фото
     *
     * @param \Shop\Contracts\CustomerAccessPricesInterface $customer
     * @param type $parent
     * @return ObjectCollection
     */
    public function findByParentIdWithImage(CustomerEntityInterface $customer, $parent = null): ProductCategoryCollection
    {
        $results = $this->createQuery()
            ->orderByTitle()
            ->filterByParentId($parent)
            ->useProductQuery()
            ->filterByImg('', Criteria::GREATER_THAN)
            ->usePositionQuery()
            ->filterByPricelistSheetId($customer->getAccessPrices())
            ->filterByRemain(0, Criteria::GREATER_THAN)
            ->endUse()
            ->endUse()
            ->groupById()
            ->find();
        $collection = new ProductCategoryCollection();
        foreach ($results as $result) {
            $collection->push($result);
        }

        return $collection;
    }

    /**
     * @todo перевсти на корректный запрос по скидкам
     * Получение разделов с скидочными товарами
     * @param type $parent
     * @return ObjectCollection
     */
    public function findByParentIdWithImageAndSale(CustomerEntityInterface $customer, $parent = null): ProductCategoryCollection
    {
        $results = $this->createQuery()
            ->orderByTitle()
            ->filterByParentId($parent)
            ->useProductQuery()
            ->filterByImg('', Criteria::GREATER_THAN)
            ->usePositionQuery()
            ->filterByPricelistSheetId($customer->getAccessPrices())
            ->filterByRemain(0, Criteria::GREATER_THAN)
            ->filterBySaleId(1)
            ->endUse()
            ->endUse()
            ->groupById()
            ->find();

        $collection = new ProductCategoryCollection();
        foreach ($results as $result) {
            $collection->push($result);
        }

        return $collection;
    }

    /**
     * получение разделов верхнего уровня
     *
     * @param \Shop\Contracts\CustomerAccessPricesInterface $customer
     * @param int $days
     * @param int|null $parent
     * @return ProductCategoryCollection
     */
    public function findByParentIdWithImageAndNew(CustomerEntityInterface $customer, int $days, int $parent = null): ProductCategoryCollection
    {
        $results = $this->createQuery()
            ->filterByParentId($parent)
            ->useCategoryRelatedByIdQuery("parent")
            ->useProductQuery()
            ->filterBy("dateCreate", sprintf("cdate >= DATE_SUB(CURRENT_DATE, INTERVAL %s DAY)", $days), Criteria::CUSTOM)
            ->filterByImg('', Criteria::GREATER_THAN)
            ->usePositionQuery()
            ->filterByPricelistSheetId($customer->getAccessPrices())
            ->filterByRemain(0, Criteria::GREATER_THAN)
            ->endUse()
            ->endUse()
            ->endUse()
            ->groupById()
            ->orderByTitle()
            ->find();

        $collection = new ProductCategoryCollection();
        foreach ($results as $result) {
            $collection->push($result);
        }

        return $collection;
    }


    /**
     * @param int|null $parent
     * @return ProductCategoryCollection
     * @throws \Repo\Concrete\Exceptions\Collection
     */
    public function findByParentId(?int $parent = null): ProductCategoryCollection
    {
        $results = $this->createQuery()
            ->orderByTitle()
            ->filterByParentId($parent)
            ->find();

        $collection = new ProductCategoryCollection();
        foreach ($results as $result) {
            $collection->push($result);
        }

        return $collection;
    }

    /**
     * по ключам
     * @param array $ids
     * @return type
     */
    public function findByIds(array $ids): ProductCategoryCollection
    {
        $results = $this->createQuery()
            ->orderByTitle()
            ->filterByPrimaryKeys($ids)
            ->find();

        $collection = new ProductCategoryCollection();
        foreach ($results as $result) {
            $collection->push($result);
        }

        return $collection;
    }


    /**
     * Поиск раздела по всевдониму
     * @param type $param
     */
    public function findOneByAlias(string $alias, $ignoreRootCategory = true): ?ProductCategoryEntityInterface
    {

        $result = $this->createQuery()
            ->filterByAlias($alias)
            ->_if($ignoreRootCategory === true)
            ->filterByParentId(0, Criteria::GREATER_THAN)
            ->_endif()
            ->findOne();

        return $result;
    }

    /**
     * @param string $alias
     * @return ProductCategoryCollection
     * @throws \Repo\Concrete\Exceptions\Collection
     */
    public function findByParentAlias(string $alias): ProductCategoryCollection
    {

        $results = $this->createQuery()
            ->useCategoryRelatedByIdQuery("parent")
            ->filterByParentId(0, Criteria::GREATER_THAN)
            ->endUse()
            ->filterByAlias($alias)
            ->find();

        $collection = new ProductCategoryCollection();
        foreach ($results as $result) {
            $collection->push($result);
        }

        return $collection;
    }
}