<?php

declare(strict_types=1);

namespace App\Scolarite\Domain\Model\Evaluation;

use App\Administration\Domain\Model\SchoolClass\ClassId;
use App\Administration\Domain\Model\Subject\SubjectId;
use App\Administration\Domain\Model\User\UserId;
use App\Scolarite\Domain\Event\EvaluationCreee;
use App\Scolarite\Domain\Event\EvaluationModifiee;
use App\Scolarite\Domain\Event\EvaluationSupprimee;
use App\Scolarite\Domain\Event\NotesPubliees;
use App\Scolarite\Domain\Exception\BaremeNonModifiableException;
use App\Scolarite\Domain\Exception\EvaluationDejaSupprimeeException;
use App\Scolarite\Domain\Exception\NotesDejaPublieesException;
use App\Shared\Domain\AggregateRoot;
use App\Shared\Domain\Tenant\TenantId;
use DateTimeImmutable;

final class Evaluation extends AggregateRoot
{
    public private(set) DateTimeImmutable $updatedAt;
    public private(set) ?DateTimeImmutable $gradesPublishedAt = null;

    private function __construct(
        public private(set) EvaluationId $id,
        public private(set) TenantId $tenantId,
        public private(set) ClassId $classId,
        public private(set) SubjectId $subjectId,
        public private(set) UserId $teacherId,
        public private(set) string $title,
        public private(set) ?string $description,
        public private(set) DateTimeImmutable $evaluationDate,
        public private(set) GradeScale $gradeScale,
        public private(set) Coefficient $coefficient,
        public private(set) EvaluationStatus $status,
        public private(set) DateTimeImmutable $createdAt,
    ) {
        $this->updatedAt = $createdAt;
    }

    public static function creer(
        TenantId $tenantId,
        ClassId $classId,
        SubjectId $subjectId,
        UserId $teacherId,
        string $title,
        ?string $description,
        DateTimeImmutable $evaluationDate,
        GradeScale $gradeScale,
        Coefficient $coefficient,
        DateTimeImmutable $now,
    ): self {
        $evaluation = new self(
            id: EvaluationId::generate(),
            tenantId: $tenantId,
            classId: $classId,
            subjectId: $subjectId,
            teacherId: $teacherId,
            title: $title,
            description: $description,
            evaluationDate: $evaluationDate,
            gradeScale: $gradeScale,
            coefficient: $coefficient,
            status: EvaluationStatus::PUBLISHED,
            createdAt: $now,
        );

        $evaluation->recordEvent(new EvaluationCreee(
            evaluationId: $evaluation->id,
            classId: (string) $classId,
            subjectId: (string) $subjectId,
            teacherId: (string) $teacherId,
            title: $title,
            evaluationDate: $evaluationDate,
            occurredOn: $now,
        ));

        return $evaluation;
    }

    public function modifier(
        string $title,
        ?string $description,
        Coefficient $coefficient,
        DateTimeImmutable $evaluationDate,
        ?GradeScale $gradeScale,
        bool $hasGrades,
        DateTimeImmutable $now,
    ): void {
        if ($this->status === EvaluationStatus::DELETED) {
            throw EvaluationDejaSupprimeeException::withId($this->id);
        }

        if ($gradeScale !== null && !$this->gradeScale->equals($gradeScale) && $hasGrades) {
            throw BaremeNonModifiableException::carNotesExistantes($this->id);
        }

        $this->title = $title;
        $this->description = $description;
        $this->coefficient = $coefficient;
        $this->evaluationDate = $evaluationDate;

        if ($gradeScale !== null && !$hasGrades) {
            $this->gradeScale = $gradeScale;
        }

        $this->updatedAt = $now;

        $this->recordEvent(new EvaluationModifiee(
            evaluationId: $this->id,
            title: $title,
            evaluationDate: $evaluationDate,
            occurredOn: $now,
        ));
    }

    public function publierNotes(DateTimeImmutable $now): void
    {
        if ($this->status === EvaluationStatus::DELETED) {
            throw EvaluationDejaSupprimeeException::withId($this->id);
        }

        if ($this->gradesPublishedAt !== null) {
            throw NotesDejaPublieesException::pourEvaluation($this->id);
        }

        $this->gradesPublishedAt = $now;
        $this->updatedAt = $now;

        $this->recordEvent(new NotesPubliees(
            evaluationId: $this->id,
            occurredOn: $now,
        ));
    }

    public function notesPubliees(): bool
    {
        return $this->gradesPublishedAt !== null;
    }

    public function supprimer(DateTimeImmutable $now): void
    {
        if ($this->status === EvaluationStatus::DELETED) {
            throw EvaluationDejaSupprimeeException::withId($this->id);
        }

        $this->status = EvaluationStatus::DELETED;
        $this->updatedAt = $now;

        $this->recordEvent(new EvaluationSupprimee(
            evaluationId: $this->id,
            occurredOn: $now,
        ));
    }

    /**
     * @internal Pour usage Infrastructure uniquement
     */
    public static function reconstitute(
        EvaluationId $id,
        TenantId $tenantId,
        ClassId $classId,
        SubjectId $subjectId,
        UserId $teacherId,
        string $title,
        ?string $description,
        DateTimeImmutable $evaluationDate,
        GradeScale $gradeScale,
        Coefficient $coefficient,
        EvaluationStatus $status,
        DateTimeImmutable $createdAt,
        DateTimeImmutable $updatedAt,
        ?DateTimeImmutable $gradesPublishedAt = null,
    ): self {
        $evaluation = new self(
            id: $id,
            tenantId: $tenantId,
            classId: $classId,
            subjectId: $subjectId,
            teacherId: $teacherId,
            title: $title,
            description: $description,
            evaluationDate: $evaluationDate,
            gradeScale: $gradeScale,
            coefficient: $coefficient,
            status: $status,
            createdAt: $createdAt,
        );

        $evaluation->updatedAt = $updatedAt;
        $evaluation->gradesPublishedAt = $gradesPublishedAt;

        return $evaluation;
    }
}
