<?php declare(strict_types=1);

namespace Shopware\Storefront\Test\Framework\Seo;

use Doctrine\DBAL\ArrayParameterType;
use Doctrine\DBAL\Connection;
use PHPUnit\Framework\TestCase;
use Shopware\Core\Defaults;
use Shopware\Core\Framework\Api\Util\AccessKeyHelper;
use Shopware\Core\Framework\Context;
use Shopware\Core\Framework\DataAbstractionLayer\Doctrine\FetchModeHelper;
use Shopware\Core\Framework\DataAbstractionLayer\EntityRepository;
use Shopware\Core\Framework\Log\Package;
use Shopware\Core\Framework\Test\TestCaseBase\IntegrationTestBehaviour;
use Shopware\Core\Framework\Test\TestCaseBase\QueueTestBehaviour;
use Shopware\Core\Framework\Test\TestDataCollection;
use Shopware\Core\Framework\Uuid\Uuid;
use Shopware\Core\Test\TestDefaults;
use Shopware\Storefront\Framework\Seo\SeoUrlRoute\NavigationPageSeoUrlRoute;

/**
 * @internal
 *
 * @group slow
 * @group skip-paratest
 */
#[Package('buyers-experience')]
class NavigationPageSeoUrlTest extends TestCase
{
    use IntegrationTestBehaviour;
    use QueueTestBehaviour;

    private EntityRepository $salesChannelRepository;

    private EntityRepository $categoryRepository;

    private Connection $connection;

    protected function setUp(): void
    {
        $this->connection = $this->getContainer()->get(Connection::class);
        $this->categoryRepository = $this->getContainer()->get('category.repository');
        $this->salesChannelRepository = $this->getContainer()->get('sales_channel.repository');
    }

    public function testGenerateForNewCategories(): void
    {
        $ids = new TestDataCollection();

        $categories = [
            ['id' => $ids->create('root'), 'name' => 'root'],
        ];

        $this->categoryRepository->create($categories, Context::createDefaultContext());

        $urls = $this->getSeoUrls($ids->getList(['root']), null);
        static::assertEmpty($urls);

        $this->createSalesChannel($ids->create('sales-channel'), $ids->get('root'));

        $categories = [
            ['id' => $ids->create('a'), 'name' => 'a', 'parentId' => $ids->get('root')],
            ['id' => $ids->create('b'), 'name' => 'b', 'parentId' => $ids->get('a')],
        ];

        $this->categoryRepository->create($categories, Context::createDefaultContext());

        $urls = $this->getSeoUrls($ids->getList(['a', 'b']), $ids->get('sales-channel'));

        static::assertCount(2, $urls);
        static::assertArrayHasKey('a/', $urls);
        static::assertArrayHasKey('a/b/', $urls);
    }

    public function testSwitchNavigationId(): void
    {
        $ids = new TestDataCollection();

        $categories = [
            ['id' => $ids->create('root'), 'name' => 'root', 'active' => true],
            ['id' => $ids->create('a'), 'name' => 'a', 'parentId' => $ids->get('root'), 'active' => true],
            ['id' => $ids->create('b'), 'name' => 'b', 'parentId' => $ids->get('a'), 'active' => true],
        ];

        $this->categoryRepository->create($categories, Context::createDefaultContext());

        $this->createSalesChannel($ids->create('sales-channel'), $ids->get('root'));

        // sales channel navigation id switch detected, seo urls are generated by message queue
        $this->runWorker();

        $urls = $this->getSeoUrls($ids->getList(['root', 'a', 'b']), $ids->get('sales-channel'));

        static::assertCount(2, $urls);
        static::assertArrayHasKey('a/', $urls);
        static::assertArrayHasKey('a/b/', $urls);

        $this->salesChannelRepository->update([
            ['id' => $ids->get('sales-channel'), 'navigationCategoryId' => $ids->get('a')],
        ], Context::createDefaultContext());

        $this->runWorker();

        $urls = $this->getSeoUrls($ids->getList(['root', 'a', 'b']), $ids->get('sales-channel'));

        static::assertCount(3, $urls);
        // old urls are still exists
        static::assertArrayHasKey('a/', $urls);
        static::assertArrayHasKey('a/b/', $urls);

        // new url generated
        static::assertArrayHasKey('b/', $urls);
    }

    public function testSwitchParentId(): void
    {
        $ids = new TestDataCollection();

        $categories = [
            ['id' => $ids->create('root'), 'name' => 'root', 'active' => true],
            ['id' => $ids->create('a'), 'name' => 'a', 'parentId' => $ids->get('root'), 'active' => true],
            ['id' => $ids->create('b'), 'name' => 'b', 'parentId' => $ids->get('a'), 'active' => true],
        ];

        $this->categoryRepository->create($categories, Context::createDefaultContext());

        $this->createSalesChannel($ids->create('sales-channel'), $ids->get('root'));

        // sales channel navigation id switch detected, seo urls are generated by message queue
        $this->runWorker();

        $urls = $this->getSeoUrls($ids->getList(['root', 'a', 'b']), $ids->get('sales-channel'));

        static::assertCount(2, $urls);
        static::assertArrayHasKey('a/', $urls);
        static::assertArrayHasKey('a/b/', $urls);

        $this->categoryRepository->update([
            ['id' => $ids->get('b'), 'parentId' => $ids->get('root')],
        ], Context::createDefaultContext());

        $urls = $this->getSeoUrls($ids->getList(['root', 'a', 'b']), $ids->get('sales-channel'));

        static::assertCount(3, $urls);
        // old urls are still exists
        static::assertArrayHasKey('a/', $urls);
        static::assertArrayHasKey('a/b/', $urls);

        // new url generated
        static::assertArrayHasKey('b/', $urls);
    }

    public function testUpdateName(): void
    {
        $ids = new TestDataCollection();

        $categories = [
            ['id' => $ids->create('root'), 'name' => 'root', 'active' => true],
            ['id' => $ids->create('a'), 'name' => 'a', 'parentId' => $ids->get('root'), 'active' => true],
            ['id' => $ids->create('b'), 'name' => 'b', 'parentId' => $ids->get('a'), 'active' => true],
        ];

        $this->categoryRepository->create($categories, Context::createDefaultContext());
        $this->createSalesChannel($ids->create('sales-channel'), $ids->get('root'));

        $this->runWorker();

        $urls = $this->getSeoUrls($ids->getList(['a', 'b']), $ids->get('sales-channel'));

        static::assertCount(2, $urls);
        // old urls are still exists
        static::assertArrayHasKey('a/', $urls);
        static::assertArrayHasKey('a/b/', $urls);

        $this->categoryRepository->update(
            [
                ['id' => $ids->get('b'), 'name' => 'x'],
            ],
            Context::createDefaultContext()
        );

        $urls = $this->getSeoUrls($ids->getList(['b']), $ids->get('sales-channel'));

        static::assertCount(2, $urls);
        // old urls are still exists
        static::assertArrayHasKey('a/b/', $urls);

        // new url is generated
        static::assertArrayHasKey('a/x/', $urls);
    }

    public function testFooterMenu(): void
    {
        $ids = new TestDataCollection();

        /**
         * navigation
         *    └── a
         *        └── b
         * footer
         *       service
         *          ├── newsletter
         *          └── partner
         *       information
         *          ├── imprint
         *          └── terms
         */
        $categories = [
            ['id' => $ids->create('root'), 'name' => 'root'],
            ['id' => $ids->create('a'), 'name' => 'a', 'parentId' => $ids->get('root')],
            ['id' => $ids->create('b'), 'name' => 'b', 'parentId' => $ids->get('a')],

            ['id' => $ids->create('footer'), 'name' => 'Footer'],

            ['id' => $ids->create('info'), 'name' => 'Information', 'parentId' => $ids->get('footer')],
            ['id' => $ids->create('terms'), 'name' => 'General terms and conditions', 'parentId' => $ids->get('info')],
            ['id' => $ids->create('imprint'), 'name' => 'Imprint', 'parentId' => $ids->get('info')],

            ['id' => $ids->create('service'), 'name' => 'Service', 'parentId' => $ids->get('footer')],
            ['id' => $ids->create('newsletter'), 'name' => 'Newsletter', 'parentId' => $ids->get('service')],
            ['id' => $ids->create('partner'), 'name' => 'Partner', 'parentId' => $ids->get('service')],
        ];

        $this->categoryRepository->create($categories, Context::createDefaultContext());

        $this->createSalesChannel(
            $ids->create('sales-channel'),
            $ids->get('root'),
            $ids->get('footer'),
            $ids->get('service')
        );

        $this->runWorker();

        $urls = $this->getSeoUrls($ids->all(), $ids->get('sales-channel'));

        static::assertCount(8, $urls);

        static::assertArrayHasKey('a/', $urls);
        static::assertArrayHasKey('a/b/', $urls);

        static::assertArrayHasKey('Information/', $urls);
        static::assertArrayHasKey('Information/General-terms-and-conditions/', $urls);
        static::assertArrayHasKey('Information/Imprint/', $urls);

        static::assertArrayHasKey('Service/', $urls);
        static::assertArrayHasKey('Service/Newsletter/', $urls);
        static::assertArrayHasKey('Service/Partner/', $urls);
    }

    public function testServiceMenuNotInFooter(): void
    {
        $ids = new TestDataCollection();

        /**
         * navigation
         *    └── a
         *        └── b
         * service
         *      ├── newsletter
         *      └── partner
         * footer
         *       information
         *          ├── imprint
         *          └── terms
         */
        $categories = [
            ['id' => $ids->create('root'), 'name' => 'root'],
            ['id' => $ids->create('a'), 'name' => 'a', 'parentId' => $ids->get('root')],
            ['id' => $ids->create('b'), 'name' => 'b', 'parentId' => $ids->get('a')],

            ['id' => $ids->create('footer'), 'name' => 'Footer'],

            ['id' => $ids->create('info'), 'name' => 'Information', 'parentId' => $ids->get('footer')],
            ['id' => $ids->create('terms'), 'name' => 'General terms and conditions', 'parentId' => $ids->get('info')],
            ['id' => $ids->create('imprint'), 'name' => 'Imprint', 'parentId' => $ids->get('info')],

            ['id' => $ids->create('service'), 'name' => 'Service'],
            ['id' => $ids->create('newsletter'), 'name' => 'Newsletter', 'parentId' => $ids->get('service')],
            ['id' => $ids->create('partner'), 'name' => 'Partner', 'parentId' => $ids->get('service')],
        ];

        $this->categoryRepository->create($categories, Context::createDefaultContext());

        $this->createSalesChannel(
            $ids->create('sales-channel'),
            $ids->get('root'),
            $ids->get('footer'),
            $ids->get('service')
        );

        $this->runWorker();

        $urls = $this->getSeoUrls($ids->all(), $ids->get('sales-channel'));

        static::assertCount(7, $urls);

        static::assertArrayHasKey('a/', $urls);
        static::assertArrayHasKey('a/b/', $urls);

        static::assertArrayHasKey('Information/', $urls);
        static::assertArrayHasKey('Information/General-terms-and-conditions/', $urls);
        static::assertArrayHasKey('Information/Imprint/', $urls);

        static::assertArrayHasKey('Newsletter/', $urls);
        static::assertArrayHasKey('Partner/', $urls);
    }

    public function testDuplicateUrl(): void
    {
        $ids = new TestDataCollection();

        $categories = [
            ['id' => $ids->create('root'), 'name' => 'root', 'active' => true],
            ['id' => $ids->create('a'), 'name' => 'a', 'parentId' => $ids->get('root'), 'active' => true],
            ['id' => $ids->create('b'), 'name' => 'a', 'parentId' => $ids->get('a'), 'active' => true],
        ];

        $this->categoryRepository->create($categories, Context::createDefaultContext());
        $this->createSalesChannel($ids->create('sales-channel'), $ids->get('root'));

        $this->runWorker();

        $urls = $this->getSeoUrls($ids->getList(['a', 'b']), $ids->get('sales-channel'));

        static::assertCount(2, $urls);
        // old urls are still exists
        static::assertArrayHasKey('a/', $urls);
        static::assertArrayHasKey('a/a/', $urls);

        $this->categoryRepository->update([
            ['id' => $ids->get('b'), 'parentId' => $ids->get('root')],
        ], Context::createDefaultContext());

        $urls = $this->getSeoUrls($ids->getList(['a', 'b']), $ids->get('sales-channel'));

        static::assertCount(2, $urls);
        static::assertArrayHasKey('a/', $urls);
        static::assertArrayHasKey('a/a/', $urls);

        // new url of a/ points to the category b
        static::assertSame($urls['a/'], $urls['a/a/']);
    }

    private function createSalesChannel(
        string $id,
        string $navigationId,
        ?string $footerId = null,
        ?string $serviceId = null
    ): void {
        $data = [
            'id' => $id,
            'name' => 'test',
            'typeId' => Defaults::SALES_CHANNEL_TYPE_STOREFRONT,
            'accessKey' => AccessKeyHelper::generateAccessKey('sales-channel'),
            'languageId' => Defaults::LANGUAGE_SYSTEM,
            'snippetSetId' => $this->getSnippetSetIdForLocale('en-GB'),
            'currencyId' => Defaults::CURRENCY,
            'currencyVersionId' => Defaults::LIVE_VERSION,
            'paymentMethodId' => $this->getValidPaymentMethodId(),
            'paymentMethodVersionId' => Defaults::LIVE_VERSION,
            'shippingMethodId' => $this->getValidShippingMethodId(),
            'shippingMethodVersionId' => Defaults::LIVE_VERSION,
            'countryId' => $this->getValidCountryId(),
            'countryVersionId' => Defaults::LIVE_VERSION,
            'navigationCategoryId' => $navigationId,
            'navigationCategoryVersionId' => Defaults::LIVE_VERSION,
            'serviceCategoryId' => $serviceId,
            'serviceCategoryVersionId' => Defaults::LIVE_VERSION,
            'footerCategoryId' => $footerId,
            'footerCategoryVersionId' => Defaults::LIVE_VERSION,
            'currencies' => [['id' => Defaults::CURRENCY]],
            'languages' => [['id' => Defaults::LANGUAGE_SYSTEM]],
            'paymentMethods' => [['id' => $this->getValidPaymentMethodId()]],
            'shippingMethods' => [['id' => $this->getValidShippingMethodId()]],
            'countries' => [['id' => $this->getValidCountryId()]],
            'customerGroupId' => TestDefaults::FALLBACK_CUSTOMER_GROUP,
            'domains' => [
                [
                    'languageId' => Defaults::LANGUAGE_SYSTEM,
                    'snippetSetId' => $this->getSnippetSetIdForLocale('en-GB'),
                    'currencyId' => Defaults::CURRENCY,
                    'url' => 'http://test.de',
                ],
            ],
        ];

        $this->salesChannelRepository->create([$data], Context::createDefaultContext());
    }

    /**
     * @return mixed[]
     */
    private function getSeoUrls(array $ids, ?string $salesChannelId): array
    {
        $query = $this->connection->createQueryBuilder();
        $query->addSelect([
            'seo_path_info',
            'path_info',
        ]);
        $query->from('seo_url');
        $query->andWhere('foreign_key IN (:ids)');
        $query->andWhere('route_name = :routeName');
        $query->andWhere('language_id = :language');
        if ($salesChannelId) {
            $query->andWhere('sales_channel_id = :salesChannel');
            $query->setParameter('salesChannel', Uuid::fromHexToBytes($salesChannelId));
        }

        $query->setParameter('ids', Uuid::fromHexToBytesList(array_values($ids)), ArrayParameterType::BINARY);
        $query->setParameter('routeName', NavigationPageSeoUrlRoute::ROUTE_NAME);
        $query->setParameter('language', Uuid::fromHexToBytes(Defaults::LANGUAGE_SYSTEM));

        $urls = $query->executeQuery()->fetchAllAssociative();

        return FetchModeHelper::keyPair($urls);
    }
}
