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

use Ferienpass\CoreBundle\ConsentManager\ConsentState;
use Ferienpass\CoreBundle\Entity\Consent;
use Ferienpass\CoreBundle\Entity\ConsentForm;
use Ferienpass\CoreBundle\Entity\ConsentText;
use Ferienpass\CoreBundle\Entity\User;
use PHPUnit\Framework\TestCase;

final class ConsentStateTest extends TestCase
{
    // --- isSigned ---

    public function testIsSignedWhenDecisionIsOneAndValid(): void
    {
        $text = new ConsentText('consent text');
        $consent = $this->makeConsent(decision: 1, consentText: $text);
        $state = new ConsentState(signature: $consent);
        $state = new ConsentState(consentForm: null, signature: $consent);

        // Use a ConsentForm with matching text so getConsentText() returns the same text
        $form = new ConsentForm();
        $form->setText('consent text');
        $state = new ConsentState(consentForm: $form, signature: $consent);

        self::assertTrue($state->isSigned());
    }

    public function testIsSignedFalseWhenDecisionIsZero(): void
    {
        $text = new ConsentText('consent text');
        $form = new ConsentForm();
        $form->setText('consent text');
        $consent = $this->makeConsent(decision: 0, consentText: $text);

        $state = new ConsentState(consentForm: $form, signature: $consent);

        self::assertFalse($state->isSigned());
    }

    public function testIsSignedFalseWhenRevoked(): void
    {
        $text = new ConsentText('consent text');
        $form = new ConsentForm();
        $form->setText('consent text');
        $consent = $this->makeConsent(decision: 1, revoked: true, consentText: $text);

        $state = new ConsentState(consentForm: $form, signature: $consent);

        self::assertFalse($state->isSigned());
    }

    public function testIsSignedFalseWhenTextChanged(): void
    {
        $oldText = new ConsentText('old text');
        $form = new ConsentForm();
        $form->setText('new text'); // different from what consent was signed with

        $consent = $this->makeConsent(decision: 1, consentText: $oldText);
        $state = new ConsentState(consentForm: $form, signature: $consent);

        self::assertFalse($state->isSigned());
    }

    public function testIsSignedFalseWhenNoSignature(): void
    {
        $state = new ConsentState();

        self::assertFalse($state->isSigned());
    }

    // --- isAcknowledged ---

    public function testIsAcknowledgedWhenSigned(): void
    {
        $text = new ConsentText('text');
        $form = new ConsentForm();
        $form->setText('text');
        $consent = $this->makeConsent(decision: 1, consentText: $text);

        $state = new ConsentState(consentForm: $form, signature: $consent);

        self::assertTrue($state->isAcknowledged());
    }

    public function testIsAcknowledgedWhenDecisionZeroAndValid(): void
    {
        $text = new ConsentText('text');
        $form = new ConsentForm();
        $form->setText('text');
        $consent = $this->makeConsent(decision: 0, consentText: $text);

        $state = new ConsentState(consentForm: $form, signature: $consent);

        self::assertTrue($state->isAcknowledged());
    }

    public function testIsAcknowledgedFalseWhenRevoked(): void
    {
        $text = new ConsentText('text');
        $form = new ConsentForm();
        $form->setText('text');
        $consent = $this->makeConsent(decision: 1, revoked: true, consentText: $text);

        $state = new ConsentState(consentForm: $form, signature: $consent);

        self::assertFalse($state->isAcknowledged());
    }

    public function testIsAcknowledgedFalseWhenNoSignature(): void
    {
        $state = new ConsentState();

        self::assertFalse($state->isAcknowledged());
    }

    // --- isRejected ---

    public function testIsRejectedWhenDecisionZeroAndValid(): void
    {
        $text = new ConsentText('text');
        $form = new ConsentForm();
        $form->setText('text');
        $consent = $this->makeConsent(decision: 0, consentText: $text);

        $state = new ConsentState(consentForm: $form, signature: $consent);

        self::assertTrue($state->isRejected());
    }

    public function testIsRejectedFalseWhenDecisionOne(): void
    {
        $text = new ConsentText('text');
        $form = new ConsentForm();
        $form->setText('text');
        $consent = $this->makeConsent(decision: 1, consentText: $text);

        $state = new ConsentState(consentForm: $form, signature: $consent);

        self::assertFalse($state->isRejected());
    }

    // --- isRevoked ---

    public function testIsRevokedWhenConsentRevoked(): void
    {
        $consent = $this->makeConsent(revoked: true);
        $state = new ConsentState(signature: $consent);

        self::assertTrue($state->isRevoked());
    }

    public function testIsRevokedFalseWhenNotRevoked(): void
    {
        $consent = $this->makeConsent();
        $state = new ConsentState(signature: $consent);

        self::assertFalse($state->isRevoked());
    }

    public function testIsRevokedFalseWhenNoSignature(): void
    {
        $state = new ConsentState();

        self::assertFalse($state->isRevoked());
    }

    // --- isCompulsory / isVoluntary / isOptIn / isOptOut / isForceDecision ---

    public function testDefaultCompulsoryIsCompulsory(): void
    {
        $state = new ConsentState();

        self::assertTrue($state->isCompulsory());
        self::assertFalse($state->isVoluntary());
        self::assertFalse($state->isOptIn());
        self::assertFalse($state->isOptOut());
        self::assertFalse($state->isForceDecision());
    }

    public function testOptInFromConsentForm(): void
    {
        $form = new ConsentForm();
        $form->setCompulsory('opt-in');

        $state = new ConsentState(consentForm: $form);

        self::assertTrue($state->isOptIn());
        self::assertFalse($state->isOptOut());
        self::assertFalse($state->isCompulsory());
        self::assertTrue($state->isVoluntary());
    }

    public function testOptOutFromConsentForm(): void
    {
        $form = new ConsentForm();
        $form->setCompulsory('opt-out');

        $state = new ConsentState(consentForm: $form);

        self::assertTrue($state->isOptOut());
        self::assertFalse($state->isOptIn());
        self::assertFalse($state->isCompulsory());
        self::assertTrue($state->isVoluntary());
    }

    public function testForceDecisionIsCompulsory(): void
    {
        $form = new ConsentForm();
        $form->setCompulsory('force_decision');

        $state = new ConsentState(consentForm: $form);

        self::assertTrue($state->isForceDecision());
        self::assertTrue($state->isCompulsory());
        self::assertFalse($state->isVoluntary());
    }

    // --- isAgreed ---

    public function testIsAgreedWhenSigned(): void
    {
        $text = new ConsentText('text');
        $form = new ConsentForm();
        $form->setText('text');
        $consent = $this->makeConsent(decision: 1, consentText: $text);

        $state = new ConsentState(consentForm: $form, signature: $consent);

        self::assertTrue($state->isAgreed());
    }

    public function testIsAgreedWhenOptOutAndNotAcknowledged(): void
    {
        $form = new ConsentForm();
        $form->setCompulsory('opt-out');

        // No signature → not acknowledged → opt-out → agreed by default
        $state = new ConsentState(consentForm: $form);

        self::assertTrue($state->isAgreed());
    }

    public function testIsNotAgreedWhenOptInAndNotAcknowledged(): void
    {
        $form = new ConsentForm();
        $form->setCompulsory('opt-in');

        $state = new ConsentState(consentForm: $form);

        self::assertFalse($state->isAgreed());
    }

    public function testIsNotAgreedWhenCompulsoryAndNotSigned(): void
    {
        // compulsory form, no signature
        $state = new ConsentState();

        self::assertFalse($state->isAgreed());
    }

    // --- isDisagreed ---

    public function testIsDisagreedWhenRejected(): void
    {
        $text = new ConsentText('text');
        $form = new ConsentForm();
        $form->setText('text');
        $consent = $this->makeConsent(decision: 0, consentText: $text);

        $state = new ConsentState(consentForm: $form, signature: $consent);

        self::assertTrue($state->isDisagreed());
    }

    public function testIsDisagreedWhenOptInAndNotAcknowledged(): void
    {
        $form = new ConsentForm();
        $form->setCompulsory('opt-in');

        $state = new ConsentState(consentForm: $form);

        self::assertTrue($state->isDisagreed());
    }

    public function testIsNotDisagreedWhenOptOutAndNotAcknowledged(): void
    {
        $form = new ConsentForm();
        $form->setCompulsory('opt-out');

        $state = new ConsentState(consentForm: $form);

        self::assertFalse($state->isDisagreed());
    }

    // --- getTargetId ---

    public function testGetTargetIdWithConsentFormId(): void
    {
        $form = new ConsentForm(new \Symfony\Component\Uid\Uuid('018e4c5c-1e2f-7e5a-9c3f-000000000001'));

        $state = new ConsentState(consentForm: $form);

        self::assertStringStartsWith('consent-', $state->getTargetId());
        self::assertStringContainsString('018e4c5c-1e2f-7e5a-9c3f-000000000001', $state->getTargetId());
    }

    public function testGetTargetIdWithSignatureOnly(): void
    {
        $consent = $this->makeConsent();
        // id is null (no DB), so it becomes 'legacy-null'
        $state = new ConsentState(signature: $consent);

        self::assertStringStartsWith('consent-legacy-', $state->getTargetId());
    }

    // --- hasSignature ---

    public function testHasSignatureFalseByDefault(): void
    {
        $state = new ConsentState();

        self::assertFalse($state->hasSignature());
    }

    public function testHasSignatureTrueWhenSet(): void
    {
        $consent = $this->makeConsent();
        $state = new ConsentState(signature: $consent);

        self::assertTrue($state->hasSignature());
    }

    // --- getConsentText ---

    public function testGetConsentTextFromForm(): void
    {
        $form = new ConsentForm();
        $form->setText('form text');

        $state = new ConsentState(consentForm: $form);

        self::assertSame('form text', $state->getConsentText());
    }

    public function testGetConsentTextFromSignature(): void
    {
        $text = new ConsentText('signed text');
        $consent = $this->makeConsent(consentText: $text);

        $state = new ConsentState(signature: $consent);

        self::assertSame('signed text', $state->getConsentText());
    }

    public function testGetConsentTextNullWhenNeitherSet(): void
    {
        $state = new ConsentState();

        self::assertNull($state->getConsentText());
    }

    // --- getConsentToken ---

    public function testConsentToken(): void
    {
        $state = new ConsentState();
        self::assertNull($state->getConsentToken());

        $state->setConsentToken('abc123');
        self::assertSame('abc123', $state->getConsentToken());
    }

    // --- getUser ---

    public function testGetUserFromConstructorParam(): void
    {
        $user = $this->makeUser();
        $state = new ConsentState(user: $user);

        self::assertSame($user, $state->getUser());
    }

    public function testGetUserFallsBackToSignatureUser(): void
    {
        $user = $this->makeUser();
        $consent = $this->makeConsent();

        // Inject user into consent by reflection (signature's user)
        $refl = new \ReflectionProperty(Consent::class, 'user');
        $refl->setAccessible(true);
        $refl->setValue($consent, $user);

        $state = new ConsentState(signature: $consent);

        self::assertSame($user, $state->getUser());
    }

    // --- getParticipant ---

    public function testGetParticipantFromConstructor(): void
    {
        $participant = $this->createStub(\Ferienpass\CoreBundle\Entity\Participant\ParticipantInterface::class);

        $state = new ConsentState(participant: $participant);

        self::assertSame($participant, $state->getParticipant());
    }

    public function testGetParticipantNullWhenNotSet(): void
    {
        $state = new ConsentState();

        self::assertNull($state->getParticipant());
    }

    // --- getTitle ---

    public function testGetTitleFromConsentForm(): void
    {
        $form = new ConsentForm();
        $form->setTitle('My Title');

        $state = new ConsentState(consentForm: $form);

        self::assertSame('My Title', $state->getTitle());
    }

    public function testGetTitleNullWhenNoForm(): void
    {
        $state = new ConsentState();

        self::assertNull($state->getTitle());
    }

    // --- getCompulsory ---

    public function testGetCompulsoryDefaultsToCompulsory(): void
    {
        $state = new ConsentState();

        self::assertSame('compulsory', $state->getCompulsory());
    }

    public function testGetCompulsoryFromConsentForm(): void
    {
        $form = new ConsentForm();
        $form->setCompulsory('opt-in');

        $state = new ConsentState(consentForm: $form);

        self::assertSame('opt-in', $state->getCompulsory());
    }

    // --- getSignature / hasSignature ---

    public function testGetSignatureReturnsConsentObject(): void
    {
        $consent = $this->makeConsent();
        $state = new ConsentState(signature: $consent);

        self::assertSame($consent, $state->getSignature());
    }

    public function testGetSignatureNullWhenNotSet(): void
    {
        $state = new ConsentState();

        self::assertNull($state->getSignature());
    }

    public function testGetSubjectNameReturnsParticipantName(): void
    {
        $participant = $this->createStub(\Ferienpass\CoreBundle\Entity\Participant\BaseParticipant::class);
        $participant->method('getName')->willReturn('Anna Smith');

        $state = new ConsentState(participant: $participant);

        self::assertSame('Anna Smith', $state->getSubjectName());
    }

    public function testGetSubjectNameFallsBackToUserName(): void
    {
        $user = new User();
        $user->setFirstname('John');
        $user->setLastname('Doe');

        $state = new ConsentState(user: $user);

        self::assertSame('John Doe', $state->getSubjectName());
    }

    public function testGetSubjectNameReturnsDashWhenNone(): void
    {
        $state = new ConsentState();

        self::assertSame('-', $state->getSubjectName());
    }

    public function testGetSigneeNameReturnsSignatureUserName(): void
    {
        $user = new User();
        $user->setFirstname('Jane');
        $user->setLastname('Doe');

        $consentText = new ConsentText('text');
        $consent = new Consent($user, consentText: $consentText);

        $state = new ConsentState(signature: $consent);

        self::assertSame('Jane Doe', $state->getSigneeName());
    }

    public function testGetSigneeNameReturnsNullWithoutSignature(): void
    {
        $state = new ConsentState();

        self::assertNull($state->getSigneeName());
    }

    public function testGetEditionReturnsConsentFormEdition(): void
    {
        $edition = $this->createStub(\Ferienpass\CoreBundle\Entity\Edition::class);

        $form = new ConsentForm();
        $form->setEdition($edition);

        $state = new ConsentState(consentForm: $form);

        self::assertSame($edition, $state->getEdition());
    }

    public function testGetEditionReturnsNullWhenNotSet(): void
    {
        $state = new ConsentState(consentForm: new ConsentForm());

        self::assertNull($state->getEdition());
    }

    public function testGetEditionNameReturnsEditionName(): void
    {
        $edition = $this->createStub(\Ferienpass\CoreBundle\Entity\Edition::class);
        $edition->method('getName')->willReturn('Summer 2024');

        $form = new ConsentForm();
        $form->setEdition($edition);

        $state = new ConsentState(consentForm: $form);

        self::assertSame('Summer 2024', $state->getEditionName());
    }

    public function testGetEditionNameReturnsNullWhenNoEdition(): void
    {
        $state = new ConsentState();

        self::assertNull($state->getEditionName());
    }

    public function testGetScopeNameReturnsEditionNameWhenNoOffer(): void
    {
        $edition = $this->createStub(\Ferienpass\CoreBundle\Entity\Edition::class);
        $edition->method('getName')->willReturn('Edition A');

        $form = new ConsentForm();
        $form->setEdition($edition);

        $state = new ConsentState(consentForm: $form);

        self::assertSame('Edition A', $state->getScopeName());
    }

    public function testGetScopeNameReturnsNullWhenNeitherOfferNorEdition(): void
    {
        $state = new ConsentState(consentForm: new ConsentForm());

        self::assertNull($state->getScopeName());
    }

    public function testGetScopeNameReturnsOfferNameWhenOfferSet(): void
    {
        $offer = $this->createStub(\Ferienpass\CoreBundle\Entity\Offer\OfferInterface::class);
        $offer->method('getName')->willReturn('Awesome Offer');

        $form = new ConsentForm();
        $form->setOffer($offer);

        $state = new ConsentState(consentForm: $form);

        self::assertSame('Awesome Offer', $state->getScopeName());
    }

    private function makeUser(): User
    {
        return new User();
    }

    private function makeConsent(int $decision = 1, bool $revoked = false, ?ConsentText $consentText = null, ?ConsentForm $consentForm = null): Consent
    {
        $consent = new Consent($this->makeUser(), consentText: $consentText, consentForm: $consentForm, decision: $decision);
        if ($revoked) {
            $consent->revoke();
        }

        return $consent;
    }
}
