<?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\ApplicationSystem;

use Ferienpass\AdminBundle\ApplicationSystem\ParticipantListDto;
use Ferienpass\CoreBundle\Entity\Edition;
use Ferienpass\CoreBundle\Entity\Offer\OfferInterface;
use PHPUnit\Framework\TestCase;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;

final class ParticipantListDtoTest extends TestCase
{
    public function testAdminViewColumnsReturnsArrayForNormalRequest(): void
    {
        $dto = $this->makeDto();
        $offer = $this->createMock(OfferInterface::class);
        $request = new Request();

        $columns = $dto->adminViewColumns($offer, $request);

        self::assertIsArray($columns);
        self::assertArrayHasKey('name', $columns);
        self::assertArrayHasKey('status', $columns);
        self::assertArrayHasKey('consents', $columns);
    }

    public function testAdminViewColumnsForNormalRequestIncludesContactColumns(): void
    {
        $dto = $this->makeDto();
        $offer = $this->createMock(OfferInterface::class);
        $request = new Request();

        $columns = $dto->adminViewColumns($offer, $request);

        self::assertArrayHasKey('email', $columns);
        self::assertArrayHasKey('mobile', $columns);
        self::assertArrayHasKey('phone', $columns);
    }

    public function testAdminViewColumnsIncludesPaidColumnForAdmin(): void
    {
        $dto = $this->makeDto(isAdmin: true);
        $offer = $this->createMock(OfferInterface::class);
        $request = new Request();

        $columns = $dto->adminViewColumns($offer, $request);

        self::assertArrayHasKey('paid', $columns);
    }

    public function testAdminViewColumnsExcludesPaidColumnWhenSeasonDoesNotUsePayments(): void
    {
        $edition = new Edition();
        $edition->setUsePayments(false);

        $dto = $this->makeDto(isAdmin: true);
        $offer = $this->createMock(OfferInterface::class);
        $offer->method('getEdition')->willReturn($edition);

        $columns = $dto->adminViewColumns($offer, new Request());

        self::assertArrayNotHasKey('paid', $columns);
    }

    public function testAdminViewColumnsIncludesPaidColumnWhenSeasonUsesPayments(): void
    {
        $dto = $this->makeDto(isAdmin: true);
        $offer = $this->createMock(OfferInterface::class);
        $offer->method('getEdition')->willReturn(new Edition());

        $columns = $dto->adminViewColumns($offer, new Request());

        self::assertArrayHasKey('paid', $columns);
    }

    public function testAdminViewColumnsExcludesPaidColumnForNonAdmin(): void
    {
        $dto = $this->makeDto(isAdmin: false);
        $offer = $this->createMock(OfferInterface::class);
        $request = new Request();

        $columns = $dto->adminViewColumns($offer, $request);

        self::assertArrayNotHasKey('paid', $columns);
    }

    public function testAdminViewColumnsForSlideoverRequestExcludesContactColumns(): void
    {
        $dto = $this->makeDto();
        $offer = $this->createMock(OfferInterface::class);
        $request = new Request();
        $request->headers->set('turbo-frame', 'slideover');

        $columns = $dto->adminViewColumns($offer, $request);

        self::assertIsArray($columns);
        self::assertArrayNotHasKey('email', $columns);
        self::assertArrayNotHasKey('mobile', $columns);
        self::assertArrayNotHasKey('phone', $columns);
    }

    public function testAdminViewColumnsForSlideoverRequestHasRequiredKeys(): void
    {
        $dto = $this->makeDto();
        $offer = $this->createMock(OfferInterface::class);
        $request = new Request();
        $request->headers->set('turbo-frame', 'slideover');

        $columns = $dto->adminViewColumns($offer, $request);

        self::assertArrayHasKey('name', $columns);
        self::assertArrayHasKey('status', $columns);
        self::assertArrayHasKey('consents', $columns);
    }

    /**
     * The attendance drawer lives in ParticipantsController, behind
     * ROLE_PARTICIPANTS_ADMIN. A Veranstalter opening the badge would only get
     * a slideover that 403s, so the badge must not advertise itself as a link.
     */
    public function testStatusBadgeIsNotLinkedWithoutParticipantsAdminRole(): void
    {
        $dto = $this->makeDto(isAdmin: false);
        $offer = $this->createMock(OfferInterface::class);

        foreach ([new Request(), self::slideoverRequest()] as $request) {
            $status = $dto->adminViewColumns($offer, $request)['status'];

            self::assertStringNotContainsString('admin_attendance_show', $status);
            self::assertStringNotContainsString('<a ', $status);
            self::assertStringNotContainsString('hover:', $status);
            self::assertStringContainsString('<twig:Admin:Badge', $status);
        }
    }

    public function testStatusBadgeLinksToTheAttendanceDrawerForParticipantsAdmin(): void
    {
        $dto = $this->makeDto();
        $offer = $this->createMock(OfferInterface::class);

        foreach ([new Request(), self::slideoverRequest()] as $request) {
            $status = $dto->adminViewColumns($offer, $request)['status'];

            self::assertStringContainsString('admin_attendance_show', $status);
            self::assertStringContainsString('data-turbo-frame="slideover"', $status);
        }
    }

    /**
     * The consent drawer is the exception: ConsentStateVoter grants it to
     * whoever may view the participants, so the badge stays a link for a
     * Veranstalter too.
     *
     * @see \Ferienpass\CoreBundle\Security\Voter\ConsentStateVoter
     */
    public function testConsentBadgesLinkToTheConsentDrawerForEveryone(): void
    {
        $offer = $this->createMock(OfferInterface::class);

        foreach ([$this->makeDto(), $this->makeDto(isAdmin: false)] as $dto) {
            foreach ([new Request(), self::slideoverRequest()] as $request) {
                $consents = $dto->adminViewColumns($offer, $request)['consents'];

                self::assertStringContainsString('admin_consents_details', $consents);
                self::assertStringContainsString('data-turbo-frame="slideover"', $consents);
            }
        }
    }

    public function testParticipantNameIsNotLinkedWithoutParticipantsAdminRole(): void
    {
        $dto = $this->makeDto(isAdmin: false);
        $offer = $this->createMock(OfferInterface::class);

        foreach ([new Request(), self::slideoverRequest()] as $request) {
            $name = $dto->adminViewColumns($offer, $request)['name'];

            self::assertStringNotContainsString('admin_participants_show', $name);
            self::assertStringNotContainsString('<a ', $name);
            self::assertStringNotContainsString('hover:', $name);
            self::assertStringContainsString('item.participant.name', $name);
        }
    }

    /**
     * ROLE_ADMIN does not imply ROLE_PARTICIPANTS_ADMIN (only ROLE_SUPER_ADMIN
     * does), so an admin without it keeps the pay status but loses the link
     * into the payments drawer.
     */
    public function testPaidCellIsNotLinkedWithoutParticipantsAdminRole(): void
    {
        $dto = $this->makeDto(isAdmin: true, isParticipantsAdmin: false);
        $offer = $this->createMock(OfferInterface::class);

        $paid = $dto->adminViewColumns($offer, new Request())['paid'];

        self::assertStringNotContainsString('admin_participants_attendances_payments', $paid);
        self::assertStringNotContainsString('<a ', $paid);
        self::assertStringContainsString('payLabel', $paid);
    }

    public function testPaidCellLinksToThePaymentsDrawerForParticipantsAdmin(): void
    {
        $dto = $this->makeDto();
        $offer = $this->createMock(OfferInterface::class);

        $paid = $dto->adminViewColumns($offer, new Request())['paid'];

        self::assertStringContainsString('admin_participants_attendances_payments', $paid);
    }

    private static function slideoverRequest(): Request
    {
        $request = new Request();
        $request->headers->set('turbo-frame', 'slideover');

        return $request;
    }

    private function makeDto(bool $isAdmin = true, ?bool $isParticipantsAdmin = null): ParticipantListDto
    {
        $isParticipantsAdmin ??= $isAdmin;

        $dispatcher = $this->createMock(EventDispatcherInterface::class);
        $dispatcher->method('dispatch')->willReturnArgument(0);

        $authorizationChecker = $this->createMock(AuthorizationCheckerInterface::class);
        $authorizationChecker->method('isGranted')->willReturnCallback(
            static fn (mixed $attribute): bool => match ($attribute) {
                'ROLE_ADMIN' => $isAdmin,
                'ROLE_PARTICIPANTS_ADMIN' => $isParticipantsAdmin,
                default => false,
            }
        );

        return new ParticipantListDto($dispatcher, $authorizationChecker);
    }
}
