<?php

namespace FiloBlu\Flow\Model\Actions\From;

use Exception;
use Magento\Catalog\Api\Data\ProductLinkInterfaceFactory;
use Magento\Catalog\Model\Product\Type;
use Magento\ConfigurableProduct\Model\ResourceModel\Product\Type\ConfigurableFactory;
use Magento\Framework\App\ProductMetadataInterface;
use Magento\Framework\App\ResourceConnection;
use Magento\Framework\DB\Adapter\AdapterInterface;
use Zend_Db;

/**
 * Class AbstractAddLink
 * @package FiloBlu\Flow\Model\Actions\From
 */
abstract class AbstractAddLink
{
    /**
     * @var ProductLinkInterfaceFactory
     */
    protected $productLinkInterfaceFactory;

    /**
     * @var ConfigurableFactory
     */
    protected $configurableFactory;

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

    /**
     * @var ProductMetadataInterface
     */
    protected $productMetadata;

    /**
     * @var AdapterInterface
     */
    protected $connection;
    /**
     * @var array
     */
    protected $attributes = [];

    /**
     * Addcrosssell constructor.
     * @param ConfigurableFactory $configurableFactory
     * @param ResourceConnection $connection
     */
    public function __construct(
        ConfigurableFactory $configurableFactory,
        ResourceConnection $connection
    ) {
        $this->configurableFactory = $configurableFactory;
        $this->resourceConnection = $connection;
    }

    /**
     * @param $product
     * @param $value
     * @param $storeId
     * @throws Exception
     */
    public function process($product, $value, $storeId)
    {
        if (empty($value)) {
            return;
        }

        if ($storeId !== 0) {
            throw new Exception('The function is avalaible only for the ADMIN store.');
        }

        $products = $this->getTargetProducts($product);
        $productsToLink = $this->getProductsToLink($value);

        // Adding the linked product in reverse order because of:
        // https://bitbucket.org/filoblu/mod2-refilo/commits/4332e732ca6dcf9b319b38f3a529b51514648bf7
        $productsToLink = array_reverse($productsToLink);

        foreach ($products as $productId) {
            foreach ($productsToLink as $toLink) {
                $this->setLink($productId, $toLink['entity_id'], $this->getLinkType());
            }
        }
    }

    /**
     * @param $product
     * @return array
     */
    protected function getTargetProducts($product)
    {
        $products = [$product->getRowId()];

        if ($product->getTypeId() === Type::DEFAULT_TYPE) {
            $parentIds = $this->configurableFactory->create()->getParentIdsByChild($product->getId());
            return array_merge($products, $parentIds);
        }

        return $products;
    }

    /**
     * @param $value string like SA13L91E2|OR75B93E2
     * @return array
     */
    protected function getProductsToLink($value)
    {
        $connection = $this->getConnection();
        $toLink = explode('|', $value);
        // This will preserve the CSV ordering
        $toLinkQuoted = "'" . implode ( "', '", $toLink ) . "'";
        $table = $connection->getTableName('catalog_product_entity');
        $sql = $connection->select()->from($table, ['sku', 'entity_id'])
            ->where('sku IN (?)', $toLink)
            ->order(new \Zend_Db_Expr("FIELD(sku, {$toLinkQuoted})"))
            ->assemble();

        return $this->connection->fetchAll($sql, array(), Zend_Db::FETCH_ASSOC);
    }

    /**
     * @return AdapterInterface
     */
    protected function getConnection()
    {
        if ($this->connection) {
            return $this->connection;
        }
        return ($this->connection = $this->resourceConnection->getConnection());
    }

    /**
     * Create or update link
     *
     * @param $product_id int In Magento Enterprise Edition $product_id references catalog_product_entity.row_id
     * @param $linked_product_id int references catalog_product_entity.entity_id
     * @param $link_type_id int one of \Magento\Catalog\Model\Product\Link::LINK_TYPE_RELATED, \Magento\Catalog\Model\Product\Link::LINK_TYPE_CROSSSELL, \Magento\Catalog\Model\Product\Link::LINK_TYPE_UPSELL
     *
     */
    protected function setLink($product_id, $linked_product_id, $link_type_id)
    {
        /* 1] Find entry */
        $this->getLinkEntry($product_id, $linked_product_id, $link_type_id);

        /* 2] Set position*/
        $this->updatePositions($product_id, $link_type_id);
    }

    /**
     * @param $product_id
     * @param $linked_product_id
     * @param $link_type_id
     * @return array|null
     */
    protected function getLinkEntry($product_id, $linked_product_id, $link_type_id)
    {
        $connection = $this->getConnection();

        $catalogProductLinkTable = $connection->getTableName('catalog_product_link');

        $linkEntry = $connection->fetchRow(
            "SELECT * FROM {$catalogProductLinkTable} WHERE `product_id` = ? AND `linked_product_id` = ? AND `link_type_id` = ? ",
            [$product_id, $linked_product_id, $link_type_id],
            Zend_Db::FETCH_ASSOC
        );

        if (isset($linkEntry['link_id'])) {
            return $linkEntry;
        }

        $connection->insert($catalogProductLinkTable, [
            'product_id' => $product_id,
            'linked_product_id' => $linked_product_id,
            'link_type_id' => $link_type_id
        ]);

        $linkEntry = [
            'link_id' => $connection->lastInsertId(),
            'product_id' => $product_id,
            'linked_product_id' => $linked_product_id,
            'link_type_id' => $link_type_id
        ];

        $positionAttribute = $this->getLinkAttributeByCode('position', $link_type_id);

        $connection->insert(
            $connection->getTableName('catalog_product_link_attribute_int'),
            [
                'product_link_attribute_id' => $positionAttribute['product_link_attribute_id'],
                'link_id' => $linkEntry['link_id'],
                'value' => 0
            ]
        );

        return $linkEntry;
    }

    /**
     * @param $attributeCode
     * @param $linkTypeId
     * @return array
     */
    protected function getLinkAttributeByCode($attributeCode, $linkTypeId)
    {
        if (isset($this->attributes[$attributeCode][$linkTypeId])) {
            return $this->attributes[$attributeCode][$linkTypeId];
        }

        $connection = $this->getConnection();
        $catalogProductLinkAttributeTable = $connection->getTableName('catalog_product_link_attribute');

        return ($this->attributes[$attributeCode][$linkTypeId] = $connection->fetchRow(
            "SELECT * FROM {$catalogProductLinkAttributeTable} WHERE link_type_id = ? AND product_link_attribute_code = ?",
            [$linkTypeId, $attributeCode],
            Zend_Db::FETCH_ASSOC
        ));
    }

    /**
     * @param $product_id
     * @param $link_type_id
     */
    protected function updatePositions($product_id, $link_type_id)
    {
        $positionAttribute = $this->getLinkAttributeByCode('position', $link_type_id);
        $connection = $this->getConnection();

        $items = $connection->fetchAll(
            'SELECT
cpl.product_id,
	cplai.value_id,
	cplai.value,
    cplai.link_id,  
    cplai.product_link_attribute_id,
    cplai.value_id
FROM
    catalog_product_link_attribute cpla
INNER JOIN catalog_product_link_attribute_int cplai ON
    cpla.product_link_attribute_id = cplai.product_link_attribute_id
JOIN catalog_product_link cpl ON
    cplai.link_id = cpl.link_id
WHERE 
cpla.link_type_id = ? AND
cpl.product_id = ? AND
cplai.product_link_attribute_id = ?
ORDER BY  cplai.`value` ASC',
            [$link_type_id, $product_id, $positionAttribute['product_link_attribute_id']],
            Zend_Db::FETCH_ASSOC
        );

        $i = 1;
        foreach ($items as $item) {
            $connection->update('catalog_product_link_attribute_int', ['value' => $i++], ['value_id = ?' => $item['value_id']]);
        }
    }

    /**
     * @param $product_id
     * @param $link_type_id
     * @return void
     */
    protected function deleteLinkEntries($product_id, $link_type_id)
    {
        $connection = $this->resourceConnection->getConnection('core_write');
        $catalogProductLinkTable = $connection->getTableName('catalog_product_link');
        $sql = "DELETE FROM {$catalogProductLinkTable} WHERE `product_id` = {$product_id} AND `link_type_id` = {$link_type_id} ";
        $connection->query($sql);
    }

    /**
     * @return int of \Magento\Catalog\Model\Product\Link::LINK_TYPE_RELATED, \Magento\Catalog\Model\Product\Link::LINK_TYPE_CROSSSELL, \Magento\Catalog\Model\Product\Link::LINK_TYPE_UPSELL
     */
    abstract protected function getLinkType();
}
