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

use Ferienpass\AdminBundle\Components\AddAttendance;
use Ferienpass\CoreBundle\ConsentManager\ConsentManager;
use Ferienpass\CoreBundle\ConsentManager\ConsentState;
use Ferienpass\CoreBundle\Entity\ConsentForm;
use Ferienpass\CoreBundle\Entity\Offer\Base;
use Ferienpass\CoreBundle\Entity\Offer\OfferInterface;
use Ferienpass\CoreBundle\Entity\Participant\BaseParticipant;
use Ferienpass\CoreBundle\Facade\AttendanceFacade;
use Ferienpass\CoreBundle\Notification\AttendanceNotificationChecker;
use Ferienpass\CoreBundle\Repository\OfferRepositoryInterface;
use Ferienpass\CoreBundle\Repository\ParticipantRepositoryInterface;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Uid\Uuid;

class AddAttendanceTest extends TestCase
{
    public function testQuestionsDropsBlankAndKeepsAnswerable(): void
    {
        $offer = new Base();
        $offer->setApplicationExtra([
            ['uuid' => 'q1', 'question' => 'Kann Ihr Kind schwimmen?', 'type' => 'yn', 'required' => true],
            ['uuid' => 'q2', 'question' => '   ', 'type' => 'txt', 'required' => false],
        ]);

        $component = $this->component($this->createStub(ConsentManager::class));
        $component->offer = $offer;

        $questions = $component->questions();

        self::assertCount(1, $questions);
        self::assertSame('q1', $questions[0]['uuid']);
    }

    public function testQuestionsNormalisesLegacyRowMissingRequired(): void
    {
        // A row stored before the "required" field existed, read straight from the
        // database (the entity only normalises on save). The component must fill in
        // all four keys so the template's q.required/q.uuid access never throws.
        $offer = $this->createMock(OfferInterface::class);
        $offer->method('getApplicationExtra')->willReturn([
            ['uuid' => 'legacy', 'question' => 'Kann Ihr Kind schwimmen?', 'type' => 'yn'],
        ]);

        $component = $this->component($this->createStub(ConsentManager::class));
        $component->offer = $offer;

        $questions = $component->questions();

        self::assertCount(1, $questions);
        self::assertArrayHasKey('required', $questions[0]);
        self::assertFalse($questions[0]['required']);
        self::assertSame('legacy', $questions[0]['uuid']);
        self::assertSame('yn', $questions[0]['type']);
    }

    public function testPendingQuestionsCountsOnlyRequiredDelegated(): void
    {
        $offer = new Base();
        $offer->setApplicationExtra([
            ['uuid' => 'req', 'question' => 'Pflichtfrage?', 'type' => 'yn', 'required' => true],
            ['uuid' => 'opt', 'question' => 'Freiwillig?', 'type' => 'txt', 'required' => false],
        ]);

        $component = $this->component($this->createStub(ConsentManager::class));
        $component->offer = $offer;

        // Nothing delegated → nothing pending.
        self::assertSame(0, $component->pendingQuestions());

        // Delegating the optional one does not make it pending.
        $component->delegatedQuestions = ['opt' => true];
        self::assertSame(0, $component->pendingQuestions());

        // Delegating the required one does.
        $component->delegatedQuestions = ['req' => true, 'opt' => true];
        self::assertSame(1, $component->pendingQuestions());
    }

    public function testPendingConsentsCountsOpenCompulsoryOnly(): void
    {
        $formId = Uuid::v7();

        $state = $this->createMock(ConsentState::class);
        $state->method('isOutstanding')->willReturn(true);
        $state->method('isCompulsory')->willReturn(true);
        $state->method('getConsentForm')->willReturn(new ConsentForm($formId));

        $consentManager = $this->createMock(ConsentManager::class);
        $consentManager->method('forOfferAndParticipants')->willReturn([$state]);

        $component = $this->component($consentManager);
        $component->offer = new Base();
        $component->participant = new BaseParticipant();

        // Default decision is "open" → the compulsory consent is pending.
        self::assertSame(1, $component->pendingConsents());
        self::assertTrue($component->isPending());

        // Granting it on behalf clears the pending state.
        $component->consentDecisions = [(string) $formId => 'grant'];
        self::assertSame(0, $component->pendingConsents());
        self::assertFalse($component->isPending());
    }

    public function testVoluntaryOpenConsentIsNotPending(): void
    {
        $state = $this->createMock(ConsentState::class);
        $state->method('isOutstanding')->willReturn(true);
        $state->method('isCompulsory')->willReturn(false);
        $state->method('getConsentForm')->willReturn(new ConsentForm(Uuid::v7()));

        $consentManager = $this->createMock(ConsentManager::class);
        $consentManager->method('forOfferAndParticipants')->willReturn([$state]);

        $component = $this->component($consentManager);
        $component->offer = new Base();
        $component->participant = new BaseParticipant();

        self::assertSame(0, $component->pendingConsents());
        self::assertFalse($component->isPending());
    }

    public function testHasContact(): void
    {
        $component = $this->component($this->createStub(ConsentManager::class));

        // No participant, no override → unreachable.
        self::assertFalse($component->hasContact());

        // An entered override e-mail is enough.
        $component->overrideEmail = 'eltern@beispiel.de';
        self::assertTrue($component->hasContact());

        // A participant's own e-mail is enough, without any override.
        $component->overrideEmail = '';
        $participant = new BaseParticipant();
        $participant->setEmail('kind@beispiel.de');
        $component->participant = $participant;
        self::assertTrue($component->hasContact());
    }

    public function testConsentStatesEmptyWithoutOfferAndParticipant(): void
    {
        $consentManager = $this->createMock(ConsentManager::class);
        $consentManager->expects(self::never())->method('forOfferAndParticipants');

        $component = $this->component($consentManager);

        self::assertSame([], $component->consentStates());
        self::assertFalse($component->isPending());
    }

    private function component(ConsentManager $consentManager): AddAttendance
    {
        return new AddAttendance(
            $this->createStub(OfferRepositoryInterface::class),
            $this->createStub(ParticipantRepositoryInterface::class),
            $this->createStub(AttendanceFacade::class),
            new AttendanceNotificationChecker(),
            $consentManager,
        );
    }
}
