<?php

declare(strict_types=1);

namespace FiloBlu\Refilo\Model\Importer;

use FiloBlu\Core\Model\Configuration;
use FiloBlu\Refilo\Model\Adapter\Mongo;
use FiloBlu\Refilo\Model\WishlistManagement;
use Magento\Catalog\Api\Data\ProductInterface;
use Magento\Catalog\Api\ProductRepositoryInterface;
use Magento\Catalog\Model\Product\Type;
use Magento\ConfigurableProduct\Model\Product\Type\Configurable;
use Magento\Framework\App\Config\ScopeConfigInterface;
use Magento\Framework\App\ResourceConnection;
use Magento\Framework\Exception\NoSuchEntityException;
use Symfony\Component\Console\Input\InputInterface;

/**
 * @class Wishlist
 * @package FiloBlu\Refilo\Model\Importer
 */
class Wishlist extends AbstractImporter
{

    /** @var ResourceConnection */
    private $connection;
    /** @var WishlistManagement */
    private $wishlistManagement;
    /** @var ProductRepositoryInterface */
    private $productRepository;
    /** @var Configurable */
    private $configurable;

    /**
     * @param Mongo $mongo
     * @param Configuration $coreConfiguration
     * @param ScopeConfigInterface $scopeConfig
     * @param ResourceConnection $connection
     * @param WishlistManagement $wishlistManagement
     * @param ProductRepositoryInterface $productRepository
     * @param Configurable $configurable
     */
    public function __construct(
        Mongo                      $mongo,
        Configuration              $coreConfiguration,
        ScopeConfigInterface       $scopeConfig,
        ResourceConnection         $connection,
        WishlistManagement         $wishlistManagement,
        ProductRepositoryInterface $productRepository,
        Configurable               $configurable
    ) {
        parent::__construct(
            $mongo,
            $coreConfiguration,
            $scopeConfig
        );
        $this->scopeConfig = $scopeConfig;
        $this->connection = $connection;
        $this->wishlistManagement = $wishlistManagement;
        $this->productRepository = $productRepository;
        $this->configurable = $configurable;
    }

    public function preExecute(InputInterface $input = null)
    {
        // Intentionally left blank
    }

    /**
     * @return iterable
     */
    public function getCollections(): iterable
    {
        yield 'wishlist';
    }

    /**
     * @param InputInterface|null $input
     */
    public function execute(InputInterface $input = null)
    {
        // Iterate only the wishlists that actually exist on Mongo, instead of probing every
        // customer id from 1..max (which degenerates into millions of remote calls).
        foreach ($this->getConnection()->getAll('wishlist') as $wishlist) {
            $customerId = (int)($wishlist['_id'] ?? 0);
            if (!$customerId || !$this->customerExists($customerId)) {
                continue;
            }

            $this->importCustomerWishlist($customerId, $wishlist['items'] ?? []);
        }
    }

    /**
     * Re-sync a single customer's wishlist from the source data.
     *
     * The delete + re-insert run inside a single DB transaction: a systemic failure rolls
     * the delete back, so the customer never ends up with an emptied wishlist. Individual
     * items that can't be resolved (missing SKU, no parent) are skipped without aborting.
     *
     * @param int $customerId
     * @param iterable $wishlistItems
     * @return void
     */
    private function importCustomerWishlist(int $customerId, $wishlistItems): void
    {
        $connection = $this->connection->getConnection();
        $connection->beginTransaction();
        try {
            $this->deleteCustomerWishlist($customerId);

            foreach ($wishlistItems as $item) {
                try {
                    $product = $this->loadProductBySku($item['sku'] ?? null);
                    if ($product === null) {
                        continue;
                    }

                    if ($product->getTypeId() === Type::TYPE_SIMPLE) {
                        $parentIds = array_values($this->configurable->getParentIdsByChild($product->getId()));
                        if (empty($parentIds)) {
                            continue;
                        }

                        $parentId = array_shift($parentIds);
                        if ($parentId) {
                            $this->wishlistManagement->addProductForCustomer((int)$parentId, $customerId);
                        }

                        continue;
                    }

                    $this->wishlistManagement->addProductForCustomer((int)$product->getId(), $customerId);
                } catch (\Exception $e) {
                    // Skip a single unresolvable item, keep importing the rest.
                    continue;
                }
            }

            $connection->commit();
        } catch (\Throwable $e) {
            $connection->rollBack();
        }
    }

    /**
     * @return int
     */
    public function getMaxCustomerId()
    {
        $connection = $this->connection->getConnection();
        $maxIdObj = $connection->fetchOne('SELECT entity_id FROM customer_entity ORDER BY entity_id DESC LIMIT 0, 1');

        return (int)$maxIdObj;
    }

    /**
     * @param string|null $sku
     * @return ProductInterface|null
     */
    public function loadProductBySku(?string $sku)
    {
        try {
            return $this->productRepository->get($sku);
        } catch (NoSuchEntityException $exception) {
            return null;
        }
    }

    /**
     * @param int $customerId
     * @return bool
     */
    public function customerExists(int $customerId)
    {
        $connection = $this->connection->getConnection();
        $customer = $connection->fetchOne('SELECT entity_id FROM customer_entity WHERE entity_id = ? ', [$customerId]);

        return (bool)$customer;
    }

    /**
     * @param InputInterface|null $input
     * @return void
     */
    public function postExecute(InputInterface $input = null)
    {
        // Intentionally left blank
    }

    /**
     * @param int $customerId
     * @return void
     */
    public function deleteCustomerWishlist(int $customerId)
    {
        // wishlist cleaning through WishlistManagement seems to be not working occasionally
        $connection = $this->connection->getConnection();
        $connection->delete($connection->getTableName('wishlist'), ['customer_id = ?' => $customerId]);
    }
}
