<?php

declare(strict_types=1);

namespace App\Scolarite\Domain\Model\Grade;

use App\Administration\Domain\Model\User\UserId;
use App\Scolarite\Domain\Event\NoteModifiee;
use App\Scolarite\Domain\Event\NoteSaisie;
use App\Scolarite\Domain\Exception\AppreciationTropLongueException;
use App\Scolarite\Domain\Exception\NoteRequiseException;
use App\Scolarite\Domain\Exception\ValeurNoteInvalideException;
use App\Scolarite\Domain\Model\Evaluation\EvaluationId;
use App\Scolarite\Domain\Model\Evaluation\GradeScale;
use App\Shared\Domain\AggregateRoot;
use App\Shared\Domain\Tenant\TenantId;
use DateTimeImmutable;

use function mb_strlen;

final class Grade extends AggregateRoot
{
    public const int MAX_APPRECIATION_LENGTH = 500;

    public private(set) DateTimeImmutable $updatedAt;
    public private(set) ?string $appreciation;
    public private(set) ?DateTimeImmutable $appreciationUpdatedAt;

    private function __construct(
        public private(set) GradeId $id,
        public private(set) TenantId $tenantId,
        public private(set) EvaluationId $evaluationId,
        public private(set) UserId $studentId,
        public private(set) ?GradeValue $value,
        public private(set) GradeStatus $status,
        public private(set) UserId $createdBy,
        public private(set) DateTimeImmutable $createdAt,
    ) {
        $this->updatedAt = $createdAt;
        $this->appreciation = null;
        $this->appreciationUpdatedAt = null;
    }

    public static function saisir(
        TenantId $tenantId,
        EvaluationId $evaluationId,
        UserId $studentId,
        ?GradeValue $value,
        GradeStatus $status,
        GradeScale $gradeScale,
        UserId $createdBy,
        DateTimeImmutable $now,
    ): self {
        self::validerCoherence($value, $status, $gradeScale);

        $grade = new self(
            id: GradeId::generate(),
            tenantId: $tenantId,
            evaluationId: $evaluationId,
            studentId: $studentId,
            value: $value,
            status: $status,
            createdBy: $createdBy,
            createdAt: $now,
        );

        $grade->recordEvent(new NoteSaisie(
            gradeId: $grade->id,
            evaluationId: (string) $evaluationId,
            studentId: (string) $studentId,
            value: $value?->value,
            status: $status->value,
            createdBy: (string) $createdBy,
            occurredOn: $now,
        ));

        return $grade;
    }

    public function modifier(
        ?GradeValue $value,
        GradeStatus $status,
        GradeScale $gradeScale,
        UserId $modifiedBy,
        DateTimeImmutable $now,
    ): void {
        self::validerCoherence($value, $status, $gradeScale);

        $oldValue = $this->value?->value;
        $oldStatus = $this->status->value;

        $this->value = $value;
        $this->status = $status;
        $this->updatedAt = $now;

        $this->recordEvent(new NoteModifiee(
            gradeId: $this->id,
            evaluationId: (string) $this->evaluationId,
            oldValue: $oldValue,
            newValue: $value?->value,
            oldStatus: $oldStatus,
            newStatus: $status->value,
            modifiedBy: (string) $modifiedBy,
            occurredOn: $now,
        ));
    }

    public function saisirAppreciation(?string $appreciation, DateTimeImmutable $now): void
    {
        if ($appreciation !== null && mb_strlen($appreciation) > self::MAX_APPRECIATION_LENGTH) {
            throw AppreciationTropLongueException::depasseLimite(mb_strlen($appreciation), self::MAX_APPRECIATION_LENGTH);
        }

        $this->appreciation = $appreciation !== null && $appreciation !== '' ? $appreciation : null;
        $this->appreciationUpdatedAt = $now;
        $this->updatedAt = $now;
    }

    /**
     * @internal Pour usage Infrastructure uniquement
     */
    public static function reconstitute(
        GradeId $id,
        TenantId $tenantId,
        EvaluationId $evaluationId,
        UserId $studentId,
        ?GradeValue $value,
        GradeStatus $status,
        UserId $createdBy,
        DateTimeImmutable $createdAt,
        DateTimeImmutable $updatedAt,
        ?string $appreciation = null,
        ?DateTimeImmutable $appreciationUpdatedAt = null,
    ): self {
        $grade = new self(
            id: $id,
            tenantId: $tenantId,
            evaluationId: $evaluationId,
            studentId: $studentId,
            value: $value,
            status: $status,
            createdBy: $createdBy,
            createdAt: $createdAt,
        );

        $grade->updatedAt = $updatedAt;
        $grade->appreciation = $appreciation;
        $grade->appreciationUpdatedAt = $appreciationUpdatedAt;

        return $grade;
    }

    private static function validerCoherence(
        ?GradeValue $value,
        GradeStatus $status,
        GradeScale $gradeScale,
    ): void {
        if ($status === GradeStatus::GRADED && $value === null) {
            throw NoteRequiseException::pourStatutNote();
        }

        if ($value !== null && $value->value > $gradeScale->maxValue) {
            throw ValeurNoteInvalideException::depasseBareme($value->value, $gradeScale->maxValue);
        }
    }
}
