<?php

declare(strict_types=1);

namespace App\Administration\Domain\Model\Invitation;

use App\Administration\Domain\Event\InvitationParentActivee;
use App\Administration\Domain\Event\InvitationParentEnvoyee;
use App\Administration\Domain\Exception\InvitationDejaActiveeException;
use App\Administration\Domain\Exception\InvitationExpireeException;
use App\Administration\Domain\Exception\InvitationNonEnvoyeeException;
use App\Administration\Domain\Model\User\Email;
use App\Administration\Domain\Model\User\UserId;
use App\Shared\Domain\AggregateRoot;
use App\Shared\Domain\Tenant\TenantId;
use DateTimeImmutable;

use function sprintf;

final class ParentInvitation extends AggregateRoot
{
    private const int EXPIRATION_DAYS = 7;

    public private(set) ?DateTimeImmutable $sentAt = null;
    public private(set) ?DateTimeImmutable $activatedAt = null;
    public private(set) ?UserId $activatedUserId = null;

    private function __construct(
        public private(set) ParentInvitationId $id,
        public private(set) TenantId $tenantId,
        public private(set) UserId $studentId,
        public private(set) Email $parentEmail,
        public private(set) InvitationCode $code,
        public private(set) InvitationStatus $status,
        public private(set) DateTimeImmutable $expiresAt,
        public private(set) DateTimeImmutable $createdAt,
        public private(set) UserId $createdBy,
    ) {
    }

    public static function creer(
        TenantId $tenantId,
        UserId $studentId,
        Email $parentEmail,
        InvitationCode $code,
        DateTimeImmutable $createdAt,
        UserId $createdBy,
    ): self {
        return new self(
            id: ParentInvitationId::generate(),
            tenantId: $tenantId,
            studentId: $studentId,
            parentEmail: $parentEmail,
            code: $code,
            status: InvitationStatus::PENDING,
            expiresAt: $createdAt->modify(sprintf('+%d days', self::EXPIRATION_DAYS)),
            createdAt: $createdAt,
            createdBy: $createdBy,
        );
    }

    /**
     * @internal For use by Infrastructure layer only
     */
    public static function reconstitute(
        ParentInvitationId $id,
        TenantId $tenantId,
        UserId $studentId,
        Email $parentEmail,
        InvitationCode $code,
        InvitationStatus $status,
        DateTimeImmutable $expiresAt,
        DateTimeImmutable $createdAt,
        UserId $createdBy,
        ?DateTimeImmutable $sentAt,
        ?DateTimeImmutable $activatedAt,
        ?UserId $activatedUserId,
    ): self {
        $invitation = new self(
            id: $id,
            tenantId: $tenantId,
            studentId: $studentId,
            parentEmail: $parentEmail,
            code: $code,
            status: $status,
            expiresAt: $expiresAt,
            createdAt: $createdAt,
            createdBy: $createdBy,
        );

        $invitation->sentAt = $sentAt;
        $invitation->activatedAt = $activatedAt;
        $invitation->activatedUserId = $activatedUserId;

        return $invitation;
    }

    public function envoyer(DateTimeImmutable $at): void
    {
        if (!$this->status->peutEnvoyer()) {
            throw InvitationDejaActiveeException::pourInvitation($this->id);
        }

        $this->status = InvitationStatus::SENT;
        $this->sentAt = $at;

        $this->recordEvent(new InvitationParentEnvoyee(
            invitationId: $this->id,
            studentId: $this->studentId,
            parentEmail: $this->parentEmail,
            tenantId: $this->tenantId,
            occurredOn: $at,
        ));
    }

    /**
     * Validate that the invitation can be activated (not expired, not already activated, has been sent).
     * Does NOT change state - use activer() after successful user creation.
     *
     * @throws InvitationDejaActiveeException if already activated
     * @throws InvitationNonEnvoyeeException  if not yet sent
     * @throws InvitationExpireeException     if expired
     */
    public function validerPourActivation(DateTimeImmutable $at): void
    {
        if ($this->status === InvitationStatus::ACTIVATED) {
            throw InvitationDejaActiveeException::pourInvitation($this->id);
        }

        if ($this->status !== InvitationStatus::SENT) {
            throw InvitationNonEnvoyeeException::pourActivation($this->id);
        }

        if ($this->estExpiree($at)) {
            throw InvitationExpireeException::pourInvitation($this->id);
        }
    }

    public function activer(UserId $parentUserId, DateTimeImmutable $at): void
    {
        $this->validerPourActivation($at);

        $this->status = InvitationStatus::ACTIVATED;
        $this->activatedAt = $at;
        $this->activatedUserId = $parentUserId;

        $this->recordEvent(new InvitationParentActivee(
            invitationId: $this->id,
            studentId: $this->studentId,
            parentUserId: $parentUserId,
            tenantId: $this->tenantId,
            occurredOn: $at,
        ));
    }

    public function marquerExpiree(): void
    {
        if ($this->status->peutExpirer()) {
            $this->status = InvitationStatus::EXPIRED;
        }
    }

    public function renvoyer(InvitationCode $nouveauCode, DateTimeImmutable $at): void
    {
        if (!$this->status->peutRenvoyer()) {
            throw InvitationDejaActiveeException::pourInvitation($this->id);
        }

        $this->code = $nouveauCode;
        $this->status = InvitationStatus::SENT;
        $this->sentAt = $at;
        $this->expiresAt = $at->modify(sprintf('+%d days', self::EXPIRATION_DAYS));

        $this->recordEvent(new InvitationParentEnvoyee(
            invitationId: $this->id,
            studentId: $this->studentId,
            parentEmail: $this->parentEmail,
            tenantId: $this->tenantId,
            occurredOn: $at,
        ));
    }

    public function estExpiree(DateTimeImmutable $at): bool
    {
        return $at >= $this->expiresAt;
    }

    public function estActivee(): bool
    {
        return $this->status === InvitationStatus::ACTIVATED;
    }
}
