<?php

namespace Leadlion\AutoRelated\Cron;

use Magento\Catalog\Api\ProductRepositoryInterface;
use Magento\Catalog\Model\Product\Visibility;
use Magento\Framework\Api\SearchCriteriaBuilder;
use Leadlion\AutoRelated\Helper\Data;
use Magento\Catalog\Model\Product\Link;
use Leadlion\AutoRelated\Model\Collection as AutoRelCollection;
use Magento\Catalog\Api\Data\ProductLinkInterfaceFactory as ProductLinkFactory;
use Magento\Store\Model\App\Emulation;
use Magento\Store\Model\StoreManagerInterface;
use Leadlion\AutoRelated\Logger\Logger;
use Magento\Framework\App\ResourceConnection;

class GenerateAutoRelated
{
    const LINK_TYPE_RELATED = 'related';
    const LINK_TYPE_UPSELL = 'upsell';

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

    /**
     * @var Logger
     */
    protected $logger;

    /**
     * @var AutoRelCollection
     */
    protected $autoRelCollection;

    /**
     * @var Data
     */
    protected $customHelper;

    /**
     * @var ProductRepositoryInterface
     */
    protected $productRepository;

    /**
     * @var SearchCriteriaBuilder
     */
    protected $criteriaBuilder;

    /**
     * @var Visibility
     */
    protected $productVisibility;

    /**
     * Product link instance
     *
     * @var Link
     */
    protected $linkInstance;
    
    /**
     * @var ProductLinkFactory
     * @since 101.0.0
     */
    protected $productLinkFactory;
    protected $emulation;

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

    public function __construct(
        ProductRepositoryInterface $productRepository,
        SearchCriteriaBuilder      $criteriaBuilder,
        Visibility                 $productVisibility,
        Data                       $customHelper,
        Link                       $linkInstance,
        AutoRelCollection          $autoRelCollection,
        ProductLinkFactory         $productLinkFactory,
        Logger                     $logger,
        Emulation                  $emulation,
        StoreManagerInterface      $storeManager,
        ResourceConnection $resource
    ) {
        $this->resource = $resource;
        $this->productRepository = $productRepository;
        $this->criteriaBuilder = $criteriaBuilder;
        $this->productVisibility = $productVisibility;
        $this->customHelper = $customHelper;
        $this->linkInstance = $linkInstance;
        $this->autoRelCollection = $autoRelCollection;
        $this->productLinkFactory = $productLinkFactory;
        $this->logger = $logger;
        $this->emulation = $emulation;
        $this->storeManager = $storeManager;
    }

    public function execute()
    {
        $this->emulation->startEnvironmentEmulation(1, \Magento\Framework\App\Area::AREA_FRONTEND, true);

        $pageSize = 200;
        $currentPage = 1;

        do {
            $websiteProducts = $this->getWebsiteProducts($currentPage, $pageSize);
            $totalCount = count($websiteProducts);

            foreach ($websiteProducts as $product) {
                try {
                if ($product->getVisibility() == \Magento\Catalog\Model\Product\Visibility::VISIBILITY_NOT_VISIBLE) {
                    continue;
                }
                $productLinks = $product->getProductLinks();
                $linkTypes = [];
                $addRelated = true;
                $addUpsell = true;
                foreach ($productLinks as $key => $productLink) {
                    if ($productLink->getLinkType() == self::LINK_TYPE_RELATED) {
                        $addRelated = false;
                    }
                    if ($productLink->getLinkType() == self::LINK_TYPE_UPSELL) {
                        $addUpsell = false;
                    }
                }

                if ($addRelated) {
                    $this->logger->info('Adding Related Products for product with sku '.$product->getSku());
                    $linkTypes[self::LINK_TYPE_RELATED] = $this->getRelated($product);
                }
                if ($addUpsell) {
                    $this->logger->info('Adding Upsell Products for product with sku '.$product->getSku());
                    $linkTypes[self::LINK_TYPE_UPSELL] = $this->getUpsell($product);
                }

                if (isset($linkTypes[self::LINK_TYPE_RELATED]) && !empty($linkTypes[self::LINK_TYPE_RELATED])) {
                    $connection = $this->resource->getConnection();
                    $catalogProductLink = $this->resource->getTableName('catalog_product_link');
                    $catalogProductLinkAttrInt = $this->resource->getTableName('catalog_product_link_attribute_int');

                    foreach ($linkTypes[self::LINK_TYPE_RELATED] as $link) {
                        $bind = [
                            'product_id' => (int)$product->getId(),
                            'linked_product_id' => $this->getIdBySku($link->getLinkedProductSku()),
                            'link_type_id' => 1
                        ];
                        $connection->insert($catalogProductLink, $bind);

                        $bind = [
                            [
                                'product_link_attribute_id' => 1,
                                'link_id' => $connection->lastInsertId($catalogProductLink),
                                'value' => $link->getPosition()
                            ],
                            [
                                'product_link_attribute_id' => 6,
                                'link_id' => $connection->lastInsertId($catalogProductLink),
                                'value' => 1
                            ]
                        ];
                        $connection->insertMultiple($catalogProductLinkAttrInt, $bind);
                    }
                }

                if (isset($linkTypes[self::LINK_TYPE_UPSELL]) && !empty($linkTypes[self::LINK_TYPE_UPSELL])) {
                    $connection = $this->resource->getConnection();
                    $catalogProductLink = $this->resource->getTableName('catalog_product_link');
                    $catalogProductLinkAttrInt = $this->resource->getTableName('catalog_product_link_attribute_int');

                    foreach ($linkTypes[self::LINK_TYPE_UPSELL] as $link) {
                        $bind = [
                            'product_id' => (int)$product->getId(),
                            'linked_product_id' => $this->getIdBySku($link->getLinkedProductSku()),
                            'link_type_id' => 4
                        ];
                        $connection->insert($catalogProductLink, $bind);

                        $bind = [
                            [
                                'product_link_attribute_id' => 4,
                                'link_id' => $connection->lastInsertId($catalogProductLink),
                                'value' => $link->getPosition()
                            ],
                            [
                                'product_link_attribute_id' => 7,
                                'link_id' => $connection->lastInsertId($catalogProductLink),
                                'value' => 1
                            ]
                        ];
                        $connection->insertMultiple($catalogProductLinkAttrInt, $bind);
                    }
                }

                    $this->logger->info('-------------------------------------------');
                } catch (\Exception $e) {
                    $this->logger->info('ERROR for sku ' . $product->getSku() . ': ' . $e->getMessage());
                }
            }

            $currentPage++;
        } while ($totalCount === $pageSize);

        $this->emulation->stopEnvironmentEmulation();
    }

    /**
     * Get product identifier by sku
     *
     * @param  string $sku
     * @return int|false
     */
    public function getIdBySku($sku)
    {
        $connection = $this->resource->getConnection();
        $catalogProduct = $this->resource->getTableName('catalog_product_entity');
        
        $select = $connection->select()->from($catalogProduct, 'entity_id')->where('sku = :sku');

        $bind = [':sku' => (string)$sku];

        return $connection->fetchOne($select, $bind);
    }

    protected function getRelated($product)
    {
        $relatedConfig = $this->customHelper->getRelatedDisplayType();
        $autorelProducts = $this->autoRelCollection->getAdvancedCollection(
            $product,
            self::LINK_TYPE_RELATED,
            $relatedConfig
        );
        $productLinks = [];
        $position = 0;
        $skuLogs = [];
        if ($autorelProducts) {
            foreach ($autorelProducts as $autorelProduct) {
                $skuLogs[] = $autorelProduct->getSku();
                $link = $this->productLinkFactory->create();
                $link->setSku($product->getSku())
                    ->setLinkedProductSku($autorelProduct->getSku())
                    ->setLinkType(self::LINK_TYPE_RELATED)
                    ->setPosition($position)
                    ->getExtensionAttributes()
                    ->setAutoAdded(1);
                $productLinks[] = $link;
                $position++;
            }
        }
        
        if (!empty($skuLogs)) {
            $this->logger->info(implode(',', $skuLogs));
        }
        return $productLinks;
    }

    protected function getUpsell($product)
    {
        $upsellConfig = $this->customHelper->getUpsellDisplayType();
        $autorelProducts = $this->autoRelCollection->getAdvancedCollection(
            $product,
            self::LINK_TYPE_UPSELL,
            $upsellConfig
        );
        
        $productLinks = [];
        $productsLimit = $this->customHelper->get_limit();
        $productByCategory = [];
        if ($autorelProducts) {
            foreach ($autorelProducts as $autorelProduct) {
                $catIds = $autorelProduct->getCategoryIds();
                if (!empty($catIds)) {
                    $lastCategoryId = end($catIds);
                    if ($lastCategoryId == $this->getRootCategoryId()) {
                        $lastCategoryId = prev($catIds);
                    }
                    if ($product->getAutoBody() &&
                        $autorelProduct->getAutoBody() &&
                        $product->getAutoBody() != $autorelProduct->getAutoBody()
                    ) {
                        continue;
                    }
                    $productByCategory[$lastCategoryId][] = $autorelProduct;
                }
                
            }
        }

        $finalProducts = [];
        $sortOrderUpsellCatIds = $this->getSortProdConf($product);
        if ($sortOrderUpsellCatIds) {
            $finalProducts = $this->upsellSortedProds($sortOrderUpsellCatIds, $productByCategory, $finalProducts);
        }

        if (count($finalProducts) < $productsLimit) {
            foreach ($productByCategory as $catId => $products) {
                if (($sortOrderUpsellCatIds && !in_array($catId, $sortOrderUpsellCatIds)) ||
                    !$sortOrderUpsellCatIds
                ) {
                    $counter = 0;
                    foreach ($products as $prod) {
                        if ($counter == 5) {
                            break;
                        }
                        if (count($finalProducts) == $productsLimit) {
                            break 2;
                        }
                        $finalProducts[] = $prod;
                        $counter++;
                    }
                }
            }
        }
        
        $position = 0;
        $skuLogs = [];
        foreach ($finalProducts as $autorelProduct) {
            $skuLogs[] = $autorelProduct->getSku();
            $link = $this->productLinkFactory->create();
            $link->setSku($product->getSku())
                ->setLinkedProductSku($autorelProduct->getSku())
                ->setLinkType(self::LINK_TYPE_UPSELL)
                ->setPosition($position)
                ->getExtensionAttributes()
                ->setAutoAdded(1);
            $productLinks[] = $link;
            $position++;
        }
        
        if (!empty($skuLogs)) {
            $this->logger->info(implode(',', $skuLogs));
        }

        return $productLinks;
    }

    protected function upsellSortedProds($sortOrderUpsellCatIds, $productByCategory, $finalProducts)
    {
        $productsLimit = $this->customHelper->get_limit();
        $returnProducts = true;
        foreach ($sortOrderUpsellCatIds as $sortCatUpsellId) {
            if (isset($productByCategory[$sortCatUpsellId])) {
                $returnProducts = false;
                $products = $productByCategory[$sortCatUpsellId];
                if (empty($products)) {
                    unset($productByCategory[$sortCatUpsellId]);
                }
                $counter = 0;
                foreach ($products as $key => $prod) {
                    if ($counter == 1) {
                        break;
                    }
                    if (count($finalProducts) == $productsLimit) {
                        return $finalProducts;
                    }
                    $finalProducts[] = $prod;
                    $counter++;
                    unset($productByCategory[$sortCatUpsellId][$key]);
                }
            }
        }
        if (!$returnProducts) {
            return $this->upsellSortedProds($sortOrderUpsellCatIds, $productByCategory, $finalProducts);
        }
        return $finalProducts;
    }

    protected function getSortProdConf($product)
    {
        $configuration = $this->customHelper->getUpsellSortOrder();
        $mainProdCatIds = $product->getCategoryIds();
        if (!empty($mainProdCatIds)) {
            $mainProdLastCategoryId = end($mainProdCatIds);
            if ($mainProdLastCategoryId == $this->getRootCategoryId()) {
                $mainProdLastCategoryId = prev($mainProdCatIds);
            }
            foreach ($configuration as $key => $info) {
                if ($info['category'] == $mainProdLastCategoryId) {
                    return explode(',', $info['categories_ids_sort']);
                }
            }
        }
        
        return null;
    }
    
    protected function getRootCategoryId()
    {
        return $this->storeManager->getStore()->getRootCategoryId();
    }
    
    protected function getWebsiteProducts($currentPage = 1, $pageSize = 200)
    {
        $searchCriteria = $this->criteriaBuilder
            ->addFilter('visibility', $this->productVisibility->getVisibleInSiteIds(), 'in')
            ->setPageSize($pageSize)
            ->setCurrentPage($currentPage)
            ->create();

        $products = $this->productRepository->getList($searchCriteria);

        return $products->getItems();
    }
}
