<?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\AdminBundle\Tests\Controller\Page;

use Doctrine\DBAL\Connection;
use Doctrine\DBAL\Result;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\EntityRepository;
use Ferienpass\AdminBundle\Breadcrumb\Breadcrumb;
use Ferienpass\AdminBundle\Controller\Page\HomeController;
use Ferienpass\CoreBundle\Facade\EraseDataFacade;
use Ferienpass\CoreBundle\Repository\EditionRepository;
use Ferienpass\CoreBundle\Repository\OfferRepository;
use Ferienpass\CoreBundle\Repository\SentMessageRepository;
use FOS\HttpCacheBundle\Http\SymfonyResponseTagger;
use PHPUnit\Framework\TestCase;
use Symfony\Component\DependencyInjection\Container;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
use Twig\Environment;

final class HomeControllerTest extends TestCase
{
    public function testHostDashboardTagsPageAndRendersInlineNextStepsOnly(): void
    {
        $upcoming = [new \stdClass()];

        $offers = $this->createMock(OfferRepository::class);
        $offers->expects(self::once())->method('findUpcoming')->willReturn($upcoming);

        $editions = $this->createMock(EditionRepository::class);
        // A host must never trigger the admin-only aggregate queries.
        $editions->expects(self::never())->method('findWithUncommitedAttendances');

        $twig = $this->createMock(Environment::class);
        $twig->expects(self::once())->method('render')->with(
            '@FerienpassAdmin/page/home.html.twig',
            self::callback(static fn (array $p): bool => ($p['upcomingOffers'] ?? null) === $upcoming
                && ($p['editions'] ?? null) === $editions
                // admin-only aggregates must be absent for a host
                && !\array_key_exists('heatmapValues', $p)
                && !\array_key_exists('activities', $p)),
        )->willReturn('Dashboard');

        $tagger = $this->createMock(SymfonyResponseTagger::class);
        $tagger->expects(self::once())->method('addTags')->with(['dashboard.test']);

        $response = $this->invoke($twig, $tagger, isAdmin: false, offers: $offers, editions: $editions);

        self::assertSame(200, $response->getStatusCode());
    }

    public function testAdminDashboardTagsPageAndPassesInlineAggregates(): void
    {
        $offers = $this->createMock(OfferRepository::class);
        $offers->method('findUpcoming')->willReturn([]);

        $editions = $this->createMock(EditionRepository::class);
        $editions->method('findWithUncommitedAttendances')->willReturn([]);
        $editions->method('findWithFinalizedOffers')->willReturn([]);
        $editions->method('findWithWaitingAttendances')->willReturn([]);

        $twig = $this->createMock(Environment::class);
        $twig->expects(self::once())->method('render')->with(
            '@FerienpassAdmin/page/home.html.twig',
            self::callback(static fn (array $p): bool => \array_key_exists('heatmapValues', $p)
                && \array_key_exists('activities', $p)
                && \array_key_exists('expiredParticipants', $p)),
        )->willReturn('Dashboard');

        // The whole /admin page now carries the dashboard tag (no ESI fragments).
        $tagger = $this->createMock(SymfonyResponseTagger::class);
        $tagger->expects(self::once())->method('addTags')->with(['dashboard.test']);

        $response = $this->invoke($twig, $tagger, isAdmin: true, offers: $offers, editions: $editions);

        self::assertSame(200, $response->getStatusCode());
    }

    private function invoke(Environment $twig, SymfonyResponseTagger $tagger, bool $isAdmin, OfferRepository $offers, EditionRepository $editions): Response
    {
        $authChecker = $this->createMock(AuthorizationCheckerInterface::class);
        $authChecker->method('isGranted')->with('ROLE_ADMIN')->willReturn($isAdmin);

        $container = new Container();
        $container->set('twig', $twig);
        $container->set('security.authorization_checker', $authChecker);

        $controller = new HomeController('test');
        $controller->setFosResponseTagger($tagger);
        $controller->setContainer($container);

        $breadcrumb = $this->createMock(Breadcrumb::class);
        $breadcrumb->method('generate')->willReturn(new \Knp\Menu\MenuFactory()->createItem('root'));

        $sentMessages = $this->createMock(SentMessageRepository::class);
        $sentMessages->method('findWithError')->willReturn([]);

        $eraseData = $this->createMock(EraseDataFacade::class);
        $eraseData->method('expiredParticipants')->willReturn([]);

        // Heatmap (raw DBAL) + activity feed (log repos) — only queried on the
        // admin path; the host path never reaches them.
        $result = $this->createMock(Result::class);
        $result->method('fetchAllNumeric')->willReturn([]);
        $connection = $this->createMock(Connection::class);
        $connection->method('executeQuery')->willReturn($result);

        $logRepository = $this->createMock(EntityRepository::class);
        $logRepository->method('findBy')->willReturn([]);
        $entityManager = $this->createMock(EntityManagerInterface::class);
        $entityManager->method('getRepository')->willReturn($logRepository);

        return $controller($breadcrumb, $offers, $editions, $sentMessages, $eraseData, $connection, $entityManager);
    }
}
