<?php

namespace KupShop\OrderDiscountBundle\Tests;

use KupShop\DevelopmentBundle\Util\Tests\CartTestTrait;
use KupShop\KupShopBundle\Context\CurrencyContext;
use KupShop\KupShopBundle\Util\Price\Price;
use KupShop\OrderDiscountBundle\Entity\OrderDiscount;
use KupShop\OrderDiscountBundle\OrderDiscountLocator;
use KupShop\OrderDiscountBundle\Triggers\CouponTrigger;
use KupShop\OrderDiscountBundle\Triggers\CurrencyTrigger;
use KupShop\OrderDiscountBundle\Triggers\DiscountAppliedTrigger;
use KupShop\OrderDiscountBundle\Triggers\ForbidCombineTrigger;
use KupShop\OrderDiscountBundle\Triggers\Frontend\IsicHandler;
use KupShop\OrderDiscountBundle\Triggers\IsicTrigger;
use KupShop\OrderDiscountBundle\Triggers\LanguageTrigger;
use KupShop\OrderDiscountBundle\Triggers\PriceRangeTrigger;
use KupShop\OrderDiscountBundle\Triggers\ProductFilterTrigger;
use KupShop\OrderDiscountBundle\Triggers\ProductPiecesRangeTrigger;
use KupShop\OrderDiscountBundle\Triggers\UserFirstPurchaseTrigger;
use KupShop\OrderDiscountBundle\Triggers\UserGroupTrigger;
use KupShop\OrderDiscountBundle\Triggers\UserTypeTrigger;
use KupShop\OrderDiscountBundle\Triggers\UsesCountTrigger;
use KupShop\OrderDiscountBundle\Util\DiscountManager;
use KupShop\OrderingBundle\Entity\Purchase\ProductPurchaseItem;
use KupShop\OrderingBundle\Entity\Purchase\PurchaseState;
use KupShop\OrderingBundle\Tests\Purchase\PurchaseTestTrait;
use KupShop\OrderingBundle\Util\Purchase\PurchaseUtil;

class TriggersTest extends \DatabaseTestCase
{
    use CartTestTrait;
    use OrderTestDiscountTrait;
    use PurchaseTestTrait;

    /** @var OrderDiscountLocator */
    private $orderDiscountLocator;

    /** @var DiscountManager */
    private $discountManager;

    protected function setUp(): void
    {
        parent::setUp();

        $this->orderDiscountLocator = $this->get(OrderDiscountLocator::class);
        $this->discountManager = $this->get(DiscountManager::class);
    }

    public function testProductFilterTrigger()
    {
        $purchaseState = $this->createPurchaseState();
        $trigger = $this->orderDiscountLocator->getTrigger(ProductFilterTrigger::getType());

        $filter = '{"categories":["5"],"subcategories":"Y","parametersValuesOperator":"AND","variationsValuesOperator":"AND","discount":{"min":"","max":""},"price":{"min":"","max":""}}';

        $this->assertTrue($trigger->isApplicable($purchaseState, $this->getOrderDiscount(), json_decode($filter, true)));
    }

    public function testForbidCombineTrigger()
    {
        $orderDiscount = $this->getOrderDiscount();
        $purchaseState = $this->createPurchaseState();
        $trigger = $this->orderDiscountLocator->getTrigger(ForbidCombineTrigger::getType());

        $this->assertTrue($trigger->isApplicable($purchaseState, $orderDiscount, ['discounts' => [1]]));

        $purchaseState->addUsedDiscount(1);

        $this->assertFalse($trigger->isApplicable($purchaseState, $orderDiscount, ['discounts' => [1]]));
    }

    public function testProductPiecesRangeTrigger()
    {
        $orderDiscount = $this->getOrderDiscount();

        $purchaseState = $this->createPurchaseState([
            [
                'id_product' => 8,
                'id_variation' => null,
                'pieces' => 1,
            ],
            [
                'id_product' => 9,
                'id_variation' => null,
                'pieces' => 1,
            ],
        ]);
        $trigger = $this->orderDiscountLocator->getTrigger(ProductPiecesRangeTrigger::getType());

        $data = '{"min":"2","max":"3","total_count":"Y","filter":{"categories":["3"],"subcategories":"Y","parametersValuesOperator":"AND","variationsValuesOperator":"AND","discount":{"min":"","max":""},"price":{"min":"","max":""}}}';

        $this->assertTrue($trigger->isApplicable($purchaseState, $orderDiscount, json_decode($data, true)));

        $data = '{"min":"1","max":"6","filter":{"categories":["3"],"subcategories":"Y","parametersValuesOperator":"AND","variationsValuesOperator":"AND","discount":{"min":"","max":""},"price":{"min":"","max":""}}}';

        $this->assertTrue($trigger->isApplicable($purchaseState, $orderDiscount, json_decode($data, true)));

        $data = '{"min":"1","max":"6","filter":{"categories":["15"],"subcategories":"Y","parametersValuesOperator":"AND","variationsValuesOperator":"AND","discount":{"min":"","max":""},"price":{"min":"","max":""}}}';

        $this->assertFalse($trigger->isApplicable($purchaseState, $orderDiscount, json_decode($data, true)));
    }

    public function testDiscountAppliedTrigger()
    {
        $orderDiscount = $this->getOrderDiscount();

        $purchaseState = $this->createPurchaseState();
        $trigger = $this->orderDiscountLocator->getTrigger(DiscountAppliedTrigger::getType());
        $data = ['discounts' => [1, 2]];

        $this->assertFalse($trigger->isApplicable($purchaseState, $orderDiscount, $data));

        $purchaseState->addUsedDiscount(1);
        $this->assertFalse($trigger->isApplicable($purchaseState, $orderDiscount, $data));

        $purchaseState->addUsedDiscount(2);
        $this->assertTrue($trigger->isApplicable($purchaseState, $orderDiscount, $data));
    }

    /**
     * @dataProvider data_deliveryTrigger
     */
    public function testDeliveryTrigger(int $deliveryType, float $expectedTotalPrice): void
    {
        $this->activateDiscount([13]);

        $this->prepareCart();
        $this->setDeliveryType($deliveryType);

        $this->insertProduct(3);

        $totalPrice = $this->recalculateCart();

        $this->assertEquals($expectedTotalPrice, $totalPrice->asFloat());

        $this->checkOrderPriceIsSameAsCart();
    }

    public function testIsicTrigger()
    {
        $orderDiscount = $this->getOrderDiscount();

        $trigger = $this->orderDiscountLocator->getTrigger(IsicTrigger::getType());
        /* @var IsicHandler $handler */
        $handler = $trigger->getFrontendHandler();

        $isicPurchaseState = $this->createPurchaseState();

        $isicPurchaseState->setCustomData([
            'isic' => ['id' => 'Test',
                'name' => 'Test',
                'success' => true],
        ]);

        $handler->setPersistentData($isicPurchaseState);

        // ISIC success = true -> dostane slevu - jiz bylo zvalidovano
        $this->assertTrue($trigger->isApplicable($isicPurchaseState, $orderDiscount, [], $trigger->getHandlerData($isicPurchaseState)));

        $isicPurchaseState->setCustomData([
            'isic' => ['id' => 'Test',
                'name' => 'Test',
                'success' => false],
        ]);

        $handler->setPersistentData($isicPurchaseState);

        // ISIC success = false -> nedostane slevu
        $this->assertFalse($trigger->isApplicable($isicPurchaseState, $orderDiscount, [], $trigger->getHandlerData($isicPurchaseState)));
    }

    /**
     * @dataProvider data_triggersDiscountFilterSpec
     */
    public function testTriggersDiscountFilterSpec(
        string $type,
        int $activeDiscount,
        PurchaseState $purchaseState,
        array $expectedDiscounts,
        ?callable $callback = null,
    ) {
        $this->activateDiscount([$activeDiscount]);

        if ($callback) {
            $callback();
        }

        $trigger = $this->orderDiscountLocator->getTrigger($type);

        $discountToCheck = sqlQueryBuilder()->select('od.*')
            ->from('order_discounts', 'od');

        $discountToSubstract = sqlQueryBuilder()
            ->select('odt.id_order_discount')
            ->from('order_discounts_triggers', 'odt')
            ->andWhere(
                $trigger->getDiscountFilterSpec($purchaseState)
            );

        $discounts = sqlFetchAll($discountToCheck
            ->andWhere("od.id NOT IN ({$discountToSubstract->getSQL()})")
            ->andWhere('od.active = 1')
            ->addParameters($discountToSubstract->getParameters(), $discountToSubstract->getParameterTypes())
            ->execute(),
            'id');

        $this->assertEquals($expectedDiscounts, array_keys($discounts));
    }

    public function data_deliveryTrigger(): array
    {
        return [
            [1, 720],
            [3, 922],
        ];
    }

    public function data_triggersDiscountFilterSpec()
    {
        /** @var PurchaseUtil $purchaseUtil */
        $purchaseUtil = $this->get(PurchaseUtil::class);
        /** @var CurrencyContext $currencyContext */
        $currencyContext = $this->get(CurrencyContext::class);

        $couponPurchaseState = new PurchaseState([]);
        $couponPurchaseState->addCoupon('TEST321');

        $priceRangePurchaseState = new PurchaseState([]);
        $priceRangePurchaseState = $purchaseUtil->recalculateTotalPrices($priceRangePurchaseState);

        $priceRangePurchaseState2 = new PurchaseState([
            new ProductPurchaseItem(9, null, 1, new Price(toDecimal(1500), $currencyContext->getActive(), 0), null),
        ]);
        $priceRangePurchaseState2 = $purchaseUtil->recalculateTotalPrices($priceRangePurchaseState2);

        return [
            // user_type + prihlasenu uzivatel = mela by se vyselectit aktivni discount s ID 3
            [UserTypeTrigger::getType(),
                3,
                new PurchaseState([]),
                [3],
                function () {
                    $this->loginUser();
                }],
            // user_type + neprihlaseny uzivatel = nemela by se vyselectit zadna discounta
            [UserTypeTrigger::getType(), 3, new PurchaseState([]), []],
            // coupon + aktivni kupon v PurchaseState = mela by se vyselectit sleva s ID 1
            [CouponTrigger::getType(), 1, $couponPurchaseState, [1]],
            // coupon + zadny kupon neni aktivni = nemela by se vyselectit zadna sleva
            [CouponTrigger::getType(), 1, new PurchaseState([]), []],
            // user_first_purchase s neprihlasenym uzivatelem
            [UserFirstPurchaseTrigger::getType(), 4, new PurchaseState([]), []],
            // user_first_purchase s prihlasenym uzivatelem
            [UserFirstPurchaseTrigger::getType(),
                4,
                new PurchaseState([]),
                [4],
                function () {
                    sqlQuery('DELETE FROM orders WHERE id_user = 1');
                    $this->loginUser();
                }],
            [UserGroupTrigger::getType(), 5, new PurchaseState([]), []],
            [UserGroupTrigger::getType(),
                5,
                new PurchaseState([]),
                [5],
                function () {
                    $this->loginUser();
                }],
            // skupina uzivatele s negaci - neprihlaseny uzivatel - nema zadnou skupinu a timpadem by se sleva mela aktivovat
            [UserGroupTrigger::getType(), 784, new PurchaseState([]), [784]],
            // skupina uzivatele s negaci - prihlaseny uzivatel, kterej ma skupinu ID 1, takze by se sleva nemela aktivovat
            [UserGroupTrigger::getType(), 784, new PurchaseState([]), [], fn () => $this->loginUser()],
            // skupina uzivatele s negaci - prihlaseny uzivatel, kterej ma skupinu ID 2, takze by se sleva mela aktivovat
            [UserGroupTrigger::getType(), 784, new PurchaseState([]), [784], fn () => $this->loginUser(2)],
            // skupina uzivatele s negaci - prihlaseny uzivatel, kterej nema zadnou skupinu, takze by se sleva mela aktivovat
            [UserGroupTrigger::getType(), 784, new PurchaseState([]), [784], fn () => $this->loginUser(3)],
            // language - sleva se aktivuje protoze je aktivni jazyk cs
            [LanguageTrigger::getType(), 6, new PurchaseState([]), [6]],
            // language - sleva se neaktivuje protoze je aktivni jazyk sk
            [LanguageTrigger::getType(),
                6,
                new PurchaseState([]),
                [],
                function () {
                    $this->switchLanguage();
                }],
            // currency - sleva se aktivuje protoze je aktivni CZK
            [CurrencyTrigger::getType(), 7, new PurchaseState([]), [7]],
            // currency - sleva se neaktivuje protoze je aktivni EUR
            [CurrencyTrigger::getType(),
                7,
                new PurchaseState([]),
                [],
                function () {
                    changeCurrency('EUR');
                }],
            // currency - sleva se aktivuje protoze je aktivni EUR
            [CurrencyTrigger::getType(),
                8,
                new PurchaseState([]),
                [8],
                function () {
                    changeCurrency('EUR');
                }],
            // uses_count - sleva se aktivuje protoze max pocet pouziti je 2 a je pouzita 1x
            [UsesCountTrigger::getType(), 9, new PurchaseState([]), [9]],
            // uses_count - sleva se neaktivuje protoze max pocet pouziti je 2 a je pouzita 2x
            [UsesCountTrigger::getType(),
                9,
                new PurchaseState([]),
                [],
                function () {
                    $this->updateSQL('order_discounts', ['uses_count' => 2], ['id' => 9]);
                }],
            // price_range - sleva se neaktivuje, protoze je minimalne od ceny 1000
            [PriceRangeTrigger::getType(), 10, $priceRangePurchaseState, []],
            // price_range - sleva se aktivuje protoze je minimalne od ceny 1000
            [PriceRangeTrigger::getType(), 10, $priceRangePurchaseState2, [10]],
        ];
    }

    private function getOrderDiscount(): OrderDiscount
    {
        $orderDiscount = $this->selectSQL('order_discounts', ['id' => 1])->fetch();

        return $this->discountManager->getOrderDiscount($orderDiscount);
    }

    private function loginUser($id_user = null)
    {
        $user = \User::createFromId($id_user ?: 1);
        $user->login(null);
        $user->activateUser();
    }

    protected function getDataSet()
    {
        return $this->getJsonDataSetFromFile('data.json');
    }
}
