<?php

namespace Shop\Infrastructure\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\Concrete\AbstractCollection;
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\Infrastructure\Models\Category\Category;
use Shop\Infrastructure\Models\Category\CategoryQuery;
use Shop\Infrastructure\Models\Product\ProductQuery;

class ProductCategoryRepository extends PropelAbstractRepository implements ProductCategoryRepositoryInterface
{

    use RecursiveTrait;

    protected static $CACHE_COUNTS = [];

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

    /**
     * @return ModelCriteria|CategoryQuery
     *
     */
    protected function createQuery(): ModelCriteria
    {
        return CategoryQuery::create()
            ->addAsColumn('child_exist','`child`.id')
            ->leftJoinCategoryRelatedById('child')
            ->groupById()
            ;
    }

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

        $dbCriteria

            ->_if($criteria->getFilterByCatalogId())
            ->filterByCatalogId($criteria->getFilterByCatalogId())
            ->_endif()

            ->_if($criteria->getFilterByAlias() !== null)
            ->filterByAlias($criteria->getFilterByAlias())
            ->_endif()

            ->_if($criteria->getSearchByTitle() !== null)
            ->filterByTitle('%' . $criteria->getSearchByTitle() . '%', Criteria::LIKE)
            ->_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|ProductCategoryCollection
     * @throws \Repo\Concrete\Exceptions\Collection
     */
    public function findByCriteria(PaginationInterface $criteria): CollectionInterface
    {
        $query = $this->createQuery();
        self::modifyCriteriaForFilterSort($criteria, $query);
        $this->modifyCriteria($criteria, $query);
        $result = $query->paginate($criteria->getPage(), $criteria->getLimit());

        $collection = self::createCollection();
        $rows = $result->getResults();

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

        return $collection;
    }

    protected function createCollection(): CollectionInterface
    {
        return new ProductCategoryCollection();
    }


    public function calculateParentsLevel(array $row, &$level = 0): void
    {
        if(isset($row['ParentCategory'])){
            $level++;
            $this->calculateParentsLevel($row['ParentCategory'], $level);
        }
    }

    public function buildCategoriesCollection(array $categories, ProductCategoryCollection $collection,ProductCategoryCriteria $criteria)
    {

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

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

        array_walk($categories, function ($row) use ($collection, $criteria) {
            $category = new Category;
            $category->fromArray($row);

            $level = 0;
            $this->calculateParentsLevel($row, $level);
            $category->setParentsLevel($level);
            $category->setCountProducts($row['Products']);
            $collection->push($category);


            if (isset($row['Children'])) {

                $category->setCountChildrens(count($row['Children']));
                $this->buildCategoriesCollection($row['Children'], $collection, $criteria);
            }

        });
    }

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

        $categories = $this->buildCategoryTree($rows->toArray());

        $this->buildCategoriesCollection($categories, $collection, $criteria);


        // dd($collection);

        return;

        /**
         * строим дерево чтобы отсчеь разделы которые были в игноре но седержать дочерние, дочерние должны быть убраны тоже
         * @var Category $row
         * @var Category $child
         */
        foreach ($rows as $row) {
            $level[0][$row->getId()] = $row;
        }

        $levelCount = 0;

        while (true) {

            if (!isset($level[$levelCount])) {
                break;
            }

            foreach ($level[$levelCount] as $i => $row) {
                if ($row->getParentId() > 0) {
                    if (isset($level[$levelCount][$row->getParentId()])) {
                        $level[$levelCount + 1][$row->getId()] = $row;
                        unset($level[$levelCount][$i]);
                    }
                }
            }

            $levelCount++;
        }

    }

    /**
     * @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 &$row) {

            $row['Products'] = 0;

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

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

                $row['Products'] = $res;

                $count += $res;
            }
        }

        return $count;
    }

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

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

        if (isset($row['Children'])) {
            $res = $this->calculateCountPositions($row['Children'], $criteria, $resCount);
        } else {
            $res = $resCount;
        }

        self::$CACHE_COUNTS[$row['Id']] = $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;
    }

    /**
     * @param type $parent
     * @return ObjectCollection
     * @todo перевсти на корректный запрос по скидкам
     * Получение разделов с скидочными товарами
     */
    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("dateUpdate", sprintf("udate >= 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;
    }

    public function findOneByAliasAndParentId(string $alias, int $parentId): ?ProductCategoryEntityInterface
    {

        $result = $this->createQuery()
            ->filterByAlias($alias)
            ->filterByParentId($parentId)
            ->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;
    }
}