<?php

declare(strict_types=1);

namespace BrandCrockAquaTheme\Content;

use BrandCrockAquaTheme\Content\CategoryBoxesStruct;
use Shopware\Core\Content\Category\CategoryCollection;
use Shopware\Core\Content\Category\CategoryDefinition;
use Shopware\Core\Content\Category\CategoryEntity;
use Shopware\Core\Content\Cms\Aggregate\CmsSlot\CmsSlotEntity;
use Shopware\Core\Content\Cms\DataResolver\CriteriaCollection;
use Shopware\Core\Content\Cms\DataResolver\Element\AbstractCmsElementResolver;
use Shopware\Core\Content\Cms\DataResolver\Element\ElementDataCollection;
use Shopware\Core\Content\Cms\DataResolver\FieldConfig;
use Shopware\Core\Content\Cms\DataResolver\FieldConfigCollection;
use Shopware\Core\Content\Cms\DataResolver\ResolverContext\EntityResolverContext;
use Shopware\Core\Content\Cms\DataResolver\ResolverContext\ResolverContext;
use Shopware\Core\Content\Product\ProductCollection;
use Shopware\Core\Content\ProductStream\Service\ProductStreamBuilderInterface;
use Shopware\Core\Framework\Context;
use Shopware\Core\Framework\DataAbstractionLayer\EntityRepository;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\EqualsFilter;

class CategoryBoxesCmsElementResolver extends AbstractCmsElementResolver
{
    private const CATEGORY_BOXES_ENTITY_FALLBACK = 'bc-category-boxes-entity-fallback';
    private const STATIC_SEARCH_KEY = 'bc-category-boxes';
    private $productRepository;
    private $productStreamBuilder;

    public function getType(): string
    {
        return 'bc-category-boxes';
    }

    public function __construct(
        ProductStreamBuilderInterface $productStreamBuilder,
        EntityRepository $productRepository
    ) {
        $this->productStreamBuilder = $productStreamBuilder;
        $this->productRepository = $productRepository;
    }

    public function collect(CmsSlotEntity $slot, ResolverContext $resolverContext): ?CriteriaCollection
    {
        $config = $slot->getFieldConfig();

        $collection = new CriteriaCollection();

        if (!$categories = $config->get('categories')) {
            return null;
        }

        if ($categories->isStatic() && $categories->getValue()) {
            $criteria = new Criteria($categories->getValue());
            $criteria->addAssociations([
                'productStreamId',
                'products',
                'nestedProducts.children',
                'productStream',
                'children'
            ])->addFilter(new EqualsFilter('active', 1));

            $collection->add(
                self::STATIC_SEARCH_KEY . '_' . $slot->getUniqueIdentifier(), 
                CategoryDefinition::class,
                $criteria
            );
        }

        if (
            $categories->isMapped()
            && $categories->getValue()
            && $resolverContext instanceof EntityResolverContext
        ) {
            if ($criteria = $this->collectByEntity($resolverContext, $categories)) {
                $collection->add(
                    self::CATEGORY_BOXES_ENTITY_FALLBACK . '_' . $slot->getUniqueIdentifier(),
                    CategoryDefinition::class,
                    $criteria
                );
            }
        }

        return $collection->all() ? $collection : null;
    }

    public function enrich(CmsSlotEntity $slot, ResolverContext $resolverContext, ElementDataCollection $result): void
    {
        $config = $slot->getFieldConfig();

        $boxes = new CategoryBoxesStruct();
        $slot->setData($boxes);

        if (!$categoryConfig = $config->get('categories')) {
            return;
        }

        if ($categoryConfig->isStatic()) {
            $this->enrichFromSearch($boxes, $result, self::STATIC_SEARCH_KEY . '_' . $slot->getUniqueIdentifier(), $config, $resolverContext);
        }

        if ($categoryConfig->isMapped() && $resolverContext instanceof EntityResolverContext) {
            $categories = $this->resolveEntityValue($resolverContext->getEntity(), $categoryConfig->getValue());
            if (!$categories) {
                $this->enrichFromSearch($boxes, $result, self::CATEGORY_BOXES_ENTITY_FALLBACK . '_' . $slot->getUniqueIdentifier(), $config, $resolverContext);
            } else {
                $boxes->setCategories($categories);
            }
        }
    }

    private function enrichFromSearch(CategoryBoxesStruct $boxes, ElementDataCollection $result, string $searchKey, FieldConfigCollection $config, ResolverContext $resolverContext): void
    {
        $context = $resolverContext->getSalesChannelContext()?->getContext();

        $showVariantCount = $config->get('showCountWithVariant');

        if ($showVariantCount !== null) {
            $showVariantCount = $showVariantCount->getValue();
        } else {
            $showVariantCount = false;
        }

        $searchResult = $result->get($searchKey);

        if (!$searchResult) return;

        /** @var CategoryCollection|null $categories */
        $categories = $searchResult->getEntities();

        if (!$categories) return;

        foreach ($categories as $category) {
            $count = 0;
            if (
                $category->getProductAssignmentType() === 'product_stream' &&
                $category->getProductStreamId() &&
                $category->getProductStream()?->getApiFilter()
            ) {
                $products = $this->loadStreamProducts($category, $context);

                if (!empty($products)) {
                    foreach ($products as $product) {
                        if (!$showVariantCount) {
                            if ($product->getParentId() === null) {
                                $count++;
                            }
                            continue;
                        }

                        if ($product->getParentId() === null) {
                            $count += max(1, $product->getChildCount());
                        }
                    }
                }
            } else {
                foreach ($category->getNestedProducts() as $nestedProduct) {
                    if (!$showVariantCount && $nestedProduct->getParentId() == null) {
                        if ($nestedProduct->getActive()) {
                            $count++;
                        }
                        continue;
                    }

                    if ($nestedProduct->getParentId() == null) {
                        if ($nestedProduct->getChildCount() > 0) {
                            $children = $nestedProduct->getChildren();
                            foreach ($children as $child) {
                                if ($child->getActive()) {
                                    $count++;
                                }
                            }
                            $count += $nestedProduct->getChildCount();
                        } else {
                            if ($nestedProduct->getActive()) {
                                $count++;
                            }
                        }
                    }
                }
            }

            $category->setExtensions(['bcProductCount' => $count]);
        }

        $boxes->setCategories($categories);
    }

    private function collectByEntity(EntityResolverContext $resolverContext, FieldConfig $config): ?Criteria
    {
        $entityCategories = $this->resolveEntityValue($resolverContext->getEntity(), $config->getValue());

        if ($entityCategories) {
            return null;
        }

        $criteria = $this->resolveCriteriaForLazyLoadedRelations($resolverContext, $config);
        $criteria->addAssociation('cover');

        return $criteria;
    }

    private function loadStreamProducts(CategoryEntity $category, Context $context): ProductCollection
    {
        $filters = $this->productStreamBuilder->buildFilters(
            $category->getProductStreamId(),
            $context
        );

        $criteria = new Criteria();
        $criteria->addFilter(...$filters);
        $criteria->addFilter(new EqualsFilter('active', true));
        $criteria->addAssociation('children');

        return $this->productRepository->search($criteria, $context)->getEntities();
    }
}
