<?php
/**
 * Copyright © Magento, Inc. All rights reserved.
 * See COPYING.txt for license details.
 */
declare(strict_types=1);

namespace Magento\Quote\Model\Plugin;

use Magento\Checkout\Model\Session;
use Magento\Framework\Api\SearchCriteriaBuilder;
use Magento\FunctionalTestingFramework\ObjectManagerInterface;
use Magento\Quote\Api\CartRepositoryInterface;
use Magento\Quote\Model\Quote;
use Magento\Store\Api\Data\StoreInterface;
use Magento\Store\Api\StoreCookieManagerInterface;
use Magento\Store\Model\StoreManagerInterface;
use Magento\Store\Model\StoreRepository;
use Magento\TestFramework\Helper\Bootstrap as BootstrapHelper;

/**
 * @magentoAppArea frontend
 * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
 */
class UpdateQuoteStoreTest extends \PHPUnit\Framework\TestCase
{
    /**
     * @var ObjectManagerInterface
     */
    private $objectManager;

    /**
     * @var CartRepositoryInterface
     */
    private $quoteRepository;

    protected function setUp()
    {
        $this->objectManager = BootstrapHelper::getObjectManager();
        $this->quoteRepository = $this->objectManager->create(CartRepositoryInterface::class);
    }

    /**
     * Tests that active quote store id updates after store cookie change.
     *
     * @magentoDataFixture Magento/Quote/_files/empty_quote.php
     * @magentoDataFixture Magento/Store/_files/second_store.php
     * @throws \ReflectionException
     * @throws \Magento\Framework\Exception\NoSuchEntityException
     */
    public function testUpdateQuoteStoreAfterChangeStoreCookie()
    {
        $secondStoreCode = 'fixture_second_store';
        $reservedOrderId = 'reserved_order_id';

        /** @var StoreManagerInterface $storeManager */
        $storeManager = $this->objectManager->get(StoreManagerInterface::class);
        $currentStore = $storeManager->getStore();

        $quote = $this->getQuote($reservedOrderId);
        $this->assertEquals(
            $currentStore->getId(),
            $quote->getStoreId(),
            'Current store id and quote store id are not match'
        );

        /** @var Session $checkoutSession */
        $checkoutSession = $this->objectManager->get(Session::class);
        $checkoutSession->setQuoteId($quote->getId());

        $storeRepository = $this->objectManager->create(StoreRepository::class);
        $secondStore = $storeRepository->get($secondStoreCode);

        $storeCookieManager = $this->getStoreCookieManager($currentStore);
        $storeCookieManager->setStoreCookie($secondStore);

        $updatedQuote = $this->getQuote($reservedOrderId);
        $this->assertEquals(
            $secondStore->getId(),
            $updatedQuote->getStoreId(),
            'Active quote store id should be equal second store id'
        );
    }

    /**
     * Retrieves quote by reserved order id.
     *
     * @param string $reservedOrderId
     * @return Quote
     */
    private function getQuote(string $reservedOrderId): Quote
    {
        /** @var SearchCriteriaBuilder $searchCriteriaBuilder */
        $searchCriteriaBuilder = $this->objectManager->get(SearchCriteriaBuilder::class);
        $searchCriteria = $searchCriteriaBuilder->addFilter('reserved_order_id', $reservedOrderId)
            ->create();

        $items = $this->quoteRepository->getList($searchCriteria)->getItems();

        return array_pop($items);
    }

    /**
     * Returns instance of StoreCookieManagerInterface with mocked cookieManager dependency.
     *
     * Mock is needed since integration test framework use own cookie manager with
     * behavior that differs from real environment.
     *
     * @param $currentStore
     * @return StoreCookieManagerInterface
     * @throws \ReflectionException
     */
    private function getStoreCookieManager(StoreInterface $currentStore): StoreCookieManagerInterface
    {
        /** @var StoreCookieManagerInterface $storeCookieManager */
        $storeCookieManager = $this->objectManager->get(StoreCookieManagerInterface::class);
        $cookieManagerMock = $this->getMockBuilder(\Magento\Framework\Stdlib\Cookie\PhpCookieManager::class)
            ->disableOriginalConstructor()
            ->getMock();
        $cookieManagerMock->method('getCookie')
            ->willReturn($currentStore->getCode());

        $reflection = new \ReflectionClass($storeCookieManager);
        $cookieManager = $reflection->getProperty('cookieManager');
        $cookieManager->setAccessible(true);
        $cookieManager->setValue($storeCookieManager, $cookieManagerMock);

        return $storeCookieManager;
    }
}