<?php

declare(strict_types=1);

/*
 * This file is part of the Ferienpass package.
 *
 * (c) Richard Henkenjohann <richard@ferienpass.online>
 *
 * For more information visit the project website <https://ferienpass.online>
 * or the documentation under <https://docs.ferienpass.online>.
 */

namespace Ferienpass\CoreBundle\Tests\ApiPlatform;

use ApiPlatform\Doctrine\Orm\Util\QueryNameGeneratorInterface;
use ApiPlatform\Metadata\Operation;
use ApiPlatform\State\ProviderInterface;
use Contao\CoreBundle\Filesystem\VirtualFilesystemInterface;
use Contao\CoreBundle\Image\ImageFactoryInterface;
use Doctrine\ORM\QueryBuilder;
use Ferienpass\CoreBundle\ApiPlatform\OfferAnonymousReadExtension;
use Ferienpass\CoreBundle\ApiPlatform\OfferAnonymousReadFilter;
use Ferienpass\CoreBundle\ApiPlatform\OfferAuthenticatedReadExtension;
use Ferienpass\CoreBundle\ApiPlatform\OfferRepresentationProvider;
use Ferienpass\CoreBundle\Entity\Offer\OfferInterface;
use Ferienpass\CoreBundle\Repository\EditionRepository;
use Ferienpass\CoreBundle\Repository\HostRepository;
use PHPUnit\Framework\TestCase;
use Symfony\Bundle\SecurityBundle\Security;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;

final class OfferReadExtensionsTest extends TestCase
{
    // ---- OfferAuthenticatedReadExtension ----

    public function testAuthenticatedExtensionSkipsNonOfferResourceClass(): void
    {
        $security = $this->createMock(Security::class);
        $security->expects(self::never())->method('isGranted');

        $hosts = $this->createMock(HostRepository::class);
        $qb = $this->createMock(QueryBuilder::class);
        $qb->expects(self::never())->method('andWhere');
        $qb->expects(self::never())->method('where');

        $ext = new OfferAuthenticatedReadExtension($security, $hosts);
        $ext->applyToCollection($qb, $this->createMock(QueryNameGeneratorInterface::class), \stdClass::class);
    }

    public function testAuthenticatedExtensionSkipsWhenGroupNotInContext(): void
    {
        $security = $this->createMock(Security::class);
        $security->expects(self::never())->method('isGranted');

        $hosts = $this->createMock(HostRepository::class);
        $qb = $this->createMock(QueryBuilder::class);
        $qb->expects(self::never())->method('andWhere');
        $qb->expects(self::never())->method('where');

        $ext = new OfferAuthenticatedReadExtension($security, $hosts);
        $ext->applyToCollection($qb, $this->createMock(QueryNameGeneratorInterface::class), OfferInterface::class, null, ['groups' => []]);
    }

    public function testAuthenticatedExtensionSkipsForAdminUser(): void
    {
        $security = $this->createMock(Security::class);
        $security->method('isGranted')->with('ROLE_ADMIN')->willReturn(true);

        $hosts = $this->createMock(HostRepository::class);
        $qb = $this->createMock(QueryBuilder::class);
        $qb->expects(self::never())->method('andWhere');
        $qb->expects(self::never())->method('where');

        $ext = new OfferAuthenticatedReadExtension($security, $hosts);
        $ext->applyToCollection($qb, $this->createMock(QueryNameGeneratorInterface::class), OfferInterface::class, null, ['groups' => ['api:offer:read']]);
    }

    // ---- OfferAnonymousReadExtension ----

    public function testAnonymousExtensionSkipsNonOfferResourceClass(): void
    {
        // Real (final) filter with a stub repo; the $qb->never()->andWhere
        // assertion proves apply() was not invoked (it always calls andWhere).
        $filter = new OfferAnonymousReadFilter($this->createStub(EditionRepository::class));

        $qb = $this->createMock(QueryBuilder::class);
        $qb->expects(self::never())->method('andWhere');

        $ext = new OfferAnonymousReadExtension($filter);
        $ext->applyToCollection($qb, $this->createMock(QueryNameGeneratorInterface::class), \stdClass::class);
    }

    public function testAnonymousExtensionSkipsWhenApiOfferReadGroupInContext(): void
    {
        $filter = new OfferAnonymousReadFilter($this->createStub(EditionRepository::class));

        $qb = $this->createMock(QueryBuilder::class);
        $qb->expects(self::never())->method('andWhere');

        $ext = new OfferAnonymousReadExtension($filter);
        $ext->applyToCollection($qb, $this->createMock(QueryNameGeneratorInterface::class), OfferInterface::class, null, ['groups' => ['api:offer:read']]);
    }

    // ---- OfferRepresentationProvider ----

    public function testProvideReturnsNonOfferItemUnchanged(): void
    {
        $expected = new \stdClass();

        $itemProvider = $this->createMock(ProviderInterface::class);
        $itemProvider->method('provide')->willReturn($expected);

        $collectionProvider = $this->createMock(ProviderInterface::class);
        $collectionProvider->expects(self::never())->method('provide');

        $operation = $this->createMock(Operation::class);

        $provider = new OfferRepresentationProvider(
            $itemProvider,
            $collectionProvider,
            $this->createMock(UrlGeneratorInterface::class),
            $this->createMock(VirtualFilesystemInterface::class),
            $this->createMock(ImageFactoryInterface::class),
            '/tmp'
        );

        $result = $provider->provide($operation, [], []);

        self::assertSame($expected, $result);
    }
}
