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

namespace Magento\Tax\Model;

use Magento\Framework\Api\Filter;
use Magento\TestFramework\Helper\Bootstrap;

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

    /**
     * @var \Magento\Tax\Api\Data\TaxRuleInterfaceFactory
     */
    private $taxRuleFactory;

    /**
     * @var \Magento\Tax\Api\TaxRuleRepositoryInterface
     */
    private $taxRuleRepository;

    /**
     * @var TaxRuleFixtureFactory
     */
    private $taxRuleFixtureFactory;

    /**
     * Array of default tax classes ids
     *
     * Key is class name
     *
     * @var int[]
     */
    private $taxClasses;

    /**
     * Array of default tax rates ids.
     *
     * Key is rate percentage as string.
     *
     * @var int[]
     */
    private $taxRates;

    /**
     * Array of default tax rules ids.
     *
     * Key is rule code.
     *
     * @var int[]
     */
    private $taxRules;

    /**
     * @var \Magento\Tax\Api\TaxRateRepositoryInterface
     */
    private $taxRateRepository;

    /**
     * @var \Magento\Framework\Api\DataObjectHelper
     */
    private $dataObjectHelper;

    protected function setUp()
    {
        $this->objectManager = Bootstrap::getObjectManager();
        $this->taxRuleRepository = $this->objectManager->get(\Magento\Tax\Api\TaxRuleRepositoryInterface::class);
        $this->taxRateRepository = $this->objectManager->get(\Magento\Tax\Api\TaxRateRepositoryInterface::class);
        $this->taxRuleFactory = $this->objectManager->create(\Magento\Tax\Api\Data\TaxRuleInterfaceFactory::class);
        $this->dataObjectHelper = $this->objectManager->create(\Magento\Framework\Api\DataObjectHelper::class);
        $this->taxRuleFixtureFactory = new TaxRuleFixtureFactory();
    }

    /**
     * @magentoDbIsolation enabled
     */
    public function testSave()
    {
        // Tax rule data object created
        $taxRuleDataObject = $this->createTaxRuleDataObject();
        //Tax rule service call
        $taxRule = $this->taxRuleRepository->save($taxRuleDataObject);

        //Assertions
        $this->assertInstanceOf(\Magento\Tax\Api\Data\TaxRuleInterface::class, $taxRule);
        $this->assertEquals($taxRuleDataObject->getCode(), $taxRule->getCode());
        $this->assertEquals(
            $taxRuleDataObject->getCustomerTaxClassIds(),
            $taxRule->getCustomerTaxClassIds()
        );
        $this->assertEquals($taxRuleDataObject->getProductTaxClassIds(), $taxRule->getProductTaxClassIds());
        $this->assertEquals($taxRuleDataObject->getPriority(), $taxRule->getPriority());
        $this->assertEquals($taxRuleDataObject->getPosition(), $taxRule->getPosition());
        $this->assertNotNull($taxRule->getId());
    }

    /**
     * @expectedException \Magento\Framework\Exception\NoSuchEntityException
     * @expectedExceptionMessage No such entity with taxRuleId = 9999
     * @magentoDbIsolation enabled
     */
    public function testSaveThrowsExceptionIdIfTargetTaxRuleDoesNotExist()
    {
        $taxRuleDataObject = $this->taxRuleFactory->create();
        $taxRuleDataObject->setId(9999)
            ->setCode('code')
            ->setCustomerTaxClassIds([3])
            ->setProductTaxClassIds([2])
            ->setTaxRateIds([2])
            ->setPriority(0)
            ->setPosition(1);
        $this->taxRuleRepository->save($taxRuleDataObject);
    }

    /**
     * @magentoDbIsolation enabled
     * @expectedException \Magento\Framework\Exception\CouldNotSaveException
     * @expectedExceptionMessage No such entity
     */
    public function testSaveThrowsExceptionIfProvidedTaxClassIdsAreInvalid()
    {
        $taxRuleData = [
            'code' => 'code',
            // These TaxClassIds exist, but '2' is should be a productTaxClassId and
            // '3' should be a customerTaxClassId. See MAGETWO-25683.
            'customer_tax_class_ids' => [2],
            'product_tax_class_ids' => [3],
            'tax_rate_ids' => [1],
            'priority' => 0,
            'position' => 0,
        ];
        // Tax rule data object created
        $taxRule = $this->taxRuleFactory->create();
        $this->dataObjectHelper->populateWithArray(
            $taxRule,
            $taxRuleData,
            \Magento\Tax\Api\Data\TaxRuleInterface::class
        );

        $this->taxRuleRepository->save($taxRule);
    }

    /**
     * @magentoDbIsolation enabled
     * @expectedException \Magento\Framework\Exception\CouldNotSaveException
     * @expectedExceptionMessage The position value of "-1" must be greater than or equal to 0.
     */
    public function testSaveThrowsExceptionIfProvidedPositionIsInvalid()
    {
        $taxRuleData = [
            'code' => 'code',
            'customer_tax_class_ids' => [3],
            'product_tax_class_ids' => [2],
            'tax_rate_ids' => [1],
            'priority' => 0,
            'position' => -1,
        ];
        // Tax rule data object created
        $taxRule = $this->taxRuleFactory->create();
        $this->dataObjectHelper->populateWithArray(
            $taxRule,
            $taxRuleData,
            \Magento\Tax\Api\Data\TaxRuleInterface::class
        );

        //Tax rule service call
        $this->taxRuleRepository->save($taxRule);
        $this->fail('Did not throw expected InputException');
    }

    /**
     * @magentoDbIsolation enabled
     */
    public function testGetReturnsTaxRuleCreatedByRepository()
    {
        // Tax rule data object created
        $taxRuleDataObject = $this->createTaxRuleDataObject();
        //Tax rule service call to create rule
        $ruleId = $this->taxRuleRepository->save($taxRuleDataObject)->getId();

        // Call getTaxRule and verify
        $taxRule = $this->taxRuleRepository->get($ruleId);
        $this->assertEquals('code', $taxRule->getCode());
        $this->assertEquals([3], $taxRule->getCustomerTaxClassIds());
        $this->assertEquals([2], $taxRule->getProductTaxClassIds());
        $this->assertEquals([2], $taxRule->getTaxRateIds());
        $this->assertEquals(0, $taxRule->getPriority());
        $this->assertEquals(1, $taxRule->getPosition());
    }

    /**
     * @magentoDataFixture Magento/Tax/_files/tax_classes.php
     */
    public function testGetReturnsTaxRuleCreatedFromModel()
    {
        /** @var $registry \Magento\Framework\Registry */
        $registry = $this->objectManager->get(\Magento\Framework\Registry::class);
        /** @var $taxRuleModel \Magento\Tax\Model\Calculation\Rule */
        $taxRuleModel = $registry->registry('_fixture/Magento_Tax_Model_Calculation_Rule');
        $this->assertNotNull($taxRuleModel);
        $ruleId = $taxRuleModel->getId();

        $taxRateId = $registry->registry('_fixture/Magento_Tax_Model_Calculation_Rate')->getId();
        $customerTaxClassIds = array_values(array_unique($taxRuleModel->getCustomerTaxClasses()));

        // Call getTaxRule and verify
        $taxRule = $this->taxRuleRepository->get($ruleId);
        $this->assertEquals($customerTaxClassIds, $taxRule->getCustomerTaxClassIds());
        $this->assertEquals([$taxRateId], $taxRule->getTaxRateIds());
    }

    /**
     * @magentoDataFixture Magento/Tax/_files/tax_classes.php
     * @expectedException \Magento\Framework\Exception\NoSuchEntityException
     * @expectedExceptionMessage No such entity with taxRuleId
     */
    public function testDeleteById()
    {
        /** @var $registry \Magento\Framework\Registry */
        $registry = $this->objectManager->get(\Magento\Framework\Registry::class);
        /** @var $taxRule \Magento\Tax\Model\Calculation\Rule */
        $taxRule = $registry->registry('_fixture/Magento_Tax_Model_Calculation_Rule');
        $this->assertNotNull($taxRule);
        $ruleId = $taxRule->getId();

        // Delete the new tax rate
        $this->assertTrue($this->taxRuleRepository->deleteById($ruleId));

        // Get the new tax rule, this should fail
        $this->taxRuleRepository->get($ruleId);
    }

    /**
     * @magentoDataFixture Magento/Tax/_files/tax_classes.php
     * @expectedException \Magento\Framework\Exception\NoSuchEntityException
     * @expectedExceptionMessage No such entity with taxRuleId
     */
    public function testDeleteByIdThrowsExceptionIfTargetTaxRuleDoesNotExist()
    {
        /** @var $registry \Magento\Framework\Registry */
        $registry = $this->objectManager->get(\Magento\Framework\Registry::class);
        /** @var $taxRule \Magento\Tax\Model\Calculation\Rule */
        $taxRule = $registry->registry('_fixture/Magento_Tax_Model_Calculation_Rule');
        $this->assertNotNull($taxRule);
        $ruleId = $taxRule->getId();

        // Delete the new tax rule
        $this->assertTrue($this->taxRuleRepository->deleteById($ruleId));
        // Delete the new tax rule again, this should fail
        $this->taxRuleRepository->deleteById($ruleId);
    }

    /**
     * @magentoDbIsolation enabled
     */
    public function testSaveUpdatesExistingTaxRule()
    {
        $taxRule = $this->createTaxRuleDataObject();
        //Tax rule service call
        $taxRule = $this->taxRuleRepository->save($taxRule);
        $taxRule->setCode('updated code');
        $this->taxRuleRepository->save($taxRule);
        $retrievedRule = $this->taxRuleRepository->get($taxRule->getId());

        $this->assertEquals('updated code', $retrievedRule->getCode());
    }

    /**
     * @magentoDbIsolation enabled
     * @expectedException \Magento\Framework\Exception\CouldNotSaveException
     * @expectedExceptionMessage "code" is required. Enter and try again.
     */
    public function testSaveThrowsExceptionIsRequiredFieldsAreMissing()
    {
        $taxRule = $this->taxRuleRepository->save($this->createTaxRuleDataObject());
        $taxRule->setCode(null);

        $this->taxRuleRepository->save($taxRule);
    }

    /**
     *
     * @param Filter[] $filters
     * @param Filter[] $filterGroup
     * @param string[] $expectedRuleCodes The codes of the tax rules that are expected to be found
     *
     * @magentoDbIsolation enabled
     * @dataProvider searchTaxRulesDataProvider
     */
    public function testGetList($filters, $filterGroup, $expectedRuleCodes)
    {
        $this->setUpDefaultRules();

        /** @var \Magento\Framework\Api\SearchCriteriaBuilder $searchBuilder */
        $searchBuilder = Bootstrap::getObjectManager()
            ->create(\Magento\Framework\Api\SearchCriteriaBuilder::class);
        foreach ($filters as $filter) {
            $searchBuilder->addFilters([$filter]);
        }
        if ($filterGroup !== null) {
            $searchBuilder->addFilters($filterGroup);
        }
        $searchCriteria = $searchBuilder->create();

        $searchResults = $this->taxRuleRepository->getList($searchCriteria);
        $items = [];
        foreach ($expectedRuleCodes as $ruleCode) {
            $ruleId = $this->taxRules[$ruleCode];
            $items[] = $this->taxRuleRepository->get($ruleId);
        }

        $this->assertEquals($searchCriteria, $searchResults->getSearchCriteria());
        $this->assertEquals(count($expectedRuleCodes), $searchResults->getTotalCount());
        foreach ($searchResults->getItems() as $rule) {
            $this->assertContains($rule->getCode(), $expectedRuleCodes);
        }

        $this->tearDownDefaultRules();
    }

    public function searchTaxRulesDataProvider()
    {
        $filterBuilder = Bootstrap::getObjectManager()->create(\Magento\Framework\Api\FilterBuilder::class);

        return [
            'code eq "Default Rule"' => [
                [$filterBuilder->setField('code')->setValue('Default Rule')->create()],
                null,
                ['Default Rule'],
            ],
            'customer_tax_class_ids eq 3 AND priority eq 0' => [
                [
                    $filterBuilder->setField('customer_tax_class_ids')->setValue(3)->create(),
                    $filterBuilder->setField('priority')->setValue('0')->create(),
                ],
                [],
                ['Default Rule', 'Higher Rate Rule'],
            ],
            'code eq "Default Rule" OR code eq "Higher Rate Rule"' => [
                [],
                [
                    $filterBuilder->setField('code')->setValue('Default Rule')->create(),
                    $filterBuilder->setField('code')->setValue('Higher Rate Rule')->create(),
                ],
                ['Default Rule', 'Higher Rate Rule'],
            ],
            'code like "%Rule"' => [
                [
                    $filterBuilder->setField('code')->setValue('%Rule')->setConditionType('like')
                        ->create(),
                ],
                [],
                ['Default Rule', 'Higher Rate Rule'],
            ],
        ];
    }

    /**
     * Helper function that sets up some default rules
     */
    private function setUpDefaultRules()
    {
        $this->taxClasses = $this->taxRuleFixtureFactory->createTaxClasses([
            ['name' => 'DefaultCustomerClass', 'type' => ClassModel::TAX_CLASS_TYPE_CUSTOMER],
            ['name' => 'DefaultProductClass', 'type' => ClassModel::TAX_CLASS_TYPE_PRODUCT],
            ['name' => 'HigherProductClass', 'type' => ClassModel::TAX_CLASS_TYPE_PRODUCT],
        ]);

        $this->taxRates = $this->taxRuleFixtureFactory->createTaxRates([
            ['percentage' => 7.5, 'country' => 'US', 'region' => 42],
            ['percentage' => 7.5, 'country' => 'US', 'region' => 12], // Default store rate
        ]);

        $higherRates = $this->taxRuleFixtureFactory->createTaxRates([
            ['percentage' => 22, 'country' => 'US', 'region' => 42],
            ['percentage' => 10, 'country' => 'US', 'region' => 12], // Default store rate
        ]);

        $this->taxRules = $this->taxRuleFixtureFactory->createTaxRules([
            [
                'code' => 'Default Rule',
                'customer_tax_class_ids' => [$this->taxClasses['DefaultCustomerClass'], 3],
                'product_tax_class_ids' => [$this->taxClasses['DefaultProductClass']],
                'tax_rate_ids' => array_values($this->taxRates),
                'sort_order' => 0,
                'priority' => 0,
                'calculate_subtotal' => 1,
            ],
            [
                'code' => 'Higher Rate Rule',
                'customer_tax_class_ids' => [$this->taxClasses['DefaultCustomerClass'], 3],
                'product_tax_class_ids' => [$this->taxClasses['HigherProductClass']],
                'tax_rate_ids' => array_values($higherRates),
                'sort_order' => 0,
                'priority' => 0,
                'calculate_subtotal' => 1,
            ],
            [
                'code' => 'Highest Rate',
                'customer_tax_class_ids' => [$this->taxClasses['DefaultCustomerClass'], 3],
                'product_tax_class_ids' => [$this->taxClasses['HigherProductClass']],
                'tax_rate_ids' => array_values($higherRates),
                'sort_order' => 1,
                'priority' => 1,
                'calculate_subtotal' => 0,
            ],
        ]);

        // For cleanup
        $this->taxRates = array_merge($this->taxRates, $higherRates);
    }

    /**
     * Helper function that tears down some default rules
     */
    private function tearDownDefaultRules()
    {
        $this->taxRuleFixtureFactory->deleteTaxRules(array_values($this->taxRules));
        $this->taxRuleFixtureFactory->deleteTaxRates(array_values($this->taxRates));
        $this->taxRuleFixtureFactory->deleteTaxClasses(array_values($this->taxClasses));
    }

    /**
     * Creates Tax Rule Data Object
     *
     * @return \Magento\Tax\Api\Data\TaxRuleInterface
     */
    private function createTaxRuleDataObject()
    {
        $taxRule = $this->taxRuleFactory->create();
        $taxRule->setCode('code')
            ->setCustomerTaxClassIds([3])
            ->setProductTaxClassIds([2])
            ->setTaxRateIds([2])
            ->setPriority(0)
            ->setPosition(1);
        return $taxRule;
    }
}