<?php

declare(strict_types=1);

namespace App\Scolarite\Domain\Model\TeacherReplacement;

use App\Administration\Domain\Model\User\UserId;
use App\Scolarite\Domain\Event\RemplacementDesigne;
use App\Scolarite\Domain\Event\RemplacementTermine;
use App\Scolarite\Domain\Exception\DatesRemplacementInvalidesException;
use App\Scolarite\Domain\Exception\RemplacementDejaTermineException;
use App\Scolarite\Domain\Exception\RemplacementSameTeacherException;
use App\Shared\Domain\AggregateRoot;
use App\Shared\Domain\Tenant\TenantId;
use DateTimeImmutable;
use InvalidArgumentException;

/**
 * Aggregate Root représentant le remplacement temporaire d'un enseignant.
 *
 * Un administrateur désigne un remplaçant pour un enseignant absent, avec des dates
 * de début/fin et un périmètre de classes/matières. Le remplaçant peut saisir de
 * nouvelles notes et lire les notes passées (lecture seule).
 *
 * @see FR9: Désigner remplaçant temporaire
 */
final class TeacherReplacement extends AggregateRoot
{
    public private(set) DateTimeImmutable $updatedAt;

    /** @param array<ClassSubjectPair> $classes */
    private function __construct(
        public private(set) TeacherReplacementId $id,
        public private(set) TenantId $tenantId,
        public private(set) UserId $replacedTeacherId,
        public private(set) UserId $replacementTeacherId,
        public private(set) DateTimeImmutable $startDate,
        public private(set) DateTimeImmutable $endDate,
        public private(set) ReplacementStatus $status,
        public private(set) array $classes,
        public private(set) ?string $reason,
        public private(set) UserId $createdBy,
        public private(set) DateTimeImmutable $createdAt,
        public private(set) ?DateTimeImmutable $endedAt,
    ) {
        $this->updatedAt = $createdAt;
    }

    /** @param array<ClassSubjectPair> $classes */
    public static function designer(
        TenantId $tenantId,
        UserId $replacedTeacherId,
        UserId $replacementTeacherId,
        DateTimeImmutable $startDate,
        DateTimeImmutable $endDate,
        array $classes,
        ?string $reason,
        UserId $createdBy,
        DateTimeImmutable $now,
    ): self {
        if ($replacedTeacherId->equals($replacementTeacherId)) {
            throw RemplacementSameTeacherException::create();
        }

        if ($endDate < $startDate) {
            throw DatesRemplacementInvalidesException::finAvantDebut($startDate, $endDate);
        }

        if ($classes === []) {
            throw new InvalidArgumentException('Au moins une paire classe/matière est requise.');
        }

        $replacement = new self(
            id: TeacherReplacementId::generate(),
            tenantId: $tenantId,
            replacedTeacherId: $replacedTeacherId,
            replacementTeacherId: $replacementTeacherId,
            startDate: $startDate,
            endDate: $endDate,
            status: ReplacementStatus::ACTIVE,
            classes: $classes,
            reason: $reason,
            createdBy: $createdBy,
            createdAt: $now,
            endedAt: null,
        );

        $replacement->recordEvent(new RemplacementDesigne(
            replacementId: $replacement->id,
            replacedTeacherId: $replacedTeacherId,
            replacementTeacherId: $replacementTeacherId,
            startDate: $startDate,
            endDate: $endDate,
            occurredOn: $now,
        ));

        return $replacement;
    }

    public function isActive(DateTimeImmutable $now): bool
    {
        return $this->status === ReplacementStatus::ACTIVE
            && $now >= $this->startDate
            && $now <= $this->endDate;
    }

    public function isExpired(DateTimeImmutable $now): bool
    {
        return $this->status === ReplacementStatus::ACTIVE
            && $now > $this->endDate;
    }

    public function terminer(DateTimeImmutable $at): void
    {
        if ($this->status === ReplacementStatus::ENDED) {
            throw RemplacementDejaTermineException::withId($this->id);
        }

        $this->status = ReplacementStatus::ENDED;
        $this->endedAt = $at;
        $this->updatedAt = $at;

        $this->recordEvent(new RemplacementTermine(
            replacementId: $this->id,
            replacedTeacherId: $this->replacedTeacherId,
            replacementTeacherId: $this->replacementTeacherId,
            startDate: $this->startDate,
            endDate: $this->endDate,
            occurredOn: $at,
        ));
    }

    public function couvreClasseMatiere(ClassSubjectPair $pair): bool
    {
        foreach ($this->classes as $classSubjectPair) {
            if ($classSubjectPair->equals($pair)) {
                return true;
            }
        }

        return false;
    }

    /**
     * Reconstitue un TeacherReplacement depuis le stockage.
     *
     * @internal Pour usage Infrastructure uniquement
     *
     * @param array<ClassSubjectPair> $classes
     */
    public static function reconstitute(
        TeacherReplacementId $id,
        TenantId $tenantId,
        UserId $replacedTeacherId,
        UserId $replacementTeacherId,
        DateTimeImmutable $startDate,
        DateTimeImmutable $endDate,
        ReplacementStatus $status,
        array $classes,
        ?string $reason,
        UserId $createdBy,
        DateTimeImmutable $createdAt,
        ?DateTimeImmutable $endedAt,
        DateTimeImmutable $updatedAt,
    ): self {
        $replacement = new self(
            id: $id,
            tenantId: $tenantId,
            replacedTeacherId: $replacedTeacherId,
            replacementTeacherId: $replacementTeacherId,
            startDate: $startDate,
            endDate: $endDate,
            status: $status,
            classes: $classes,
            reason: $reason,
            createdBy: $createdBy,
            createdAt: $createdAt,
            endedAt: $endedAt,
        );

        $replacement->updatedAt = $updatedAt;

        return $replacement;
    }
}
