<?php

use Heureka\ShopCertification;
use KupShop\DevelopmentBundle\Util\Tests\CartTestTrait;
use KupShop\DevelopmentBundle\Util\Tests\RequestCookiesTrait;
use KupShop\KupShopBundle\Config;
use KupShop\KupShopBundle\Context\ContextManager;
use KupShop\KupShopBundle\Context\CurrencyContext;
use KupShop\KupShopBundle\Context\UserContext;
use KupShop\MarketingBundle\EventListener\OrderConversionListener;
use KupShop\OrderingBundle\Util\CartFactory;
use KupShop\OrderingBundle\Util\Order\OrderInfo;
use KupShop\OrderingBundle\Util\Purchase\PurchaseUtil;

class CartTest extends DatabaseTestCase
{
    use CartTestTrait;
    use RequestCookiesTrait;

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

        Delivery::clearCache();
    }

    public function testSharedAndNonSharedCart(): void
    {
        $cartFactory = $this->get(CartFactory::class);

        $sharedCartFromPurchaseUtil = $this->get(PurchaseUtil::class)->getCart();
        $sharedCartFromDI = $this->get(\KupShop\OrderingBundle\Cart::class);

        $this->assertSame(spl_object_id($sharedCartFromDI), spl_object_id($sharedCartFromPurchaseUtil), 'Rikam si o shared Cart, takze musi byt vzdy stejny ID objektu v pameti');

        $nonSharedCartFromFactory = $cartFactory->create();

        $this->assertNotSame(spl_object_id($sharedCartFromDI), spl_object_id($nonSharedCartFromFactory), 'Shared a non-shared cart nesmi byt stejny');
        $this->assertNotSame(spl_object_id($nonSharedCartFromFactory), spl_object_id($cartFactory->create()), 'Mam non-shared cart a vytvarim dalsi non-shared cart, ktery nesmi byt stejny jako ten predesly');
    }

    public function testNonSharedCartDependencies(): void
    {
        $cart = $this->get(CartFactory::class)->create();

        $requiredProperties = $this->getRequiredProperties($cart);

        $reflection = new ReflectionClass($cart);

        // test that all required properties are not null
        foreach ($reflection->getProperties() as $property) {
            if (!in_array($property->getName(), $requiredProperties)) {
                continue;
            }

            $property->setAccessible(true);

            $this->assertNotNull($property->getValue($cart), '#[Required] property nemuze byt null pokud se vytvari non-shared kosik pres CartFactory!');
        }
    }

    // otestuje, ze se objednavka vytvori po novu pres PurchaseState
    /** @dataProvider data_purchaseStateProvider */
    public function testOrderSubmitFromPurchaseState($applyModule): void
    {
        $applyModule();

        $this->addModule(Modules::ORDERS, Modules::SUB_ORDERS_FROM_PURCHASE_STATE);

        $this->prepareCart();
        $this->insertProduct(1, 16, 3);
        $this->insertProduct(10);

        $order = $this->submitOrder();

        $this->removeModule(Modules::ORDERS, Modules::SUB_ORDERS_FROM_PURCHASE_STATE);

        $this->assertInstanceOf(Order::class, $order);

        $this->assertCount(2, $order->items);
        $this->assertEquals(4009, $order->getTotalPrice()->getPriceWithVat()->asFloat());
        $this->assertEqualsWithDelta($this->cart->totalPricePay->asFloat(), $this->order->total_price->asFloat(), 0.01);

        $this->assertEquals(OrderInfo::ORDER_SOURCE_SHOP, $order->source);
    }

    /** @dataProvider data_DeliveryPriceNotOnFirstStep */
    public function testCartDeliveryPriceNotOnFirstStep($goBack, $expectedPrice)
    {
        $this->createCart();
        $this->insertProduct(1, 16, 1);
        $this->setDeliveryType(2);

        $this->visitStep('delivery');

        if ($goBack) {
            $this->cart->actualStep = 'cart';
        }

        $this->cart->createFromDB();

        $this->assertEquals($expectedPrice, $this->cart->totalPricePay->asFloat());
    }

    public function data_DeliveryPriceNotOnFirstStep()
    {
        yield [true, 800];
        yield [false, 870];
    }

    /** @dataProvider data_purchaseStateProvider */
    public function testOrderingRegistrationInForeignCurrency($applyModule)
    {
        $applyModule();

        $order = $this->getContextManager()->activateContexts(
            [
                CurrencyContext::class => 'EUR',
                UserContext::class => ContextManager::FORCE_EMPTY,
            ],
            function () {
                $this->createCart();

                $this->insertProduct(1, 16, 1);
                $this->setInvoice();
                $this->setDeliveryType();

                $this->cart->register = true;
                $this->cart->createFromDB();

                return $this->cart->submitOrder();
            }
        );

        $this->assertEquals('EUR', $order->currency);
    }

    /**
     * @dataProvider data_orderingRegistration
     */
    public function testOrderingRegistration($newsletterEnable, $getNewsExpect)
    {
        $settings = Settings::getDefault();
        $settings->newsletter['enable'] = $newsletterEnable;

        $this->createCart();

        $this->insertProduct(1, 16, 1);
        $this->setInvoice();
        $this->setDeliveryType();

        $this->cart->register = true;
        $this->cart->createFromDB();
        $order = $this->cart->submitOrder();

        // createFromLogin to avoid cached user when using getCurrentUser
        $user = User::createFromLogin('test@wpj.cz');

        $this->assertInstanceOf(Order::class, $order);
        $this->assertEquals('800', $order->total_price->asFloat());
        $this->assertEquals($user['email'], 'test@wpj.cz');
        $this->assertEquals($user['get_news'], $getNewsExpect);
    }

    public function data_orderingRegistration()
    {
        return [
            ['Y', 'N'],
            ['N', 'Y'],
        ];
    }

    /** @dataProvider data_purchaseStateProvider */
    public function testGetPayments($applyModule)
    {
        $applyModule();

        $this->createCart();
        $this->cart->createFromDB();
        $payments = $this->cart->getPayments();

        $this->assertEquals(12, $payments[1]['price']['value_with_vat']->asFloat());
        $this->assertEquals(round(12 / 1.21, 4), $payments[1]['price']['value_without_vat']->asFloat());
        $this->assertEquals(24, $payments[2]['price']['value_with_vat']->asFloat());
        $this->assertEquals(36, $payments[3]['price']['value_with_vat']->asFloat());
        $this->assertEquals(48, $payments[4]['price']['value_with_vat']->asFloat());
    }

    /** @dataProvider data_purchaseStateProvider */
    public function testAddItemToCart($applyModule)
    {
        $applyModule();

        $this->loginUser(1);

        $this->createCart();
        $this->insertProduct(1, 16);

        $this->cart->createFromDB();

        $this->assertEquals(800, $this->cart->totalPriceWithVat->asFloat());

        $this->checkOrderPriceIsSameAsCart();
    }

    /** @dataProvider data_purchaseStateProvider */
    public function testAddItemToCartNegativePieces($applyModule)
    {
        $applyModule();

        $this->createCart();
        $item = ['id_product' => 1, 'id_variation' => 16, 'pieces' => -1];
        $itemId = $this->cart->addItem($item);
        $this->assertEmpty($itemId);
        $item = ['id_product' => 1, 'id_variation' => 16, 'pieces' => 0];
        $itemId = $this->cart->addItem($item);
        $this->assertEmpty($itemId);

        $this->cart->createFromDB();

        $this->assertEquals(0, $this->cart->totalPriceWithVat->asFloat());
        $this->assertEmpty($this->cart->products);
    }

    /** @dataProvider data_purchaseStateProvider */
    public function testCheckItemsInCartNotInStore($applyModule)
    {
        $applyModule();

        // order_not_in_store == Y
        $this->createCart();
        $this->insertProduct(1, 16, 2);
        $this->cart->createFromDB();
        $return = $this->cart->checkCartItems();

        $this->assertEquals(null, $return);
    }

    /** @dataProvider data_purchaseStateProvider */
    public function testCheckItemsInCartInStore($applyModule)
    {
        $applyModule();

        global $dbcfg, $cfg;

        $cfg['Modules']['products_suppliers'] = false;

        $dbcfg->order_not_in_store = 'N';
        // order_not_in_store == N
        $this->createCart();
        $this->insertProduct(1, 16, 2);
        $this->cart->createFromDB();

        $this->expectException(\KupShop\OrderingBundle\Exception\CartValidationException::class);

        $return = $this->cart->checkCartItems();

        $this->assertEquals(30, $return);
    }

    public function testCheckItemsInCartVisibility()
    {
        $this->createCart();
        $this->insertProduct(4, 1, 1);
        $this->cart->createFromDB();
        // cart product variation is visible
        $return = $this->cart->checkCartItems();
        $this->assertEquals(null, $return);

        $qb = sqlQueryBuilder()->update('products_variations')
            ->directValues(['figure' => 'N'])
            ->andWhere('id=1')->execute();
        // cart product variation is not visible
        $return = $this->cart->checkCartItems();
        $this->assertEquals(31, $return);
    }

    /** @dataProvider data_purchaseStateProvider */
    public function testCheckItemsInCartInSupplierStore($applyModule)
    {
        $applyModule();

        global $dbcfg, $cfg;

        $cfg['Modules']['products_suppliers'] = ['delivery_time' => -2];
        $dbcfg->order_not_in_store = 'N';
        // order_not_in_store == N
        $this->createCart();
        $this->insertProduct(1, 16, 2);
        $this->cart->createFromDB();
        $return = $this->cart->checkCartItems();

        $this->assertEquals(null, $return);
    }

    public function getDataSet()
    {
        return $this->getJsonDataSet(
            /* @lang JSON */ '
        {"products_of_suppliers" : [
        {
            "id":1,
            "id_product":1,
            "id_variation":16,
            "id_supplier":1,
            "code":123,
            "in_store":2
        }
        ]}'
        );
    }

    /** @dataProvider data_purchaseStateProvider */
    public function testAddItemToCartInEuro($applyModule)
    {
        $applyModule();

        $this->loginUser(1);

        $c = $this->get(\KupShop\KupShopBundle\Context\CurrencyContext::class);
        $c->activate('EUR');
        $currency = $this->get(\KupShop\KupShopBundle\Context\CurrencyContext::class)->getActive();
        $currency->setRate(toDecimal(26));
        $currency->setPriceRoundOrder('0');
        $currency->setPriceRound('0');

        $this->createCart();
        $this->insertProduct(1, 16);

        $this->cart->createFromDB();

        $this->assertEquals(800 / 26, $this->cart->totalPricePay->asFloat(), '', 0.01);

        $this->checkOrderPriceIsSameAsCart();

        changeCurrency();
    }

    /** @dataProvider data_purchaseStateProvider */
    public function testAddItemToCartInEuroWithRounding($applyModule)
    {
        $applyModule();

        $this->loginUser(1);

        $context = $this->get(\KupShop\KupShopBundle\Context\CurrencyContext::class);
        $context->activate('EUR');
        $currency = $context->getActive();
        $currency->setRate(26);
        $currency->setPriceRoundOrder('100');

        $this->createCart();
        $this->insertProduct(1, 16);

        $this->cart->createFromDB();

        $this->assertEquals(round(800 / 26), $this->cart->totalPricePay->asFloat(), '', 0.01);

        $this->checkOrderPriceIsSameAsCart();
    }

    public function testSendZboziConversion()
    {
        // Prepare settings
        Settings::getDefault()->analytics = [
            'zbozi_cz_feedback' => [
                'ID' => '129931',
                'secret_key' => 'CXIGnEMhnOrP32au62B7k5tgAOhiTiyK',
            ],
        ];

        // Restart container to force reload event listeners
        $this->tearDownContainer();
        $this->restoreDefaultCtrl();

        $zboziClientMock = $this->getMockBuilder(\Soukicz\Zbozicz\Client::class)
            ->setMethods(['sendOrder'])
            ->setConstructorArgs(['129931', 'CXIGnEMhnOrP32au62B7k5tgAOhiTiyK'])
            ->getMock();

        $data = [];
        $zboziClientMock->expects($this->once())->method('sendOrder')->with(
            $this->callback(function (Soukicz\Zbozicz\Order $order) use (&$data, $zboziClientMock) {
                $data = json_decode_strict($zboziClientMock->createRequest($order)->getBody(), true);

                return true;
            })
        );

        $requestStack = $this->getRequestStackWithCookies('cookie-bar', 'analytics_storage');

        $listenerMock = $this->getMockBuilder(OrderConversionListener::class)
            ->setMethods(['getZboziClient'])
            ->setConstructorArgs(
                [
                    $this->get(\KupShop\KupShopBundle\Util\Price\PriceUtil::class),
                    $requestStack,
                    $this->get(\KupShop\KupShopBundle\Context\CountryContext::class),
                    $this->get(\KupShop\KupShopBundle\Context\CurrencyContext::class),
                ]
            )
            ->getMock();
        $listenerMock->method('getZboziClient')->willReturn($zboziClientMock);
        $listenerMock->setDomainContext(\KupShop\KupShopBundle\Util\Contexts::get(\KupShop\KupShopBundle\Context\DomainContext::class));
        $this->set(OrderConversionListener::class, $listenerMock);

        $this->loginUser(2);

        $context = $this->get(\KupShop\KupShopBundle\Context\CurrencyContext::class);
        $context->activate('EUR');

        $this->createCart();
        $this->insertProduct(1, 16);

        $this->cart->createFromDB();

        $order = $this->cart->submitOrder();

        $this->assertInstanceOf('\Order', $order);

        $this->assertNotEmpty($data);
        $this->assertEquals([
            'PRIVATE_KEY' => 'CXIGnEMhnOrP32au62B7k5tgAOhiTiyK',
            'orderId' => $order->order_no,
            'email' => 'wpj@wpj.cz',
            'deliveryType' => 'GLS',
            'deliveryPrice' => 104,
            'paymentType' => 'dobírka PPL',
            'cart' => [
                [
                    'productName' => 'DeWALT DeWALT DCD780C2 aku vrtačka',
                    'itemId' => '9',
                    'unitPrice' => 6000,
                    'quantity' => '1',
                ],
                [
                    'productName' => 'Nike NIKE Capri LACE Test Dlouheho Nazvu Produktu Test Dlouheho Produktu Tes (Velikost: 45)',
                    'itemId' => '1_16',
                    'unitPrice' => 800,
                    'quantity' => '1',
                ],
            ],
            'sandbox' => false,
            'otherCosts' => 0,
        ], $data);
    }

    /** @dataProvider data_purchaseStateProvider */
    public function testHeurekaConversionOptions($applyModule)
    {
        $applyModule();
        /** @var OrderConversionListener $listener */
        $listener = $this->get(OrderConversionListener::class);
        /** @var \KupShop\KupShopBundle\Context\CountryContext $countryContext */
        $countryContext = $this->get(\KupShop\KupShopBundle\Context\CountryContext::class);
        $languageContext = $this->get(\KupShop\KupShopBundle\Context\LanguageContext::class);

        // Prepare settings
        Settings::getDefault()->analytics = [
            'heureka_overeno' => [
                'ID' => 'dksag41s2fdsd10vs',
            ],
        ];
        $this->assertEquals(
            ['heureka_id' => 'dksag41s2fdsd10vs', 'options' => ['service' => ShopCertification::HEUREKA_CZ]],
            $listener->getHeurekaOptions()
        );

        $languageContext->activate('sk');
        Settings::getDefault()->analytics = [
            'heureka_overeno' => [
                'ID' => 'dksag41s2fdsd10vs',
            ],
        ];
        $this->assertEquals(
            ['heureka_id' => 'dksag41s2fdsd10vs', 'options' => ['service' => ShopCertification::HEUREKA_SK]],
            $listener->getHeurekaOptions()
        );

        $cfg = Config::get();
        $languageContext->activate('cs');
        $cfg['Modules']['translations'] = false;
        \Settings::clearCache(true);
        Settings::getDefault()->analytics = [
            'heureka_overeno' => [
                'ID' => 'dksag41s2fdsd10vs',
            ],
        ];
        $this->assertEquals(
            ['heureka_id' => 'dksag41s2fdsd10vs', 'options' => ['service' => ShopCertification::HEUREKA_CZ]],
            $listener->getHeurekaOptions()
        );

        Settings::getDefault()->analytics = [
            'heureka_overeno' => [
                'ID' => 'dksag41s2fdsd10vs',
                'ID_SK' => 'SKdksag41s2fdsd10vsSK',
            ],
        ];
        $languageContext->activate('sk');
        $countryContext->activate('SK');
        Settings::getDefault()->analytics = [
            'heureka_overeno' => [
                'ID' => 'dksag41s2fdsd10vs',
                'ID_SK' => 'SKdksag41s2fdsd10vsSK',
            ],
        ];
        $this->assertEquals(
            ['heureka_id' => 'SKdksag41s2fdsd10vsSK', 'options' => ['service' => ShopCertification::HEUREKA_SK]],
            $listener->getHeurekaOptions()
        );
    }

    /** @dataProvider data_purchaseStateProvider */
    public function testFreeDelivery($applyModule)
    {
        $applyModule();

        sqlQuery('UPDATE delivery_type SET price_dont_countin_from=800 WHERE id=4');

        $this->createCart();
        $this->setInvoice();
        $this->insertProduct(1, 16);
        $this->setDeliveryType(4);

        $this->checkOrderPriceIsSameAsCart();

        $this->assertEquals('800', $this->cart->totalPricePay->asFloat());
    }

    /** @dataProvider data_purchaseStateProvider */
    public function testFreeDeliveryForUnregisteredWithUser($applyModule)
    {
        $applyModule();

        // pro neregistrované je nastaveno 800, ale melo by platit i pro registrovane.
        sqlQuery('UPDATE delivery_type SET price_dont_countin_from=800 WHERE id=4');

        $user = User::createFromId(1);
        $user->activateUser();

        $this->createCart();
        $this->insertProduct(1, 16);
        $this->setDeliveryType(4);

        $this->checkOrderPriceIsSameAsCart();

        $this->assertEquals('800', $this->cart->totalPricePay->asFloat());
    }

    public function testFreeDeliveryForRegisteredWithoutUser()
    {
        // Pro neregistrované není nic nastaveno, pro registrované od 400, tzn tento neni registrovany a ma se mu pricist doprava
        sqlQuery('UPDATE delivery_type SET price_dont_countin_from_reg=400 WHERE id=4');

        $this->createCart();
        $this->setInvoice();
        $this->insertProduct(1, 16);
        $this->setDeliveryType(4);
        $this->visitStep('delivery');
        $this->checkOrderPriceIsSameAsCart();

        $this->assertEquals('904', $this->cart->totalPricePay->asFloat());
    }

    /** @dataProvider data_purchaseStateProvider */
    public function testFreeDeliveryForRegisteredWithtUser($applyModule)
    {
        $applyModule();
        // Pro neregistrované není nic nastaveno, pro registrované od 400, tzn tento neni registrovany a nema se mu pricist doprava
        sqlQuery('UPDATE delivery_type SET price_dont_countin_from_reg=400 WHERE id=4');

        $user = User::createFromId(1);
        $user->activateUser();

        $this->createCart();
        $this->insertProduct(1, 16);
        $this->setDeliveryType(4);
        $this->visitStep('delivery');
        $this->checkOrderPriceIsSameAsCart();

        $this->assertEquals('800', $this->cart->totalPricePay->asFloat());
    }

    /** @dataProvider data_purchaseStateProvider */
    public function testCartClean($applyModule)
    {
        $applyModule();
        // vytvoříme nový košík, který by neměl být smazán - ostatní jsou staršího data, tak dojde k jejich smazání
        $this->createCart();
        $this->insertProduct(1, 16);

        $cl = new \KupShop\OrderingBundle\EventListener\CronListener();
        $cl->cartCleanup();
        $this->assertTableRowCount('cart', 2);
    }

    /** @dataProvider data_purchaseStateProvider */
    public function testDeliveryPrice(callable $applyModule): void
    {
        $applyModule();

        $cart = $this->prepareCart();
        $cart->setTransport(3);
        $product = [
            'id_product' => 7,
            'id_variation' => 8,
            'pieces' => 1,
        ];
        $cart->addItem($product);

        $cart->createFromDB();

        $order = $cart->submitOrder();
        $price = $order->getTotalPrice();

        $this->assertEqualsWithDelta(922.0, $price->getPriceWithVat()->asFloat(), 1);
    }
}
