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

use Ferienpass\CoreBundle\Entity\Consent;
use Ferienpass\CoreBundle\Entity\ConsentForm;
use Ferienpass\CoreBundle\Entity\ConsentText;
use Ferienpass\CoreBundle\Entity\Offer\OfferInterface;
use Ferienpass\CoreBundle\Entity\User;
use PHPUnit\Framework\TestCase;

final class ConsentTest extends TestCase
{
    public function testDefaultDecisionIsSigned(): void
    {
        $consent = new Consent($this->makeUser());

        self::assertSame(1, $consent->getDecision());
        self::assertNotNull($consent->getSignedAt());
    }

    public function testDecisionZeroDoesNotSetSignedAt(): void
    {
        $consent = new Consent($this->makeUser(), decision: 0);

        self::assertSame(0, $consent->getDecision());
        self::assertNull($consent->getSignedAt());
    }

    public function testSetDecisionToSignedSetsSignedAt(): void
    {
        $consent = new Consent($this->makeUser(), decision: 0);
        self::assertNull($consent->getSignedAt());

        $consent->setDecision(1);

        self::assertSame(1, $consent->getDecision());
        self::assertNotNull($consent->getSignedAt());
    }

    public function testSetDecisionToSignedDoesNotOverwriteExistingSignedAt(): void
    {
        $consent = new Consent($this->makeUser()); // decision=1, signedAt set
        $originalSignedAt = $consent->getSignedAt();

        $consent->setDecision(0);
        $consent->setDecision(1); // set again — should not overwrite signedAt

        self::assertSame($originalSignedAt, $consent->getSignedAt());
    }

    public function testSetDecisionToSignedUnrevokes(): void
    {
        $consent = new Consent($this->makeUser());
        $consent->revoke();
        self::assertTrue($consent->isRevoked());

        $consent->setDecision(1);

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

    public function testRevokeAndIsRevoked(): void
    {
        $consent = new Consent($this->makeUser());
        self::assertFalse($consent->isRevoked());

        $consent->revoke();
        self::assertTrue($consent->isRevoked());
        self::assertNotNull($consent->getRevokedAt());
    }

    public function testRevokeIsIdempotent(): void
    {
        $consent = new Consent($this->makeUser());
        $consent->revoke();
        $revokedAt = $consent->getRevokedAt();

        $consent->revoke(); // second revoke should not change revokedAt

        self::assertSame($revokedAt, $consent->getRevokedAt());
    }

    public function testUnrevokeClears(): void
    {
        $consent = new Consent($this->makeUser());
        $consent->revoke();
        $consent->unrevoke();

        self::assertFalse($consent->isRevoked());
        self::assertNull($consent->getRevokedAt());
    }

    public function testIsValidWithNoTextOrHash(): void
    {
        $consent = new Consent($this->makeUser());

        // No consentText, no hash — always valid regardless of passed text
        self::assertTrue($consent->isValid(null));
        self::assertTrue($consent->isValid('anything'));
    }

    public function testIsValidWithMatchingText(): void
    {
        $text = new ConsentText('hello world');
        $consent = new Consent($this->makeUser(), consentText: $text);

        self::assertTrue($consent->isValid('hello world'));
    }

    public function testIsValidWithChangedText(): void
    {
        $text = new ConsentText('original text');
        $consent = new Consent($this->makeUser(), consentText: $text);

        self::assertFalse($consent->isValid('different text'));
    }

    public function testIsValidWithNoPassedTextAndConsentTextPresent(): void
    {
        $text = new ConsentText('some text');
        $consent = new Consent($this->makeUser(), consentText: $text);

        // null passed → hash is null → always valid
        self::assertTrue($consent->isValid(null));
    }

    public function testIsValidExpiredByValidUntil(): void
    {
        $past = new \DateTimeImmutable('-1 day');
        $consent = new Consent($this->makeUser(), validUntil: $past);

        self::assertFalse($consent->isValid(null));
    }

    public function testIsValidNotExpiredByFutureValidUntil(): void
    {
        $future = new \DateTimeImmutable('+1 day');
        $consent = new Consent($this->makeUser(), validUntil: $future);

        self::assertTrue($consent->isValid(null));
    }

    public function testCalculateHashFromConsentText(): void
    {
        $text = new ConsentText('my consent text');
        $consent = new Consent($this->makeUser(), consentText: $text);

        self::assertSame(hash('sha256', 'my consent text'), $consent->getHash());
    }

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

        $consent = new Consent($this->makeUser(), consentForm: $consentForm);

        self::assertNotNull($consent->getConsentText());
        self::assertSame('form text', $consent->getConsentText()->getText());
    }

    public function testGetTypeForFormConsent(): void
    {
        $consentForm = new ConsentForm();
        $consent = new Consent($this->makeUser(), consentForm: $consentForm);

        self::assertSame('form_consent', $consent->getType());
    }

    public function testGetTypeForBaseConsent(): void
    {
        $consent = new Consent($this->makeUser());

        self::assertSame('consent', $consent->getType());
    }

    public function testGetParticipant(): void
    {
        $consent = new Consent($this->makeUser());
        self::assertNull($consent->getParticipant());
    }

    public function testCreatedAtIsSet(): void
    {
        $before = new \DateTimeImmutable();
        $consent = new Consent($this->makeUser());
        $after = new \DateTimeImmutable();

        self::assertGreaterThanOrEqual($before, $consent->getCreatedAt());
        self::assertLessThanOrEqual($after, $consent->getCreatedAt());
    }

    public function testIdIsNullByDefault(): void
    {
        $consent = new Consent($this->makeUser());

        self::assertNull($consent->getId());
    }

    public function testGetValidUntilNullByDefault(): void
    {
        $consent = new Consent($this->makeUser());

        self::assertNull($consent->getValidUntil());
    }

    public function testGetValidUntilReturnsSet(): void
    {
        $until = new \DateTimeImmutable('+30 days');
        $consent = new Consent($this->makeUser(), validUntil: $until);

        self::assertSame($until, $consent->getValidUntil());
    }

    public function testGetConsentFormReturnsNull(): void
    {
        $consent = new Consent($this->makeUser());

        self::assertNull($consent->getConsentForm());
    }

    public function testGetConsentFormReturnsSet(): void
    {
        $form = new ConsentForm();
        $consent = new Consent($this->makeUser(), consentForm: $form);

        self::assertSame($form, $consent->getConsentForm());
    }

    public function testGetEditionNullWhenNoConsentForm(): void
    {
        $consent = new Consent($this->makeUser());

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

    public function testGetEditionNullWhenConsentFormHasNoEdition(): void
    {
        $form = new ConsentForm();
        $consent = new Consent($this->makeUser(), consentForm: $form);

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

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

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

        $consent = new Consent($this->makeUser(), consentForm: $form);

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

    public function testGetOfferNullWhenNoConsentForm(): void
    {
        $consent = new Consent($this->makeUser());

        self::assertNull($consent->getOffer());
    }

    public function testGetOfferNullWhenConsentFormHasNoOffer(): void
    {
        $form = new ConsentForm();
        $consent = new Consent($this->makeUser(), consentForm: $form);

        self::assertNull($consent->getOffer());
    }

    public function testGetUserReturnsUser(): void
    {
        $user = $this->makeUser();
        $consent = new Consent($user);

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

    public function testGetOfferReturnsOfferFromConsentForm(): void
    {
        $offer = $this->createStub(OfferInterface::class);
        $form = new ConsentForm();
        $form->setOffer($offer);

        $consent = new Consent($this->makeUser(), consentForm: $form);

        self::assertSame($offer, $consent->getOffer());
    }

    public function testGetTypeReturnsFormConsentWhenConsentFormSet(): void
    {
        $consent = new Consent($this->makeUser(), consentForm: new ConsentForm());

        self::assertSame('form_consent', $consent->getType());
    }

    public function testGetTypeReturnsConsentWhenNoConsentForm(): void
    {
        $consent = new Consent($this->makeUser());

        self::assertSame('consent', $consent->getType());
    }

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