<?php
/**
 * Copyright © Magento, Inc. All rights reserved.
 * See COPYING.txt for license details.
 */

namespace Magento\Checkout\Test\Unit\Model;

use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper;

/**
 * Class CartTest
 *
 * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
 */
class CartTest extends \PHPUnit\Framework\TestCase
{
    /** @var \Magento\Checkout\Model\Cart */
    protected $cart;

    /** @var ObjectManagerHelper */
    protected $objectManagerHelper;

    /** @var \Magento\Checkout\Model\Session|\PHPUnit_Framework_MockObject_MockObject */
    protected $checkoutSessionMock;

    /**
     * @var \PHPUnit_Framework_MockObject_MockObject
     */
    protected $customerSessionMock;

    /** @var \Magento\CatalogInventory\Api\Data\StockItemInterface|\PHPUnit_Framework_MockObject_MockObject */
    protected $stockItemMock;

    /**
     * @var \PHPUnit_Framework_MockObject_MockObject
     */
    protected $scopeConfigMock;

    /**
     * @var \Magento\Quote\Model\Quote|\PHPUnit_Framework_MockObject_MockObject
     */
    protected $quoteMock;

    /**
     * @var \PHPUnit_Framework_MockObject_MockObject
     */
    protected $eventManagerMock;

    /**
     * @var \PHPUnit_Framework_MockObject_MockObject
     */
    protected $stockRegistry;

    /**
     * @var \PHPUnit_Framework_MockObject_MockObject
     */
    protected $stockState;

    /**
     * @var \PHPUnit_Framework_MockObject_MockObject
     */
    private $storeManagerMock;

    /**
     * @var \PHPUnit_Framework_MockObject_MockObject
     */
    private $storeMock;

    /**
     * @var \PHPUnit_Framework_MockObject_MockObject
     */
    private $productRepository;

    /**
     * @var \PHPUnit_Framework_MockObject_MockObject
     */
    private $requestInfoFilterMock;

    protected function setUp()
    {
        $this->checkoutSessionMock = $this->createMock(\Magento\Checkout\Model\Session::class);
        $this->customerSessionMock = $this->createMock(\Magento\Customer\Model\Session::class);
        $this->scopeConfigMock = $this->createMock(\Magento\Framework\App\Config\ScopeConfigInterface::class);
        $this->quoteMock = $this->createMock(\Magento\Quote\Model\Quote::class);
        $this->eventManagerMock = $this->createMock(\Magento\Framework\Event\ManagerInterface::class);
        $this->storeManagerMock = $this->createMock(\Magento\Store\Model\StoreManagerInterface::class);
        $this->productRepository = $this->createMock(\Magento\Catalog\Api\ProductRepositoryInterface::class);
        $this->stockRegistry = $this->getMockBuilder(\Magento\CatalogInventory\Model\StockRegistry::class)
            ->disableOriginalConstructor()
            ->setMethods(['getStockItem', '__wakeup'])
            ->getMock();
        $this->stockItemMock = $this->createPartialMock(
            \Magento\CatalogInventory\Model\Stock\Item::class,
            ['getMinSaleQty', '__wakeup']
        );
        $this->stockState = $this->createPartialMock(
            \Magento\CatalogInventory\Model\StockState::class,
            ['suggestQty', '__wakeup']
        );
        $this->storeMock =
            $this->createPartialMock(\Magento\Store\Model\Store::class, ['getWebsiteId', 'getId', '__wakeup']);
        $this->requestInfoFilterMock = $this->createMock(
            \Magento\Checkout\Model\Cart\RequestInfoFilterInterface::class
        );

        $this->stockRegistry->expects($this->any())
            ->method('getStockItem')
            ->will($this->returnValue($this->stockItemMock));
        $this->storeMock->expects($this->any())
            ->method('getWebsiteId')
            ->will($this->returnValue(10));
        $this->storeMock->expects($this->any())
            ->method('getId')
            ->will($this->returnValue(10));
        $this->storeManagerMock->expects($this->any())
            ->method('getStore')
            ->will($this->returnValue($this->storeMock));

        $this->objectManagerHelper = new ObjectManagerHelper($this);
        $this->cart = $this->objectManagerHelper->getObject(
            \Magento\Checkout\Model\Cart::class,
            [
                'scopeConfig' => $this->scopeConfigMock,
                'checkoutSession' => $this->checkoutSessionMock,
                'stockRegistry' => $this->stockRegistry,
                'stockState' => $this->stockState,
                'customerSession' => $this->customerSessionMock,
                'eventManager' => $this->eventManagerMock,
                'storeManager' => $this->storeManagerMock,
                'productRepository' => $this->productRepository
            ]
        );

        $this->objectManagerHelper
            ->setBackwardCompatibleProperty($this->cart, 'requestInfoFilter', $this->requestInfoFilterMock);
    }

    public function testSuggestItemsQty()
    {
        $data = [[] , ['qty' => -2], ['qty' => 3], ['qty' => 3.5], ['qty' => 5], ['qty' => 4]];
        $this->quoteMock->expects($this->any())
            ->method('getItemById')
            ->will($this->returnValueMap([
                [2, $this->prepareQuoteItemMock(2)],
                [3, $this->prepareQuoteItemMock(3)],
                [4, $this->prepareQuoteItemMock(4)],
                [5, $this->prepareQuoteItemMock(5)],
            ]));

        $this->stockState->expects($this->at(0))
            ->method('suggestQty')
            ->will($this->returnValue(3.0));
        $this->stockState->expects($this->at(1))
            ->method('suggestQty')
            ->will($this->returnValue(3.5));

        $this->checkoutSessionMock->expects($this->any())
            ->method('getQuote')
            ->will($this->returnValue($this->quoteMock));

        $this->assertSame(
            [
                [],
                ['qty' => -2],
                ['qty' => 3., 'before_suggest_qty' => 3.],
                ['qty' => 3.5, 'before_suggest_qty' => 3.5],
                ['qty' => 5],
                ['qty' => 4],
            ],
            $this->cart->suggestItemsQty($data)
        );
    }

    public function testUpdateItems()
    {
        $data = [['qty' => 5.5, 'before_suggest_qty' => 5.5]];
        $infoDataObject = $this->objectManagerHelper->getObject(
            \Magento\Framework\DataObject::class,
            ['data' => $data]
        );

        $this->checkoutSessionMock->expects($this->once())
            ->method('getQuote')
            ->will($this->returnValue($this->quoteMock));
        $this->eventManagerMock->expects($this->at(0))->method('dispatch')->with(
            'checkout_cart_update_items_before',
            ['cart' => $this->cart, 'info' => $infoDataObject]
        );
        $this->eventManagerMock->expects($this->at(1))->method('dispatch')->with(
            'checkout_cart_update_items_after',
            ['cart' => $this->cart, 'info' => $infoDataObject]
        );

        $result = $this->cart->updateItems($data);
        $this->assertSame($this->cart, $result);
    }

    /**
     * @param int|bool $itemId
     * @return \PHPUnit_Framework_MockObject_MockObject
     */
    public function prepareQuoteItemMock($itemId)
    {
        $store = $this->createPartialMock(\Magento\Store\Model\Store::class, ['getId', '__wakeup', 'getWebsiteId']);
        $store->expects($this->any())
            ->method('getWebsiteId')
            ->will($this->returnValue(10));
        $store->expects($this->any())
            ->method('getId')
            ->will($this->returnValue(10));
        $this->storeManagerMock->expects($this->any())
            ->method('getStore')
            ->will($this->returnValue($store));

        switch ($itemId) {
            case 2:
                $product = $this->createPartialMock(
                    \Magento\Catalog\Model\Product::class,
                    ['getStore', 'getId', '__wakeup']
                );
                $product->expects($this->once())
                    ->method('getId')
                    ->will($this->returnValue(4));
                $product->expects($this->once())
                    ->method('getStore')
                    ->will($this->returnValue($store));
                break;
            case 3:
                $product = $this->createPartialMock(
                    \Magento\Catalog\Model\Product::class,
                    ['getStore', 'getId', '__wakeup']
                );
                $product->expects($this->once())
                    ->method('getId')
                    ->will($this->returnValue(5));
                $product->expects($this->once())
                    ->method('getStore')
                    ->will($this->returnValue($store));
                break;
            case 4:
                $product = false;
                break;
            default:
                return false;
        }

        $quoteItem = $this->createMock(\Magento\Quote\Model\Quote\Item::class);
        $quoteItem->expects($this->once())
            ->method('getProduct')
            ->will($this->returnValue($product));
        return $quoteItem;
    }

    /**
     * @param boolean $useQty
     * @dataProvider useQtyDataProvider
     */
    public function testGetSummaryQty($useQty)
    {
        $quoteId = 1;
        $itemsCount = 1;
        $quoteMock = $this->createPartialMock(
            \Magento\Quote\Model\Quote::class,
            ['getItemsCount', 'getItemsQty', '__wakeup']
        );

        $this->checkoutSessionMock->expects($this->any())->method('getQuote')->will($this->returnValue($quoteMock));
        $this->checkoutSessionMock->expects($this->at(2))->method('getQuoteId')->will($this->returnValue($quoteId));
        $this->customerSessionMock->expects($this->any())->method('isLoggedIn')->will($this->returnValue(true));

        $this->scopeConfigMock->expects($this->once())->method('getValue')
            ->with('checkout/cart_link/use_qty', \Magento\Store\Model\ScopeInterface::SCOPE_STORE)
            ->will($this->returnValue($useQty));

        $qtyMethodName = ($useQty) ? 'getItemsQty' : 'getItemsCount';
        $quoteMock->expects($this->once())->method($qtyMethodName)->will($this->returnValue($itemsCount));

        $this->assertEquals($itemsCount, $this->cart->getSummaryQty());
    }

    /**
     * @return array
     */
    public function useQtyDataProvider()
    {
        return [
            ['useQty' => true],
            ['useQty' => false]
        ];
    }

    /**
     * Test successful scenarios for AddProduct
     *
     * @param int|\Magento\Catalog\Model\Product $productInfo
     * @param \Magento\Framework\DataObject|int|array $requestInfo
     * @dataProvider addProductDataProvider
     */
    public function testAddProduct($productInfo, $requestInfo)
    {
        $product = $this->createPartialMock(
            \Magento\Catalog\Model\Product::class,
            ['getStore', 'getWebsiteIds', 'getProductUrl', 'getId', '__wakeup']
        );
        $product->expects($this->any())
            ->method('getId')
            ->will($this->returnValue(4));
        $product->expects($this->once())
            ->method('getStore')
            ->will($this->returnValue($this->storeMock));
        $product->expects($this->any())
            ->method('getWebsiteIds')
            ->will($this->returnValue([10]));
        $product->expects($this->any())
            ->method('getProductUrl')
            ->will($this->returnValue('url'));
        $this->productRepository->expects($this->any())
            ->method('getById')
            ->will($this->returnValue($product));

        $this->eventManagerMock->expects($this->at(0))->method('dispatch')->with(
            'checkout_cart_product_add_before',
            ['info' => $requestInfo, 'product' => $product]
        );

        $this->quoteMock->expects($this->once())
        ->method('addProduct')
        ->will($this->returnValue(1));
        $this->checkoutSessionMock->expects($this->once())
            ->method('getQuote')
            ->will($this->returnValue($this->quoteMock));

        $this->eventManagerMock->expects($this->at(1))->method('dispatch')->with(
            'checkout_cart_product_add_after',
            ['quote_item' => 1, 'product' => $product]
        );

        if (!$productInfo) {
            $productInfo = $product;
        }
        $result = $this->cart->addProduct($productInfo, $requestInfo);
        $this->assertSame($this->cart, $result);
    }

    /**
     * Test exception on adding product for AddProduct
     *
     * @throws \Magento\Framework\Exception\LocalizedException
     */
    public function testAddProductException()
    {
        $product = $this->createPartialMock(
            \Magento\Catalog\Model\Product::class,
            ['getStore', 'getWebsiteIds', 'getProductUrl', 'getId', '__wakeup']
        );
        $product->expects($this->any())
            ->method('getId')
            ->will($this->returnValue(4));
        $product->expects($this->once())
            ->method('getStore')
            ->will($this->returnValue($this->storeMock));
        $product->expects($this->any())
            ->method('getWebsiteIds')
            ->will($this->returnValue([10]));
        $product->expects($this->any())
            ->method('getProductUrl')
            ->will($this->returnValue('url'));
        $this->productRepository->expects($this->any())
            ->method('getById')
            ->will($this->returnValue($product));

        $this->eventManagerMock->expects($this->once())->method('dispatch')->with(
            'checkout_cart_product_add_before',
            ['info' => 4, 'product' => $product]
        );

        $this->quoteMock->expects($this->once())
            ->method('addProduct')
            ->will($this->returnValue('error'));
        $this->checkoutSessionMock->expects($this->once())
            ->method('getQuote')
            ->will($this->returnValue($this->quoteMock));

        $this->expectException(\Magento\Framework\Exception\LocalizedException::class);
        $this->cart->addProduct(4, 4);
    }

    /**
     * Test bad parameters on adding product for AddProduct
     *
     * @throws \Magento\Framework\Exception\LocalizedException
     */
    public function testAddProductExceptionBadParams()
    {
        $product = $this->createPartialMock(
            \Magento\Catalog\Model\Product::class,
            ['getWebsiteIds', 'getId', '__wakeup']
        );
        $product->expects($this->any())
            ->method('getId')
            ->will($this->returnValue(4));
        $product->expects($this->any())
            ->method('getWebsiteIds')
            ->will($this->returnValue([10]));
        $this->productRepository->expects($this->any())
            ->method('getById')
            ->will($this->returnValue($product));

        $this->eventManagerMock->expects($this->never())->method('dispatch')->with(
            'checkout_cart_product_add_before',
            ['info' => 'bad', 'product' => $product]
        );

        $this->eventManagerMock->expects($this->never())->method('dispatch')->with(
            'checkout_cart_product_add_after',
            ['quote_item' => 1, 'product' => $product]
        );
        $this->expectException(\Magento\Framework\Exception\LocalizedException::class);
        $this->cart->addProduct(4, 'bad');
    }

    /**
     * Data provider for testAddProduct
     *
     * @return array
     */
    public function addProductDataProvider()
    {
        $obj = new ObjectManagerHelper($this);
        $data = ['qty' => 5.5, 'sku' => 'prod'];

        return [
            'prod_int_info_int' => [4, 4],
            'prod_int_info_array' => [ 4, $data],
            'prod_int_info_object' => [
                4,
                $obj->getObject(
                    \Magento\Framework\DataObject::class,
                    ['data' => $data]
                )
            ],
            'prod_obj_info_int' => [null, 4],
            'prod_obj_info_array' => [ null, $data],
            'prod_obj_info_object' => [
                null,
                $obj->getObject(
                    \Magento\Framework\DataObject::class,
                    ['data' => $data]
                )
            ]
        ];
    }
}