<?php

declare(strict_types=1);

namespace App\Administration\Domain\Model\Subject;

use App\Administration\Domain\Event\MatiereCreee;
use App\Administration\Domain\Event\MatiereModifiee;
use App\Administration\Domain\Event\MatiereSupprimee;
use App\Administration\Domain\Model\SchoolClass\SchoolId;
use App\Shared\Domain\AggregateRoot;
use App\Shared\Domain\Tenant\TenantId;
use DateTimeImmutable;

/**
 * Aggregate Root représentant une matière enseignée.
 *
 * Une matière appartient à un établissement (tenant) et une école.
 * Elle a un nom, un code court unique et une couleur optionnelle pour l'affichage.
 *
 * @see FR74: Structurer l'offre pédagogique
 */
final class Subject extends AggregateRoot
{
    public private(set) ?string $description = null;
    public private(set) DateTimeImmutable $updatedAt;
    public private(set) ?DateTimeImmutable $deletedAt = null;

    private function __construct(
        public private(set) SubjectId $id,
        public private(set) TenantId $tenantId,
        public private(set) SchoolId $schoolId,
        public private(set) SubjectName $name,
        public private(set) SubjectCode $code,
        public private(set) ?SubjectColor $color,
        public private(set) SubjectStatus $status,
        public private(set) DateTimeImmutable $createdAt,
    ) {
        $this->updatedAt = $createdAt;
    }

    /**
     * Crée une nouvelle matière.
     */
    public static function creer(
        TenantId $tenantId,
        SchoolId $schoolId,
        SubjectName $name,
        SubjectCode $code,
        ?SubjectColor $color,
        DateTimeImmutable $createdAt,
    ): self {
        $subject = new self(
            id: SubjectId::generate(),
            tenantId: $tenantId,
            schoolId: $schoolId,
            name: $name,
            code: $code,
            color: $color,
            status: SubjectStatus::ACTIVE,
            createdAt: $createdAt,
        );

        $subject->recordEvent(new MatiereCreee(
            subjectId: $subject->id,
            tenantId: $subject->tenantId,
            name: $subject->name,
            code: $subject->code,
            occurredOn: $createdAt,
        ));

        return $subject;
    }

    /**
     * Renomme la matière.
     */
    public function renommer(SubjectName $nouveauNom, DateTimeImmutable $at): void
    {
        if ($this->name->equals($nouveauNom)) {
            return;
        }

        $ancienNom = $this->name;
        $this->name = $nouveauNom;
        $this->updatedAt = $at;

        $this->recordEvent(new MatiereModifiee(
            subjectId: $this->id,
            tenantId: $this->tenantId,
            champ: 'nom',
            ancienneValeur: (string) $ancienNom,
            nouvelleValeur: (string) $nouveauNom,
            occurredOn: $at,
        ));
    }

    /**
     * Change le code de la matière.
     *
     * Note: L'unicité du code doit être vérifiée par l'Application Layer
     * avant d'appeler cette méthode.
     */
    public function changerCode(SubjectCode $nouveauCode, DateTimeImmutable $at): void
    {
        if ($this->code->equals($nouveauCode)) {
            return;
        }

        $ancienCode = $this->code;
        $this->code = $nouveauCode;
        $this->updatedAt = $at;

        $this->recordEvent(new MatiereModifiee(
            subjectId: $this->id,
            tenantId: $this->tenantId,
            champ: 'code',
            ancienneValeur: (string) $ancienCode,
            nouvelleValeur: (string) $nouveauCode,
            occurredOn: $at,
        ));
    }

    /**
     * Change la couleur de la matière.
     */
    public function changerCouleur(?SubjectColor $couleur, DateTimeImmutable $at): void
    {
        // Compare colors, considering null cases
        $sameColor = ($this->color === null && $couleur === null)
            || ($this->color !== null && $couleur !== null && $this->color->equals($couleur));

        if ($sameColor) {
            return;
        }

        $ancienneCouleur = $this->color;
        $this->color = $couleur;
        $this->updatedAt = $at;

        $this->recordEvent(new MatiereModifiee(
            subjectId: $this->id,
            tenantId: $this->tenantId,
            champ: 'couleur',
            ancienneValeur: $ancienneCouleur !== null ? (string) $ancienneCouleur : null,
            nouvelleValeur: $couleur !== null ? (string) $couleur : null,
            occurredOn: $at,
        ));
    }

    /**
     * Ajoute ou modifie la description de la matière.
     */
    public function decrire(?string $description, DateTimeImmutable $at): void
    {
        if ($this->description === $description) {
            return;
        }

        $ancienneDescription = $this->description;
        $this->description = $description;
        $this->updatedAt = $at;

        $this->recordEvent(new MatiereModifiee(
            subjectId: $this->id,
            tenantId: $this->tenantId,
            champ: 'description',
            ancienneValeur: $ancienneDescription,
            nouvelleValeur: $description,
            occurredOn: $at,
        ));
    }

    /**
     * Archive la matière (soft delete).
     *
     * Note: La vérification des notes associées doit être faite par l'Application Layer
     * via une Query avant d'appeler cette méthode.
     */
    public function archiver(DateTimeImmutable $at): void
    {
        if ($this->status === SubjectStatus::ARCHIVED) {
            return;
        }

        $this->status = SubjectStatus::ARCHIVED;
        $this->deletedAt = $at;
        $this->updatedAt = $at;

        $this->recordEvent(new MatiereSupprimee(
            subjectId: $this->id,
            tenantId: $this->tenantId,
            occurredOn: $at,
        ));
    }

    /**
     * Vérifie si la matière est active.
     */
    public function estActive(): bool
    {
        return $this->status === SubjectStatus::ACTIVE;
    }

    /**
     * Vérifie si la matière peut être utilisée.
     */
    public function peutEtreUtilisee(): bool
    {
        return $this->status->peutEtreUtilisee();
    }

    /**
     * Reconstitue un Subject depuis le stockage.
     *
     * @internal Pour usage Infrastructure uniquement
     */
    public static function reconstitute(
        SubjectId $id,
        TenantId $tenantId,
        SchoolId $schoolId,
        SubjectName $name,
        SubjectCode $code,
        ?SubjectColor $color,
        SubjectStatus $status,
        ?string $description,
        DateTimeImmutable $createdAt,
        DateTimeImmutable $updatedAt,
        ?DateTimeImmutable $deletedAt,
    ): self {
        $subject = new self(
            id: $id,
            tenantId: $tenantId,
            schoolId: $schoolId,
            name: $name,
            code: $code,
            color: $color,
            status: $status,
            createdAt: $createdAt,
        );

        $subject->description = $description;
        $subject->updatedAt = $updatedAt;
        $subject->deletedAt = $deletedAt;

        return $subject;
    }
}
