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

namespace Magento\CatalogUrlRewrite\Test\Unit\Model\Product;

use Magento\Catalog\Model\Category;
use Magento\Catalog\Model\Product;
use Magento\CatalogUrlRewrite\Model\Map\UrlRewriteFinder;
use Magento\CatalogUrlRewrite\Model\ObjectRegistry;
use Magento\CatalogUrlRewrite\Model\Product\CurrentUrlRewritesRegenerator;
use Magento\CatalogUrlRewrite\Model\ProductUrlPathGenerator;
use Magento\CatalogUrlRewrite\Model\ProductUrlRewriteGenerator;
use Magento\Framework\TestFramework\Unit\Helper\ObjectManager;
use Magento\UrlRewrite\Model\MergeDataProvider;
use Magento\UrlRewrite\Model\MergeDataProviderFactory;
use Magento\UrlRewrite\Model\OptionProvider;
use Magento\UrlRewrite\Service\V1\Data\UrlRewrite;
use Magento\UrlRewrite\Service\V1\Data\UrlRewriteFactory;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;

/**
 * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
 */
class CurrentUrlRewritesRegeneratorTest extends TestCase
{
    /** @var CurrentUrlRewritesRegenerator */
    private $currentUrlRewritesRegenerator;

    /** @var ProductUrlPathGenerator|MockObject */
    private $productUrlPathGenerator;

    /** @var Product|MockObject */
    private $product;

    /** @var Category|MockObject */
    private $category;

    /** @var ObjectRegistry|MockObject */
    private $objectRegistry;

    /** @var UrlRewriteFactory|MockObject */
    private $urlRewriteFactory;

    /** @var UrlRewrite|MockObject */
    private $urlRewrite;

    /** @var MockObject */
    private $mergeDataProvider;

    /** @var MockObject */
    private $urlRewriteFinder;

    protected function setUp(): void
    {
        $this->urlRewriteFactory = $this->getMockBuilder(UrlRewriteFactory::class)
            ->onlyMethods(['create'])
            ->disableOriginalConstructor()
            ->getMock();
        $this->urlRewrite = $this->getMockBuilder(UrlRewrite::class)
            ->disableOriginalConstructor()
            ->getMock();
        $this->product = $this->getMockBuilder(Product::class)
            ->disableOriginalConstructor()
            ->getMock();
        $this->category = $this->getMockBuilder(Category::class)
            ->disableOriginalConstructor()
            ->getMock();
        $this->objectRegistry = $this->getMockBuilder(ObjectRegistry::class)
            ->disableOriginalConstructor()
            ->getMock();
        $this->urlRewriteFinder = $this->getMockBuilder(UrlRewriteFinder::class)
            ->disableOriginalConstructor()
            ->getMock();
        $this->urlRewriteFactory->expects($this->once())->method('create')
            ->willReturn($this->urlRewrite);
        $this->productUrlPathGenerator = $this->getMockBuilder(
            ProductUrlPathGenerator::class
        )->disableOriginalConstructor()
            ->getMock();
        $mergeDataProviderFactory = $this->createPartialMock(
            MergeDataProviderFactory::class,
            ['create']
        );
        $this->mergeDataProvider = new MergeDataProvider();
        $mergeDataProviderFactory->expects($this->once())->method('create')->willReturn($this->mergeDataProvider);
        $this->currentUrlRewritesRegenerator = (new ObjectManager($this))->getObject(
            CurrentUrlRewritesRegenerator::class,
            [
                'productUrlPathGenerator' => $this->productUrlPathGenerator,
                'urlRewriteFactory' => $this->urlRewriteFactory,
                'mergeDataProviderFactory' => $mergeDataProviderFactory,
                'urlRewriteFinder' => $this->urlRewriteFinder
            ]
        );
    }

    public function testIsAutogeneratedWithoutSaveRewriteHistory()
    {
        $this->urlRewriteFinder->expects($this->once())->method('findAllByData')
            ->willReturn($this->getCurrentRewritesMocks([[UrlRewrite::IS_AUTOGENERATED => 1]]));
        $this->product->expects($this->once())->method('getData')->with('save_rewrites_history')
            ->willReturn(false);

        $this->assertEquals(
            [],
            $this->currentUrlRewritesRegenerator->generate('store_id', $this->product, $this->objectRegistry)
        );
    }

    public function testSkipGenerationForAutogenerated()
    {
        $this->urlRewriteFinder->expects($this->once())->method('findAllByData')
            ->willReturn($this->getCurrentRewritesMocks([
                [UrlRewrite::IS_AUTOGENERATED => 1, UrlRewrite::REQUEST_PATH => 'same-path'],
            ]));
        $this->product->expects($this->once())->method('getData')->with('save_rewrites_history')
            ->willReturn(true);
        $this->productUrlPathGenerator->expects($this->once())->method('getUrlPathWithSuffix')
            ->willReturn('same-path');

        $this->assertEquals(
            [],
            $this->currentUrlRewritesRegenerator->generate('store_id', $this->product, $this->objectRegistry, 1)
        );
    }

    public function testIsAutogeneratedWithoutCategory()
    {
        $requestPath = 'autogenerated.html';
        $targetPath = 'some-path.html';
        $autoGenerated = 1;
        $storeId = 2;
        $productId = 12;
        $description = 'description';
        $this->urlRewriteFinder->expects($this->once())->method('findAllByData')
            ->willReturn($this->getCurrentRewritesMocks([
                [
                    UrlRewrite::REQUEST_PATH => $requestPath,
                    UrlRewrite::TARGET_PATH => 'custom-target-path',
                    UrlRewrite::STORE_ID => $storeId,
                    UrlRewrite::IS_AUTOGENERATED => $autoGenerated,
                    UrlRewrite::METADATA => [],
                    UrlRewrite::DESCRIPTION => $description,
                ],
            ]));
        $this->product->expects($this->any())->method('getEntityId')->willReturn($productId);
        $this->product->expects($this->once())->method('getData')->with('save_rewrites_history')
            ->willReturn(true);
        $this->productUrlPathGenerator->expects($this->once())->method('getUrlPathWithSuffix')
            ->willReturn($targetPath);

        $this->prepareUrlRewriteMock(
            $storeId,
            $productId,
            $requestPath,
            $targetPath,
            0,
            OptionProvider::PERMANENT,
            [],
            $description
        );

        $this->assertEquals(
            [$this->urlRewrite],
            $this->currentUrlRewritesRegenerator->generate($storeId, $this->product, $this->objectRegistry, 1)
        );
    }

    public function testIsAutogeneratedWithCategory()
    {
        $productId = 12;
        $requestPath = 'autogenerated.html';
        $targetPath = 'simple-product.html';
        $autoGenerated = 1;
        $storeId = 2;
        $metadata = ['category_id' => 2, 'some_another_data' => 1];
        $description = 'description';
        $this->urlRewriteFinder->expects($this->once())->method('findAllByData')
            ->willReturn($this->getCurrentRewritesMocks([
                [
                    UrlRewrite::REQUEST_PATH => $requestPath,
                    UrlRewrite::TARGET_PATH => 'some-path.html',
                    UrlRewrite::STORE_ID => $storeId,
                    UrlRewrite::IS_AUTOGENERATED => $autoGenerated,
                    UrlRewrite::METADATA => $metadata,
                    UrlRewrite::DESCRIPTION => $description,
                ],
            ]));
        $this->product->expects($this->any())->method('getEntityId')->willReturn($productId);
        $this->product->expects($this->once())->method('getData')->with('save_rewrites_history')
            ->willReturn(true);
        $this->productUrlPathGenerator->expects($this->once())->method('getUrlPathWithSuffix')
            ->willReturn($targetPath);
        $this->objectRegistry->expects($this->once())->method('get')->willReturn($this->category);
        $this->prepareUrlRewriteMock(
            $storeId,
            $productId,
            $requestPath,
            $targetPath,
            0,
            OptionProvider::PERMANENT,
            $metadata,
            $description
        );

        $this->assertEquals(
            [$this->urlRewrite],
            $this->currentUrlRewritesRegenerator->generate($storeId, $this->product, $this->objectRegistry, 2)
        );
    }

    public function testSkipGenerationForCustom()
    {
        $this->urlRewriteFinder->expects($this->once())->method('findAllByData')
            ->willReturn($this->getCurrentRewritesMocks([
                [
                    UrlRewrite::IS_AUTOGENERATED => 0,
                    UrlRewrite::REQUEST_PATH => 'same-path',
                    UrlRewrite::REDIRECT_TYPE => 1,
                ],
            ]));
        $this->productUrlPathGenerator->expects($this->once())->method('getUrlPathWithSuffix')
            ->willReturn('same-path');

        $this->assertEquals(
            [],
            $this->currentUrlRewritesRegenerator->generate('store_id', $this->product, $this->objectRegistry)
        );
    }

    public function testGenerationForCustomWithoutTargetPathGeneration()
    {
        $storeId = 12;
        $productId = 123;
        $requestPath = 'generate-for-custom-without-redirect-type.html';
        $targetPath = 'custom-target-path.html';
        $autoGenerated = 0;
        $description = 'description';
        $this->urlRewriteFinder->expects($this->once())->method('findAllByData')
            ->willReturn($this->getCurrentRewritesMocks([
                [
                    UrlRewrite::REQUEST_PATH => $requestPath,
                    UrlRewrite::TARGET_PATH => $targetPath,
                    UrlRewrite::REDIRECT_TYPE => 0,
                    UrlRewrite::IS_AUTOGENERATED => $autoGenerated,
                    UrlRewrite::DESCRIPTION => $description,
                    UrlRewrite::METADATA => [],
                ],
            ]));
        $this->productUrlPathGenerator->expects($this->never())->method('getUrlPathWithSuffix');
        $this->product->expects($this->any())->method('getEntityId')->willReturn($productId);
        $this->prepareUrlRewriteMock(
            $storeId,
            $productId,
            $requestPath,
            $targetPath,
            $autoGenerated,
            0,
            [],
            $description
        );

        $this->assertEquals(
            [$this->urlRewrite],
            $this->currentUrlRewritesRegenerator->generate($storeId, $this->product, $this->objectRegistry)
        );
    }

    public function testGenerationForCustomWithTargetPathGeneration()
    {
        $storeId = 12;
        $productId = 123;
        $requestPath = 'generate-for-custom-without-redirect-type.html';
        $targetPath = 'generated-target-path.html';
        $autoGenerated = 0;
        $description = 'description';
        $this->urlRewriteFinder->expects($this->once())->method('findAllByData')
            ->willReturn($this->getCurrentRewritesMocks([
                [
                    UrlRewrite::REQUEST_PATH => $requestPath,
                    UrlRewrite::TARGET_PATH => 'custom-target-path.html',
                    UrlRewrite::REDIRECT_TYPE => 'code',
                    UrlRewrite::IS_AUTOGENERATED => $autoGenerated,
                    UrlRewrite::DESCRIPTION => $description,
                    UrlRewrite::METADATA => [],
                ],
            ]));
        $this->productUrlPathGenerator->expects($this->any())->method('getUrlPathWithSuffix')
            ->willReturn($targetPath);
        $this->product->expects($this->any())->method('getEntityId')->willReturn($productId);
        $this->prepareUrlRewriteMock($storeId, $productId, $requestPath, $targetPath, 0, 'code', [], $description);

        $this->assertEquals(
            [$this->urlRewrite],
            $this->currentUrlRewritesRegenerator->generate($storeId, $this->product, $this->objectRegistry)
        );
    }

    /**
     * @param array $currentRewrites
     * @return array
     */
    protected function getCurrentRewritesMocks($currentRewrites)
    {
        $rewrites = [];
        foreach ($currentRewrites as $urlRewrite) {
            /** @var MockObject */
            $url = $this->getMockBuilder(UrlRewrite::class)
                ->disableOriginalConstructor()
                ->getMock();
            foreach ($urlRewrite as $key => $value) {
                $url->expects($this->any())
                    ->method('get' . str_replace('_', '', ucwords($key, '_')))
                    ->willReturn($value);
            }
            $rewrites[] = $url;
        }
        return $rewrites;
    }

    /**
     * @param mixed $storeId
     * @param mixed $productId
     * @param mixed $requestPath
     * @param mixed $targetPath
     * @param mixed $autoGenerated
     * @param mixed $redirectType
     * @param mixed $metadata
     * @param mixed $description
     */
    protected function prepareUrlRewriteMock(
        $storeId,
        $productId,
        $requestPath,
        $targetPath,
        $autoGenerated,
        $redirectType,
        $metadata,
        $description
    ) {
        $this->urlRewrite->expects($this->any())->method('setStoreId')->with($storeId)->willReturnSelf();
        $this->urlRewrite->expects($this->any())->method('setEntityId')->with($productId)->willReturnSelf();
        $this->urlRewrite->expects($this->any())->method('setEntityType')
            ->with(ProductUrlRewriteGenerator::ENTITY_TYPE)->willReturnSelf();
        $this->urlRewrite->expects($this->any())->method('setRequestPath')->with($requestPath)->willReturnSelf();
        $this->urlRewrite->expects($this->any())->method('setTargetPath')->with($targetPath)->willReturnSelf();
        $this->urlRewrite->expects($this->any())->method('setIsAutogenerated')->with($autoGenerated)->willReturnSelf();
        $this->urlRewrite->expects($this->any())->method('setRedirectType')->with($redirectType)->willReturnSelf();
        $this->urlRewrite->expects($this->any())->method('setMetadata')->with($metadata)->willReturnSelf();
        $this->urlRewriteFactory->expects($this->any())->method('create')->willReturn($this->urlRewrite);
        $this->urlRewrite->expects($this->once())->method('setDescription')->with($description)->willReturnSelf();
    }
}
