<?php

declare(strict_types=1);

namespace FiloBlu\Refilo\Helper\Catalog;

use FiloBlu\EvaAttributes\Model\ProductAttribute;
use Magento\Catalog\Model\ResourceModel\Category\CollectionFactory as CategoryCollectionFactory;
use Magento\Catalog\Model\ResourceModel\Product\Collection;
use Magento\CatalogInventory\Api\Data\StockItemInterfaceFactory;
use Magento\CatalogInventory\Api\StockItemCriteriaInterfaceFactory;

use Magento\Framework\Exception\LocalizedException;
use function array_key_exists;

use Exception;
use FiloBlu\Refilo\Remote\Entity\Product;
use FiloBlu\Refilo\Remote\Entity\Provider\CategoriesProductProvider;
use FiloBlu\Refilo\Remote\Entity\Provider\PriceProvider;
use FiloBlu\Refilo\Remote\Entity\Provider\StockProvider;

use function in_array;

use Magento\Catalog\Api\Data\ProductInterface;
use Magento\Catalog\Model\Product\Attribute\Source\Status as AttributeStatus;
use Magento\Catalog\Model\Product\Visibility;
use Magento\Catalog\Model\ResourceModel\Product\CollectionFactory;
use Magento\Catalog\Model\ResourceModel\Product\Gallery;
use Magento\CatalogInventory\Api\Data\StockItemInterface;
use Magento\CatalogInventory\Api\StockItemRepositoryInterface;
use Magento\CatalogInventory\Helper\Stock;
use Magento\ConfigurableProduct\Model\Product\Type\Configurable;
use Magento\Framework\App\Config\ScopeConfigInterface;
use Magento\Framework\App\ResourceConnection;
use Magento\Framework\EntityManager\MetadataPool;
use Magento\Framework\Exception\NoSuchEntityException;
use Magento\Store\Model\ScopeInterface;
use Magento\Store\Model\StoreManagerInterface;

/**
 * Class ProductHelper
 * @package FiloBlu\Refilo\Helper\Catalog
 */
class ProductHelper
{
    /** @var string */
    public const VIDEO_MEDIA_TYPE = 'external-video';

    /** @var string */
    public const XML_REFILO_CATALOG_GENERAL_HIDE_PRICE = 'refilo_catalog/general/hide_price';

    /** @var string */
    public const XML_REFILO_CATALOG_GENERAL_DISABLE_SHOP = 'refilo_catalog/general/disable_shop';

    /**
     * @var array
     */
    protected $prices = null;

    /**
     * @var array
     */
    protected $stocks = null;

    /**
     * @var array
     */
    protected $categoriesProduct = null;

    /**
     * @var StockProvider
     */
    private $stockProvider;

    /**
     * @var PriceProvider
     */
    private $priceProvider;

    /**
     * @var StoreManagerInterface
     */
    private $storeManager;

    /**
     * @var CategoriesProductProvider
     */
    private $categoriesProductProvider;

    /**
     * @var CollectionFactory
     */
    private $productCollectionFactory;

    /**
     * @var ResourceConnection
     */
    private $resourceConnection;

    /**
     * @var MetadataPool
     */
    private $metadataPool;

    /**
     * @var Stock
     */
    private $stockHelper;

    /**
     * @var array|null
     */
    private $websiteLookup = [];

    /**
     * @var ProductAttributeHelper
     */
    private $productAttributeHelper;

    /**
     * @var Gallery
     */
    private $gallery;
    /**
     * @var \Magento\Framework\App\Config\ScopeConfigInterface
     */
    private $scopeConfig;

    /**
     * @var StockItemRepositoryInterface
     */
    private $stockItemRepository;
    /**
     * @var \Magento\CatalogInventory\Api\StockItemCriteriaInterfaceFactory
     */
    private $stockItemCriteriaFactory;
    /**
     * @var \Magento\CatalogInventory\Api\Data\StockItemInterfaceFactory
     */
    private $stockItemFactory;


    private $connection;

    /**
     * @var CategoryCollectionFactory
     */
    private $categoryCollectionFactory;
    /**
     * @var array
     */
    private $categoryCache = null;

    /**
     * ProductHelper constructor.
     * @param StoreManagerInterface $storeManager
     * @param StockProvider $stockProvider
     * @param PriceProvider $priceProvider
     * @param CategoriesProductProvider $categoriesProductProvider
     * @param CollectionFactory $productCollectionFactory
     * @param ResourceConnection $resourceConnection
     * @param MetadataPool $metadataPool
     * @param ProductAttributeHelper $productAttributeHelper
     * @param Gallery $gallery
     * @param Stock $stock
     * @param ScopeConfigInterface $scopeConfig
     * @param StockItemRepositoryInterface $stockItemRepository
     * @param StockItemCriteriaInterfaceFactory $stockItemCriteriaFactory
     * @param StockItemInterfaceFactory $stockItemFactory
     * @param CategoryCollectionFactory $categoryCollectionFactory
     */
    public function __construct(
        StoreManagerInterface $storeManager,
        StockProvider $stockProvider,
        PriceProvider $priceProvider,
        CategoriesProductProvider $categoriesProductProvider,
        CollectionFactory $productCollectionFactory,
        ResourceConnection $resourceConnection,
        MetadataPool $metadataPool,
        ProductAttributeHelper $productAttributeHelper,
        Gallery $gallery,
        Stock $stock,
        ScopeConfigInterface $scopeConfig,
        StockItemRepositoryInterface $stockItemRepository,
        StockItemCriteriaInterfaceFactory $stockItemCriteriaFactory,
        StockItemInterfaceFactory $stockItemFactory,
        CategoryCollectionFactory $categoryCollectionFactory
    ) {
        $this->stockProvider = $stockProvider;
        $this->priceProvider = $priceProvider;
        $this->storeManager = $storeManager;
        $this->categoriesProductProvider = $categoriesProductProvider;
        $this->productCollectionFactory = $productCollectionFactory;
        $this->resourceConnection = $resourceConnection;
        $this->metadataPool = $metadataPool;
        $this->stockHelper = $stock;
        $this->productAttributeHelper = $productAttributeHelper;
        $this->gallery = $gallery;
        $this->scopeConfig = $scopeConfig;
        $this->stockItemRepository = $stockItemRepository;
        $this->stockItemCriteriaFactory = $stockItemCriteriaFactory;
        $this->stockItemFactory = $stockItemFactory;
        $this->categoryCollectionFactory = $categoryCollectionFactory;
    }

    /**
     * @param ProductInterface $product
     * @return mixed
     * @throws NoSuchEntityException
     */
    public function getStockFor(ProductInterface $product)
    {
        $storeId = (int)$product->getStoreId();
        $entityId = $product->getEntityId();

        if ($this->stocks === null || !isset($this->stocks[$storeId])) {
            $store = $this->storeManager->getStore($storeId);
            foreach (iterator_to_array($this->stockProvider->forStore($store)) as $stock) {
                $this->stocks[$storeId][$stock->getId()] = $stock->getData(Product::INVENTORY);
            }
        }

        return $this->stocks[$storeId][$entityId] ?? [];
    }

    /**
     * @return \Magento\Framework\DB\Adapter\AdapterInterface
     */
    public function getConnection(){

        if($this->connection === null){
            $this->connection = $this->resourceConnection->getConnection();
        }

        return $this->connection;
    }

    /**
     * @param ProductInterface $product
     * @return mixed
     * @throws NoSuchEntityException
     */
    public function getPricesFor(ProductInterface $product)
    {
        $storeId = (int)$product->getStoreId();
        $entityId = (int)$product->getEntityId();

        if ($this->prices === null || !isset($this->prices[$storeId])) {
            $store = $this->storeManager->getStore($storeId);
            foreach (iterator_to_array($this->priceProvider->forStore($store)) as $price) {
                $this->prices[$storeId][(int)$price->getId()] = $price->getData();
            }
        }

        return $this->prices[$storeId][$entityId] ?? [];
    }

    /**
     * @param ProductInterface $product
     * @return mixed
     * @throws NoSuchEntityException
     */
    public function getFinalPricesFor(ProductInterface $product)
    {
        $storeId = (int)$product->getStoreId();
        $entityId = (int)$product->getEntityId();

        if ($this->prices === null || !isset($this->prices[$storeId])) {
            $store = $this->storeManager->getStore($storeId);
            foreach (iterator_to_array($this->priceProvider->forStore($store)) as $price) {
                foreach ($price->getData() as $key => $value) {
                    if ($key === 'id' || !isset($value['final_price'])) {
                        continue;
                    }
                    $this->prices[$storeId][(int)$price->getId()][$key]['final_price'] = $value['final_price'];
                }
            }
        }

        return $this->prices[$storeId][$entityId] ?? [];
    }

    /**
     * @param ProductInterface|\Magento\Catalog\Model\Product $product
     * @return array|mixed
     * @throws NoSuchEntityException
     */
    public function getCategoriesFor(ProductInterface $product)
    {
        $storeId = (int)$product->getStoreId();
        $entityId = (int)$product->getEntityId();

        if ($this->categoriesProduct === null || !isset($this->categoriesProduct[$storeId])) {
            $store = $this->storeManager->getStore($storeId);
            /** @var \FiloBlu\Refilo\Remote\Entity\CategoriesProduct $categoriesProduct */
            foreach (iterator_to_array($this->categoriesProductProvider->forStore($store)) as $categoriesProduct) {
                // NOTE: $categoriesProduct->getId() is product->entityId
                $this->categoriesProduct[$storeId][$categoriesProduct->getId()] = $categoriesProduct->getData();
            }
        }

        return $this->categoriesProduct[$storeId][$entityId] ?? [];
    }

    /**
     * TODO: Move in some dedicated scoped class
     * @param ProductInterface|\Magento\Catalog\Model\Product $product
     * @throws \Magento\Framework\Exception\NoSuchEntityException
     * @throws \Magento\Framework\Exception\LocalizedException
     */
    public function getCategories(ProductInterface $product): array
    {
        $storeId = (int)$product->getStoreId();

        if (!isset($this->categoryCache[$storeId])) {
            $oldStore = $this->storeManager->getStore();
            $store = $this->storeManager->getStore($storeId);
            $this->storeManager->setCurrentStore($store);

            $items = $this->categoryCollectionFactory->create()
                                                     ->addAttributeToSelect('is_active')
                                                     ->addAttributeToFilter('level', ['gteq' => 2])
                                                     ->addNameToResult()
                                                     ->setStore($storeId)
                                                     ->getItems();

            foreach ($items as $item) {
                $this->categoryCache[$storeId][$item->getId()] = $item;
            }

            $this->storeManager->setCurrentStore($oldStore);
        }

        $categories = [];

        foreach ($product->getCategoryIds() as $categoryId) {

            if (!isset($this->categoryCache[$storeId][$categoryId])) {
                continue;
            }
            $categories[] = $this->categoryCache[$storeId][$categoryId];
        }

       return $categories;
    }

    /**
     * @param ProductInterface $product
     * @return array
     * @throws LocalizedException
     */
    public function getCategoriesNameFor(ProductInterface $product): array {

        $categoryNames = [];
        foreach ($this->getCategories($product) as $category) {
            $name = $category->getName();
            $categoryNames[$name] = $name;
        }

        return array_values($categoryNames);
    }

    /**
     * @param \Magento\Catalog\Model\Product $product
     * @param $usedAttributes
     * @param $extraAttribute
     * @return array|DataObject[]
     */
    public function getUsedProducts(\Magento\Catalog\Model\Product $product, $usedAttributes, $extraAttribute): array
    {
        if ($product->getTypeId() !== Configurable::TYPE_CODE) {
            return [];
        }

        $attributeToSelect = $extraAttribute;
        $attributeToSelect[] = 'status';
        foreach ($usedAttributes as $attribute) {
            $attributeToSelect[] = $attribute['attribute_code'];
        }

      //  $attributeToSelect = array_merge($attributeToSelect, $extraAttribute);

        $attributeToSelect = array_unique($attributeToSelect);

        $childrenIds = $product->getTypeInstance()->getChildrenIds($product->getId());

        if (isset($childrenIds[0]) && empty($childrenIds[0])) {
            $childrenIds = [0];
        }

        $childrenCollection = $this->productCollectionFactory->create()
            ->addAttributeToSelect($attributeToSelect, true)
            ->addStoreFilter($product->getStoreId())
            ->addFieldToFilter('entity_id', [
                'in' => $childrenIds
            ])->getItems();


        foreach ($childrenCollection as $child) {
            /** @var \Magento\Catalog\Model\Product $child */
            $this->stockHelper->assignStatusToProduct($child);
        }

        return $childrenCollection;
    }

    /**
     * @param \Magento\Catalog\Model\Product $product
     * @return array
     * @throws Exception
     */
    public function getUsedProductAttributes(\Magento\Catalog\Model\Product $product): array
    {
        $metadata = $this->metadataPool->getMetadata(ProductInterface::class);
        $linkField = $metadata->getLinkField();

        $connection = $this->getConnection();
        $select = $connection->select()->distinct(
            true
        )->from(
            ['cpsa' => $connection->getTableName('catalog_product_super_attribute')],
            null
        )->joinInner(
            ['cpe' => $connection->getTableName('catalog_product_entity')],
            "cpsa.product_id = cpe.{$linkField}",
            []
        )->joinInner(
            ['ea' => $connection->getTableName('eav_attribute')],
            'cpsa.attribute_id = ea.attribute_id',
            ['ea.attribute_code']
        )->where(
            'cpe.entity_id = :entity_id'
        );

        return $connection->fetchAll($select, ['entity_id' => $product->getEntityId()]) ?? [];
    }

    /**
     * @param $productId
     * @param $websiteId
     * @return bool
     */
    public function productBelongsTo($productId, $websiteId): bool
    {
        $websiteId = (int)$websiteId;

        if (!isset($this->websiteLookup[$websiteId])) {
            $this->websiteLookup[$websiteId] = [];
            $connection = $this->getConnection();

            $select = $connection->select()
                ->from($connection->getTableName('catalog_product_website'), ['product_id', 'product_id'])
                ->where('website_id = :website_id');

            $this->websiteLookup[$websiteId] = $connection->fetchPairs($select, ['website_id' => $websiteId]);
        }

        return array_key_exists($productId, $this->websiteLookup[$websiteId]);
    }

    /**
     * @param ProductInterface|\Magento\Catalog\Model\Product $magentoProduct
     * @return bool
     */
    public function isProductVisible($magentoProduct): bool
    {
        switch ((int)$magentoProduct->getVisibility()) {
            case Visibility::VISIBILITY_IN_CATALOG:
            case Visibility::VISIBILITY_BOTH:
                return true;
        }

        return false;
    }

    /**
     * @param ProductInterface|\Magento\Catalog\Model\Product $magentoProduct
     * @return bool
     */
    public function isProductVisibleInSearch($magentoProduct): bool
    {
        switch ((int)$magentoProduct->getVisibility()) {
            case Visibility::VISIBILITY_IN_SEARCH:
            case Visibility::VISIBILITY_BOTH:
                return true;
        }

        return false;
    }

    /**
     * @param \Magento\Catalog\Model\Product|ProductInterface $magentoProduct
     * @return string
     */
    public function isEnabled($magentoProduct): string
    {
        return ((int)$magentoProduct->getStatus() === AttributeStatus::STATUS_ENABLED) ? '1' : '0';
    }

    /**
     * @param \Magento\Catalog\Model\Product|ProductInterface $magentoProduct
     * @return array
     * @throws \Magento\Framework\Exception\NoSuchEntityException
     */
    public function getAssets($magentoProduct): array
    {
        $assets = [
            'placeholders' => [],
            'images'       => [],
            'galleries'    => []
        ];

        $gallery = $magentoProduct->getData('media_gallery');
        $linkField = $this->metadataPool->getMetadata(ProductInterface::class)->getLinkField();
        foreach ($gallery['images'] ?? [] as $image) {
            $hide_from_gallery = false;
            if (isset($image['disabled']) && (int)$image['disabled'] === 1) {
                $hide_from_gallery = true;
            }

            // Get the role, only if it's a video
            // This will work also on images, if needed
            $asset_roles = [];
            if ($image['media_type'] === self::VIDEO_MEDIA_TYPE) {
                $asset_roles = $this->getAssetRoles($image[$linkField], $image['value_id'], $linkField);
            }

            $assets['images'][] = [
                'hide_from_gallery' => $hide_from_gallery,
                'label'             => $image['label'] ?? null, // TODO : remove?
                'alt'               => $image['label'] ?? null,
                'file'              => ($image['file']) ? "/catalog/product{$image['file']}" : null,
                'media_type'        => $image['media_type'] ?? null,
                'video_url'         => $image['video_url'] ?? null,
                'video_title'       => $image['video_title'] ?? null,
                'video_description' => $image['video_description'] ?? null,
                'video_metadata'    => $image['video_metadata'] ?? null,
                'video_provider'    => $image['video_provider'] ?? null,
                'video_roles'       => $asset_roles,
            ];
        }

        $assets['placeholders'] = $this->productAttributeHelper->getImagePlaceholdersAttributes($magentoProduct);

        $galleryAttributes = array_filter($magentoProduct->getAttributes(), static function ($value) {
            return $value->getFrontendInput() === 'gallery' && !in_array(
                $value->getAttributeCode(),
                ['gallery', 'media_gallery']
            );
        });

        foreach ($galleryAttributes as $galleryAttribute) {
            $extraGallery = $this->gallery
                ->loadProductGalleryByAttributeId($magentoProduct, $galleryAttribute->getAttributeId());

            foreach ($extraGallery as $image) {
                $hide_from_gallery = false;
                if (isset($image['disabled']) && (int)$image['disabled'] === 1) {
                    $hide_from_gallery = true;
                }

                $assets['galleries'][$galleryAttribute->getAttributeCode()][] = [
                    'hide_from_gallery' => $hide_from_gallery,
                    'label'             => $image['label'] ?? null, // TODO : remove?
                    'alt'               => $image['label'] ?? null,
                    'file'              => ($image['file']) ? "/catalog/product{$image['file']}" : null,
                    'media_type'        => $image['media_type'] ?? null,
                    'video_url'         => $image['video_url'] ?? null,
                    'video_title'       => $image['video_title'] ?? null,
                    'video_description' => $image['video_description'] ?? null,
                    'video_metadata'    => $image['video_metadata'] ?? null,
                    'video_provider'    => $image['video_provider'] ?? null,
                ];
            }
        }

        return $assets;
    }

    /**
     * @param $productId
     * @param $assetValueId
     * @param string $linkField 'row_id' or 'entity_id'
     * @return array
     * This function will try to read the roles of an asset
     * Magento does not have a direct link between assets and eav_attributes, the join is made on the 'path' of the asset
     */
    public function getAssetRoles($productId, $assetValueId, $linkField)
    {
        $sql = "SELECT TMP.record_id
                ,ANY_VALUE(TMP.value_id) AS 'value_id'
                ,ANY_VALUE(TMP.store_id) AS 'store_id'
                ,ANY_VALUE(TMP.position) AS 'position'
                ,ANY_VALUE(TMP.disabled) AS 'disabled'
                ,ANY_VALUE(TMP.$linkField) AS 'product_row_id'
                ,ANY_VALUE(TMP.media_type) AS 'media_type'
                ,ANY_VALUE(TMP.value) AS 'media_path'
                ,ANY_VALUE(TMP.url) AS 'video_url'
                ,ANY_VALUE(TMP.title) AS 'video_title'
                ,ANY_VALUE(TMP.description) AS 'video_description'
                ,GROUP_CONCAT(TMP.media_role) AS 'media_roles'
                ,GROUP_CONCAT(TMP.media_role_id) AS 'media_roles_ids'
                FROM (
                    SELECT DISTINCT CPEMGV.*
                    ,CPEMG.media_type
                    ,CPEMG.value
                    ,CPEMGVV.url
                    ,CPEMGVV.title
                    ,CPEMGVV.description
                    ,EA.attribute_code AS 'media_role'
                    ,EA.attribute_id AS 'media_role_id'
                    FROM catalog_product_entity_media_gallery_value CPEMGV
                    LEFT JOIN catalog_product_entity_media_gallery CPEMG ON CPEMG.value_id = CPEMGV.value_id
                    LEFT JOIN catalog_product_entity_media_gallery_value_video CPEMGVV ON CPEMGVV.value_id = CPEMG.value_id

                    LEFT JOIN catalog_product_entity_varchar CPEV
                    ON CPEV.store_id = CPEMGV.store_id
                    AND CPEV.$linkField = CPEMGV.$linkField
                    AND CPEV.value = CPEMG.value
                    AND CPEV.attribute_id IN (SELECT attribute_id FROM eav_attribute WHERE entity_type_id = 4 AND frontend_input IN ('media_image') AND backend_type = 'varchar')

                    LEFT JOIN eav_attribute EA ON EA.attribute_id = CPEV.attribute_id

                    WHERE CPEMGV.$linkField = {$productId}
                    AND CPEMGV.store_id IN (0,1)
                    AND CPEMGV.value_id = {$assetValueId}
                    AND CPEV.value_id IS NOT NULL
                    AND EA.attribute_code IS NOT NULL

                ) TMP

                GROUP BY TMP.record_id
                ORDER BY TMP.store_id DESC
                LIMIT 1";
        $connection = $this->resourceConnection->getConnection();
        try {
            $result = ($connection->query($sql))->fetch();
        } catch (Exception $e) {
            return [];
        }
        if (!$result) {
            return [];
        }
        if (!$result['media_roles']) {
            return [];
        }
        return explode(',', $result['media_roles']);
    }

    /**
     * @return ProductAttributeHelper
     */
    public function getProductAttributeHelper(): ProductAttributeHelper
    {
        return $this->productAttributeHelper;
    }

    /**
     * @param ProductInterface $product
     * @return bool
     */
    public function getIsSalable(ProductInterface $product): bool
    {
        $disableShop = $this->scopeConfig->isSetFlag(
            self::XML_REFILO_CATALOG_GENERAL_DISABLE_SHOP,
            ScopeInterface::SCOPE_STORE,
            $product->getStoreId()
        );

        return !($disableShop || $product->getData('disable_shop'));
    }

    /**
     * @param ProductInterface $product
     * @return bool
     */
    public function getHidePrice(ProductInterface $product): bool
    {
        $hidePrice = $this->scopeConfig->isSetFlag(
            self::XML_REFILO_CATALOG_GENERAL_HIDE_PRICE,
            ScopeInterface::SCOPE_STORE,
            $product->getStoreId()
        );

        if ($hidePrice) {
            return false;
        }

        return (bool)$product->getData('hide_price');
    }

    /**
     * @param ProductInterface $product
     * @return StockItemInterface
     * @throws \Magento\Framework\Exception\LocalizedException
     */
    public function getStockConfiguration(ProductInterface $product)
    {
        $searchCriteria = $this->stockItemCriteriaFactory->create();

        $searchCriteria->addFilter(
            StockItemInterface::PRODUCT_ID,
            StockItemInterface::PRODUCT_ID,
            $product->getId()
        );

        // Stock::DEFAULT_STOCK_ID is used until we have proper multi-stock item configuration
        $searchCriteria->addFilter(
            StockItemInterface::STOCK_ID,
            StockItemInterface::STOCK_ID,
            \Magento\CatalogInventory\Model\Stock::DEFAULT_STOCK_ID
        );
        $searchCriteria->setLimit(1, 1);

        $stockItemCollection = $this->stockItemRepository->getList($searchCriteria);

        $stockItems = $stockItemCollection->getItems();
        $stockItem = reset($stockItems);

        if ($stockItem) {
            return $stockItem;
        }

        return $this->stockItemFactory->create(['data' => [
            Product::MIN_QTY => 1,
            Product::MAX_QTY => 9999,
            Product::STEP    => 1
        ]]);
    }

    /**
     * @param array $childrenIds
     * @return Collection
     * @throws NoSuchEntityException
     */
    public function getEnabledProduct(array $childrenIds)
    {
        return $this->productCollectionFactory->create()
            ->setStore($this->storeManager->getStore())
            ->addIdFilter($childrenIds)
            ->addAttributeToSelect(ProductInterface::STATUS)
            ->addAttributeToFilter(ProductInterface::STATUS, AttributeStatus::STATUS_ENABLED)
            ->addStoreFilter();
    }


    /**
     * Releases the resources held by this helper.
     * This is useful for cleaning up memory when the helper is no longer needed.
     */
    public function release(): void
    {
        $this->stocks = null;
        $this->categoryCache = null;
        $this->prices = null;
    }
}
