<?php

namespace KupShop\WatchdogBundle\Tests;

use KupShop\CatalogBundle\ProductList\ProductCollection;
use KupShop\DevelopmentBundle\MailArchive;
use KupShop\DevelopmentBundle\Util\Tests\RequestSessionTrait;
use KupShop\WatchdogBundle\Util\Watchdog;
use Query\Operator;

class WatchdogTest extends \DatabaseTestCase
{
    use RequestSessionTrait;

    protected $backupGlobalsBlacklist = ['dbCon', 'cfg'];

    /** @var Watchdog */
    public $watchdog;

    /**
     * @var WatchdogMock
     */
    public $watchdogMock;

    /**
     * @var MailArchive
     */
    public $mailArchive;

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

        $this->prepareRequestStackSession();

        $this->watchdog = $this->get(\KupShop\WatchdogBundle\Util\Watchdog::class);
        $this->watchdogMock = new WatchdogMock(
            $this->get(\Symfony\Component\HttpFoundation\RequestStack::class),
            $this->get(\KupShop\KupShopBundle\Context\ContextManager::class),
            $this->get(\KupShop\KupShopBundle\Email\WatchdogEmail::class)
        );

        $this->mailArchive = $this->get(MailArchive::class);
        $this->mailArchive->clearArchive();
    }

    public function testGenerateUserWatchdogDoesNotSendMailOnEmptyUserWatches()
    {
        $this->watchdog->generateUserWatchdog(4);
        $this->assertEmpty($this->mailArchive->getArchive());
    }

    public function testGenerateUserWatchdogSendsNotification()
    {
        $this->watchdog->generateUserWatchdog(1);

        $mailArchive = $this->get(MailArchive::class);
        $mails = $mailArchive->getArchive();
        $this->assertTrue(count($mails) > 0);
        $message = $mailArchive->getLast();
        $this->assertContains('Hlídané zboží můžete nyní objednat', $message['subject']);

        // assert users watchdog was deleted
        $userWatchdog = sqlQueryBuilder()
            ->select('*')->from('products_watchdog')
            ->where(Operator::equals(['id_user' => 1]))
            ->execute()->fetchAll();

        $this->assertEmpty($userWatchdog);
    }

    public function testDoNotSendWatchdogToBlockedUser()
    {
        $this->watchdog->generateUserWatchdog(5);

        $mailArchive = $this->get(MailArchive::class);
        $message = $mailArchive->getLast();
        $this->assertEmpty($message);
    }

    public function testSendWatchdogToNotRegisteredUser()
    {
        $this->watchdog->generateUserWatchdog(6);

        $mailArchive = $this->get(MailArchive::class);
        $message = $mailArchive->getLast();
        $this->assertNotEmpty($message);
        $this->assertEquals('newsletter@wpj.cz', $message['to']);
    }

    /**
     * @dataProvider dataSet_testGenerateUserWatchdogSendsCorrectProducts
     */
    public function testGenerateUserWatchdogSendsCorrectProducts(int $userId, array $expectedProductIds)
    {
        $sendUserNotificationCalled = false;
        $this->watchdogMock->mock(
            'sendUserNotification',
            function (\User $user, ProductCollection $products) use (
                &$sendUserNotificationCalled,
                $expectedProductIds
            ) {
                $this->assertCount(count($expectedProductIds), $products);
                $this->assertSame($expectedProductIds, $products->getKeys());
                $sendUserNotificationCalled = true;
            }
        );

        $this->watchdogMock->generateUserWatchdog($userId);

        $this->assertTrue($sendUserNotificationCalled);
    }

    public function dataSet_testGenerateUserWatchdogSendsCorrectProducts(): array
    {
        return [
            // Hlidam cely produkt a 3 varianty byly naskladneny
            [1, ['2/9', '2/10', '2/12']],
            // Hlidam pouze konkretni varianty a jedna varianta byla naskladnena
            [3, ['4/1']],
        ];
    }

    /**
     * @dataProvider dataSet_testUpdateInStore
     */
    public function testUpdateInStore($userId, $expectedResult)
    {
        $this->watchdog->updateLastInStore($userId);

        $result = sqlQueryBuilder()
            ->select('last_in_store')
            ->from('products_watchdog')
            ->execute()->fetchAllAssociative();

        $this->assertSame($expectedResult, array_column($result, 'last_in_store'));
    }

    public function dataSet_testUpdateInStore(): array
    {
        return [
            [null, [9, 9, 5, 0, 9, 9, 5, 5, 0, 0]],
            [1, [9, null, null, null, null, null, null, null, null, null]],
            [2, [null, 9, null, null, null, null, null, null, null, null]],
            [3, [null, null, 5, 0, null, null, null, null, null, null]],
        ];
    }

    public function testGenerateWatchdogsCallsGenerateUserWatchdogForEveryMatchedUser()
    {
        $counter = 0;
        $this->watchdogMock->mock('generateUserWatchdog', function () use (&$counter) {
            ++$counter;
        });

        $this->watchdogMock->generateWatchdogs();
        $this->assertSame(7, $counter);
    }

    public function testAddProductWatchdog()
    {
        $this->watchdog->addWatchdog(1, 3);

        $this->assertCount(1, sqlQuery('SELECT * FROM products_watchdog WHERE id_user=1 AND id_product=3')->fetchAll());
    }

    public function testDropProductWatchdog()
    {
        $this->assertCount(1, sqlQuery('SELECT * FROM products_watchdog WHERE id_user = 2 AND id_product = 2')->fetchAll());

        $this->watchdog->dropWatchdog(2, 2);

        $this->assertEmpty(sqlQuery('SELECT * FROM products_watchdog WHERE id_user = 2 AND id_product = 2')->fetchAll());
    }

    public function testDropVariationWatchdog()
    {
        $this->assertCount(2, sqlQuery('SELECT * FROM products_watchdog WHERE id_user = 3 AND id_product = 4')->fetchAll());

        $this->watchdog->dropWatchdog(3, 4, 1);

        $this->assertCount(1, sqlQuery('SELECT * FROM products_watchdog WHERE id_user = 3 AND id_product = 4')->fetchAll());
    }

    /** @dataProvider data_testDropProductWatchdogPartial */
    public function testDropProductWatchdogPartial(array $watchdogInfo, array $expectedBefore, ?array $expectedAfter, array $dropParts): void
    {
        [$userId, $productId, $variationId] = $watchdogInfo;
        [$availability, $price] = $dropParts;

        $watchdog = $this->watchdog->getWatchdog($userId, $productId, $variationId);

        // stav pred dropnutim
        $this->assertEquals($expectedBefore, [$watchdog['availability'], $watchdog['price']]);

        // zavolat drop
        $this->watchdog->dropWatchdog($userId, $productId, $variationId, $availability, $price);

        $watchdog = $this->watchdog->getWatchdog($userId, $productId, $variationId);

        // stav po dropnuti
        if ($expectedAfter === null) {
            $this->assertEmpty($watchdog);
        } else {
            $this->assertEquals($expectedAfter, [$watchdog['availability'], $watchdog['price']]);
        }
    }

    public function data_testDropProductWatchdogPartial(): array
    {
        return [
            // chci smazat pouze watchdog pro dostupnost a cenu zachovat
            [[8, 4, 1], [1, 1000], [0, 1000], [true, false]],
            // chci smazat pouze cenu a dostupnost zachovat
            [[8, 4, 1], [1, 1000], [1, null], [false, true]],
            // hlidani dostupnosti je vypnute a chci vypnout hlidani ceny - mel by se smazat cely watchdog
            [[9, 1, 16], [0, 100], null, [false, true]],
            // hlidani dostupnosti je vypnute a chci vypnout hlidani ceny a chybi nastavena varianta - mel by se smazat cely watchdog
            [[9, 1, null], [0, 100], null, [false, true]],
            // smazat jak hlidani ceny, tak hlidani dostupnosti
            [[8, 4, 1], [1, 1000], null, [true, true]],
        ];
    }

    public function testAddPriceWatchdog(): void
    {
        $this->watchdog->addWatchdog(1, 3, null, false, 100);

        $watchdogPrice = $this->watchdog->getWatchdog(1, 3)['price'];

        $this->assertEquals(100, $watchdogPrice);
    }

    public function testAddPriceWatchdogUpdated(): void
    {
        // pridam watchdog na cenu
        $this->watchdog->addWatchdog(1, 3, null, false, 100);

        $watchdog = $this->watchdog->getWatchdog(1, 3);

        $this->assertEquals([0, 100], [$watchdog['availability'], $watchdog['price']], 'Zkontrolovat, ze se zapnulo hlidani ceny, ale nezapnulo se hlidani skladu');

        // pridat watchdog znovu - tentokrat na sklad
        $this->watchdog->addWatchdog(1, 3);

        $watchdog = $this->watchdog->getWatchdog(1, 3);

        $this->assertEquals([1, 100], [$watchdog['availability'], $watchdog['price']], 'Zkontrolovat, ze se zapnulo hlidani skladu');
    }

    public function testPriceWatchdogNotificationIsSent(): void
    {
        $this->watchdog->generateUserWatchdog(7);

        $message = $this->mailArchive->getLast();

        $this->assertNotEmpty(
            $message,
            'E-mail s notifikaci byl odeslan'
        );

        $watchdog = sqlQueryBuilder()
            ->select('*')
            ->from('products_watchdog')
            ->where(Operator::equals(['id_user' => 7]))
            ->execute()->fetchAssociative();

        $this->assertFalse($watchdog, 'Zkontrolovat, ze watchdog byl uspesne smazan');
    }

    public function testPriceWatchdogWithInStoreWatchdogNotificationIsSent(): void
    {
        $this->watchdog->generateUserWatchdog(8);
        $this->watchdog->updateLastInStore(8);

        $this->assertNotEmpty(
            $this->mailArchive->getLast(),
            'Otestovat, ze e-mail s notifikaci byl odeslan'
        );

        $watchdog = sqlQueryBuilder()
            ->select('*')
            ->from('products_watchdog')
            ->where(Operator::equals(['id_user' => 8]))
            ->execute()->fetchAssociative();

        $this->assertEquals(
            // odeslal se watchdog na dostupnost, ale na cenu furt zustal
            [1000, 5],
            [$watchdog['price'], $watchdog['last_in_store']],
            'Zkontrolovat, ze watchdog nebyl smazan, protoze byl odeslan watchdog na cenu, ale na sklad musi furt zustat'
        );
    }

    public function testPriceWatchdogIsNotSent(): void
    {
        $this->watchdog->generateUserWatchdog(9);

        $this->assertEmpty(
            $this->mailArchive->getLast(),
            'Otestovat, ze e-mail s notifikaci nebyl odeslan'
        );
    }

    public function testDontRemoveUserWhenDontHaveEnoughtItems()
    {
        // Add users to watchdog
        $this->watchdog->addWatchdog(1, 3);
        $this->watchdog->addWatchdog(2, 3);
        $this->watchdog->addWatchdog(3, 3);
        $this->watchdog->addWatchdog(4, 3);
        $userWatchdog = sqlQueryBuilder()
            ->select('count(id_user) as id_user')->from('products_watchdog')
            ->where(Operator::equals(['id_product' => 3]))
            ->execute()->fetchAll();
        $this->assertEquals('4', $userWatchdog[0]['id_user']);

        // Need one item in_store on product_id 3
        sqlQuery('UPDATE products SET in_store = 1 WHERE id = 3');
        $count = sqlQuery('SELECT in_store FROM products WHERE id = 3')->fetchAll();
        $this->assertEquals('1', $count['0']['in_store']);

        // Ask if have enought products
        $this->watchdog->generateUserWatchdog(1);
        $userWatchdog = sqlQueryBuilder()
            ->select('count(id_user) as id_user')->from('products_watchdog')
            ->where(Operator::equals(['id_product' => 3]))
            ->execute()->fetchAll();
        $this->assertEquals('4', $userWatchdog[0]['id_user']);
    }

    public function testDropWatchdogThrows()
    {
        $this->expectException(\TypeError::class);

        $this->watchdog->dropWatchdog(null);
    }

    public function testAddVariationWatchdog()
    {
        $this->watchdog->addWatchdog(1, 4, 1);

        $this->assertCount(1, sqlQuery(
            'SELECT * FROM products_watchdog WHERE id_user=1 AND id_product=4 AND id_variation=1'
        )->fetchAll());
    }

    public function testIsWatchdogThrowsOnInvalidArgument()
    {
        $this->expectException(\TypeError::class);

        $this->watchdog->isWatchdog(1, null);
    }

    /**
     * @dataProvider dataSet_testIsWatchdog
     */
    public function testIsWatchdog($expected, array $args)
    {
        $this->assertSame($expected, call_user_func_array([$this->watchdog, 'isWatchdog'], $args));
    }

    public function dataSet_testIsWatchdog(): array
    {
        return [
            [true, [1, 2]],
            [true, [3, 4, 1]],
            [false, [3, 4]],
            [false, [1, 1]],
            [false, [1, 1, 1]],
        ];
    }

    public function sendMailMock($from, $for, $subject, $message, $contentType)
    {
        $this->emailSent = true;

        SendMail($from, $for, $subject, $message, $contentType);
    }

    public function getDataSet()
    {
        return $this->getJsonDataSetFromFile();
    }
}

class WatchdogMock extends Watchdog
{
    use \MockingTrait;

    public function updateLastInStore($id_user = null)
    {
        return $this->call(__FUNCTION__, func_get_args());
    }

    public function generateUserWatchdog($id_user, bool $contextsByUser = true)
    {
        return $this->call(__FUNCTION__, func_get_args());
    }

    public function sendUserNotification($user, $products)
    {
        return $this->call(__FUNCTION__, func_get_args());
    }
}
