<?php
namespace Autogedal\BlogInternalLinking\Block\Frontend;

use Magento\Catalog\Block\Product\AbstractProduct;
use Magento\Catalog\Block\Product\Context;
use Magento\Framework\Registry;
use Magento\Catalog\Model\ResourceModel\Product\CollectionFactory as ProductCollectionFactory;
use Magento\Catalog\Model\Product\Visibility;
use Magento\Catalog\Model\Product\Attribute\Source\Status;
use Magento\Catalog\Model\CategoryRepository;
use Magento\Framework\Url\Helper\Data as UrlHelper;
use Magento\InventoryApi\Api\GetSourceItemsBySkuInterface;
use Magento\InventoryApi\Api\Data\SourceItemInterface;
use Magento\InventoryApi\Api\GetSourcesAssignedToStockOrderedByPriorityInterface;
use Magento\InventorySalesApi\Api\StockResolverInterface;
use Magento\InventorySalesApi\Api\Data\SalesChannelInterface;
use Autogedal\BlogInternalLinking\Model\GroupFactory;

class LinkingGroup extends AbstractProduct
{
    private $registry;
    private $groupFactory;
    private $productCollectionFactory;
    private $visibility;
    private $categoryRepository;
    private $urlHelper;
    private GetSourceItemsBySkuInterface $getSourceItemsBySku;
    private GetSourcesAssignedToStockOrderedByPriorityInterface $getSourcesAssignedToStock;
    private StockResolverInterface $stockResolver;

    private $group    = false;
    private $products = false;
    private array $categoryCollections  = [];
    private ?array $assignedSourceCodes = null;
    private ?int   $msiStockId          = null;

    public function __construct(
        Context $context,
        Registry $registry,
        GroupFactory $groupFactory,
        ProductCollectionFactory $productCollectionFactory,
        Visibility $visibility,
        CategoryRepository $categoryRepository,
        UrlHelper $urlHelper,
        GetSourceItemsBySkuInterface $getSourceItemsBySku,
        GetSourcesAssignedToStockOrderedByPriorityInterface $getSourcesAssignedToStock,
        StockResolverInterface $stockResolver,
        array $data = []
    ) {
        $this->registry                  = $registry;
        $this->groupFactory              = $groupFactory;
        $this->productCollectionFactory  = $productCollectionFactory;
        $this->visibility                = $visibility;
        $this->categoryRepository        = $categoryRepository;
        $this->urlHelper                 = $urlHelper;
        $this->getSourceItemsBySku       = $getSourceItemsBySku;
        $this->getSourcesAssignedToStock = $getSourcesAssignedToStock;
        $this->stockResolver             = $stockResolver;
        parent::__construct($context, $data);
    }

    protected function _construct()
    {
        parent::_construct();
        $this->setTemplate('Autogedal_BlogInternalLinking::linking_group.phtml');
    }

    public function getAddToCartPostParams(\Magento\Catalog\Model\Product $product)
    {
        $url = $this->getAddToCartUrl($product);
        return [
            'action' => $url,
            'data'   => [
                'product' => $product->getEntityId(),
                \Magento\Framework\App\Action\Action::PARAM_NAME_URL_ENCODED => $this->urlHelper->getEncodedUrl($url),
            ],
        ];
    }

    public function getGroup()
    {
        if ($this->group !== false) {
            return $this->group;
        }

        $post = $this->registry->registry('tm_blog_post');
        if (!$post) {
            $this->group = null;
            return null;
        }

        $groupId = (int)$post->getData('linking_group_id');
        if (!$groupId) {
            $this->group = null;
            return null;
        }

        $group = $this->groupFactory->create()->load($groupId);
        $this->group = $group->getId() ? $group : null;
        return $this->group;
    }

    public function getProducts()
    {
        if ($this->products !== false) {
            return $this->products;
        }

        $group = $this->getGroup();
        if (!$group) {
            $this->products = [];
            return [];
        }
        if (!(bool)$group->getShowSlider()) {
            $this->products = [];
            return [];
        }

        $categoryIds = array_values(array_unique(array_filter(
            array_map('intval', explode(',', (string)$group->getCarouselCategoryId()))
        )));
        if ($categoryIds === []) {
            $this->products = [];
            return [];
        }

        $storeId = $this->_storeManager->getStore()->getId();
        $limit   = max(1, (int)$group->getCarouselProductsLimit() ?: 6);

        // Resolve MSI stock and its assigned sources once per request.
        $stockId             = $this->resolveStockId($storeId);
        $assignedSourceCodes = $this->resolveAssignedSourceCodes($stockId);

        // Fix #2: fetch $limit per category (not limit/catCount) so deduplication
        // always has enough candidates to fill the carousel.
        $buckets = [];
        foreach ($categoryIds as $catId) {
            if (!isset($this->categoryCollections[$catId])) {
                $this->categoryCollections[$catId] = $this->buildCollection($storeId, $catId, $limit);
            }
            $col = $this->categoryCollections[$catId];
            if (!$col) {
                continue;
            }
            // Fix #1: MSI-native stock check — sum physical qty from source items
            // assigned to the current stock, excluding backorder/la-comandă products
            // (qty=0 even if is_in_stock=1).
            $items = array_values(array_filter(
                array_values($col->getItems()),
                fn($p) => $this->hasPhysicalStock($p->getSku(), $assignedSourceCodes)
            ));
            $buckets[] = $items;
        }

        if (empty($buckets)) {
            $this->products = [];
            return [];
        }

        // Round-robin interleave with deduplication
        $seen   = [];
        $merged = [];
        $maxLen = max(array_map('count', $buckets));
        for ($i = 0; $i < $maxLen; $i++) {
            foreach ($buckets as $bucket) {
                if (isset($bucket[$i])) {
                    $productId = (int)$bucket[$i]->getId();
                    if (isset($seen[$productId])) {
                        continue;
                    }
                    $seen[$productId] = true;
                    $merged[] = $bucket[$i];
                }
            }
        }

        // Fill gaps left by deduplication
        if (count($merged) < $limit) {
            foreach ($buckets as $bucket) {
                foreach ($bucket as $product) {
                    $productId = (int)$product->getId();
                    if (isset($seen[$productId])) {
                        continue;
                    }
                    $seen[$productId] = true;
                    $merged[] = $product;
                    if (count($merged) >= $limit) {
                        break 2;
                    }
                }
            }
        }

        $this->products = array_slice($merged, 0, $limit);
        return $this->products;
    }

    private function hasPhysicalStock(string $sku, array $assignedSourceCodes): bool
    {
        foreach ($this->getSourceItemsBySku->execute($sku) as $item) {
            if (
                in_array($item->getSourceCode(), $assignedSourceCodes, true)
                && $item->getStatus() === SourceItemInterface::STATUS_IN_STOCK
                && $item->getQuantity() > 0
            ) {
                return true;
            }
        }
        return false;
    }

    private function resolveStockId(int $storeId): int
    {
        if ($this->msiStockId !== null) {
            return $this->msiStockId;
        }
        $websiteId   = $this->_storeManager->getStore($storeId)->getWebsiteId();
        $websiteCode = $this->_storeManager->getWebsite($websiteId)->getCode();
        $this->msiStockId = (int) $this->stockResolver
            ->execute(SalesChannelInterface::TYPE_WEBSITE, $websiteCode)
            ->getStockId();
        return $this->msiStockId;
    }

    private function resolveAssignedSourceCodes(int $stockId): array
    {
        if ($this->assignedSourceCodes !== null) {
            return $this->assignedSourceCodes;
        }
        $sources = $this->getSourcesAssignedToStock->execute($stockId);
        $this->assignedSourceCodes = array_map(
            fn($source) => $source->getSourceCode(),
            $sources
        );
        return $this->assignedSourceCodes;
    }

    private function buildCollection(int $storeId, int $categoryId, int $limit)
    {
        try {
            $category = $this->categoryRepository->get($categoryId, $storeId);
        } catch (\Magento\Framework\Exception\NoSuchEntityException) {
            return null;
        }

        $collection = $this->productCollectionFactory->create();
        $collection->addCategoryFilter($category);
        // Fix #3: added sku_fz and termen_livrare required by the template
        $collection->addAttributeToSelect([
            'name', 'price', 'special_price',
            'small_image', 'image', 'thumbnail',
            'url_key', 'url_path',
            'news_from_date', 'news_to_date',
            'sku_fz', 'termen_livrare',
        ]);
        $collection->addAttributeToFilter('status', ['eq' => Status::STATUS_ENABLED]);
        $collection->addAttributeToFilter('visibility', ['in' => $this->visibility->getVisibleInSiteIds()]);
        $collection->addStoreFilter($storeId);
        $collection->addPriceData();
        $collection->addUrlRewrite();

        $reviewTable = $collection->getResource()->getTable('review_entity_summary');
        $collection->getSelect()->joinLeft(
            ['rev' => $reviewTable],
            'rev.entity_pk_value = e.entity_id AND rev.entity_type = 1 AND rev.store_id = ' . $storeId,
            ['reviews_count', 'rating_summary']
        )->order('rev.reviews_count DESC');

        // Fetch extra to account for stock filtering after collection load
        $collection->setPageSize($limit * 2);

        return $collection;
    }
}
