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

use Doctrine\ORM\Mapping as ORM;
use Ferienpass\CoreBundle\Entity\Offer\OfferInterface;
use Ferienpass\CoreBundle\Entity\Participant\ParticipantInterface;
use Symfony\Bridge\Doctrine\Types\UuidType;
use Symfony\Component\Uid\Uuid;

#[ORM\Entity]
class Consent
{
    #[ORM\Id]
    #[ORM\GeneratedValue]
    #[ORM\Column(type: 'integer', options: ['unsigned' => true])]
    private ?int $id = null;

    #[ORM\Column(type: UuidType::NAME)]
    private Uuid $uuid;

    #[ORM\Column(type: 'datetime_immutable', options: ['default' => 'CURRENT_TIMESTAMP'])]
    private \DateTimeInterface $createdAt;

    #[ORM\Column(type: 'datetime_immutable', nullable: true)]
    private ?\DateTimeInterface $signedAt = null;

    #[ORM\Column(type: 'datetime_immutable', nullable: true)]
    private ?\DateTimeInterface $revokedAt = null;

    /**
     * Name of the natural person who signed, when the signature was not given
     * by an account holder — i.e. the legal guardian signing via the shared
     * consent link for a minor's anonymous application.
     */
    #[ORM\Column(type: 'string', nullable: true)]
    private ?string $signedBy = null;

    public function __construct(
        #[ORM\ManyToOne(targetEntity: User::class, inversedBy: 'consents')]
        #[ORM\JoinColumn(name: 'user_id', referencedColumnName: 'id', nullable: true, onDelete: 'cascade')]
        private ?User $user = null,
        #[ORM\Column(type: 'string', length: 64, nullable: true)]
        private ?string $hash = null,
        #[ORM\Column(type: 'datetime_immutable', nullable: true)]
        private ?\DateTimeImmutable $validUntil = null,
        #[ORM\ManyToOne(targetEntity: ParticipantInterface::class, inversedBy: 'consents')]
        #[ORM\JoinColumn(name: 'participant_id', referencedColumnName: 'id', onDelete: 'cascade')]
        private ?ParticipantInterface $participant = null,
        #[ORM\ManyToOne(targetEntity: ConsentForm::class, inversedBy: 'signatures')]
        #[ORM\JoinColumn(name: 'consent_form', referencedColumnName: 'id', onDelete: 'SET NULL')]
        private ?ConsentForm $consentForm = null,
        #[ORM\ManyToOne(targetEntity: ConsentText::class, cascade: ['persist'])]
        #[ORM\JoinColumn(name: 'consent_text', referencedColumnName: 'id', onDelete: 'SET NULL')]
        private ?ConsentText $consentText = null,
        #[ORM\Column(type: 'integer', options: ['default' => 1])]
        private int $decision = 1,
    ) {
        $this->uuid = Uuid::v7();
        $this->createdAt = new \DateTimeImmutable();
        if (1 === $this->decision) {
            $this->signedAt = new \DateTimeImmutable();
        }
        if (null === $this->consentText && null !== $this->consentForm) {
            $this->consentText = $this->consentForm->getConsentText();
        }
        $this->calculateHash();
    }

    public function calculateHash(): void
    {
        if (null === $this->consentText) {
            return;
        }

        $this->hash = hash('sha256', $this->consentText->getText());
    }

    public function getId(): ?int
    {
        return $this->id;
    }

    public function getUuid(): Uuid
    {
        return $this->uuid;
    }

    public function getCreatedAt(): \DateTimeInterface
    {
        return $this->createdAt;
    }

    public function getSignedAt(): ?\DateTimeInterface
    {
        return $this->signedAt;
    }

    public function getDecision(): int
    {
        return $this->decision;
    }

    public function setDecision(int $decision): void
    {
        $this->decision = $decision;

        if (1 === $decision && null === $this->signedAt) {
            $this->signedAt = new \DateTimeImmutable();
        }

        if (1 === $decision) {
            $this->unrevoke();
        }
    }

    public function getUser(): ?User
    {
        return $this->user;
    }

    /**
     * Bind an anonymous consent to a User on later account registration.
     * One-way — an account-bound consent should not be re-anonymized.
     */
    public function setUser(User $user): void
    {
        $this->user = $user;
    }

    public function getHash(): ?string
    {
        return $this->consentText?->getHash() ?? $this->hash;
    }

    public function getEdition(): ?Edition
    {
        if (null === $this->consentForm) {
            return null;
        }

        if (null === $this->consentForm->getEdition()) {
            return null;
        }

        return $this->consentForm->getEdition();
    }

    public function getOffer(): ?OfferInterface
    {
        if (null === $this->consentForm) {
            return null;
        }

        if (null === $this->consentForm->getOffer()) {
            return null;
        }

        return $this->consentForm->getOffer();
    }

    public function getConsentForm(): ?ConsentForm
    {
        return $this->consentForm;
    }

    public function getConsentText(): ?ConsentText
    {
        return $this->consentText;
    }

    public function isValid(?string $text = null): bool
    {
        if ($this->validUntil instanceof \DateTimeInterface && $this->validUntil < new \DateTimeImmutable()) {
            return false;
        }

        $hash = null !== $text ? hash('sha256', $text) : null;

        if (null !== $this->consentText) {
            return null === $hash || hash_equals($this->consentText->getHash(), (string) $hash);
        }

        return null === $this->hash || hash_equals($this->hash, (string) $hash);
    }

    public function getSignedBy(): ?string
    {
        return $this->signedBy;
    }

    public function setSignedBy(?string $signedBy): void
    {
        $this->signedBy = $signedBy;
    }

    public function getRevokedAt(): ?\DateTimeInterface
    {
        return $this->revokedAt;
    }

    public function getValidUntil(): ?\DateTimeImmutable
    {
        return $this->validUntil;
    }

    public function isRevoked(): bool
    {
        return $this->revokedAt instanceof \DateTimeInterface;
    }

    public function revoke(): void
    {
        if (!$this->getRevokedAt() instanceof \DateTimeInterface) {
            $this->revokedAt = new \DateTimeImmutable();
        }
    }

    public function unrevoke(): void
    {
        $this->revokedAt = null;
    }

    public function getParticipant(): ?ParticipantInterface
    {
        return $this->participant;
    }

    public function setParticipant(?ParticipantInterface $participant): void
    {
        $this->participant = $participant;
    }

    public function getType(): string
    {
        if (null !== $this->consentForm) {
            return 'form_consent';
        }

        return 'consent';
    }
}
