<?php

declare(strict_types=1);

namespace App\Administration\Application\Command\CreateStudent;

use App\Administration\Domain\Exception\ClasseNotFoundException;
use App\Administration\Domain\Exception\EmailDejaUtiliseeException;
use App\Administration\Domain\Model\ClassAssignment\ClassAssignment;
use App\Administration\Domain\Model\ConsentementParental\ConsentementParental;
use App\Administration\Domain\Model\SchoolClass\AcademicYearId;
use App\Administration\Domain\Model\SchoolClass\ClassId;
use App\Administration\Domain\Model\User\Email;
use App\Administration\Domain\Model\User\Role;
use App\Administration\Domain\Model\User\User;
use App\Administration\Domain\Policy\ConsentementParentalPolicy;
use App\Administration\Domain\Repository\ClassAssignmentRepository;
use App\Administration\Domain\Repository\ClassRepository;
use App\Administration\Domain\Repository\UserRepository;
use App\Shared\Domain\Clock;
use App\Shared\Domain\Tenant\TenantId;
use DateTimeImmutable;
use Doctrine\DBAL\Connection;
use Symfony\Component\Messenger\Attribute\AsMessageHandler;
use Throwable;

#[AsMessageHandler(bus: 'command.bus')]
final readonly class CreateStudentHandler
{
    public function __construct(
        private UserRepository $userRepository,
        private ClassAssignmentRepository $classAssignmentRepository,
        private ClassRepository $classRepository,
        private Connection $connection,
        private Clock $clock,
        private ConsentementParentalPolicy $consentementPolicy,
    ) {
    }

    public function __invoke(CreateStudentCommand $command): User
    {
        $tenantId = TenantId::fromString($command->tenantId);
        $classId = ClassId::fromString($command->classId);
        $academicYearId = AcademicYearId::fromString($command->academicYearId);

        // Valider l'existence de la classe, son tenant et son statut
        $class = $this->classRepository->get($classId);

        if (!$class->tenantId->equals($tenantId)) {
            throw ClasseNotFoundException::withId($classId);
        }

        if (!$class->status->peutRecevoirEleves()) {
            throw ClasseNotFoundException::withId($classId);
        }

        $now = $this->clock->now();

        // Vérifier l'unicité de l'email si fourni
        if ($command->email !== null) {
            $email = new Email($command->email);
            $existingUser = $this->userRepository->findByEmail($email, $tenantId);

            if ($existingUser !== null) {
                throw EmailDejaUtiliseeException::dansTenant($email, $tenantId);
            }
        }

        $this->connection->beginTransaction();

        try {
            // Créer l'utilisateur
            $user = $command->email !== null
                ? User::inviter(
                    email: new Email($command->email),
                    role: Role::ELEVE,
                    tenantId: $tenantId,
                    schoolName: $command->schoolName,
                    firstName: $command->firstName,
                    lastName: $command->lastName,
                    invitedAt: $now,
                    dateNaissance: $command->dateNaissance !== null
                        ? new DateTimeImmutable($command->dateNaissance)
                        : null,
                    studentNumber: $command->studentNumber,
                )
                : User::inscrire(
                    role: Role::ELEVE,
                    tenantId: $tenantId,
                    schoolName: $command->schoolName,
                    firstName: $command->firstName,
                    lastName: $command->lastName,
                    inscritAt: $now,
                    dateNaissance: $command->dateNaissance !== null
                        ? new DateTimeImmutable($command->dateNaissance)
                        : null,
                    studentNumber: $command->studentNumber,
                );

            // Enregistrer le consentement parental si fourni par l'admin
            if ($command->parentalConsent && $command->consentRecordedBy !== null) {
                $dateNaissance = $command->dateNaissance !== null
                    ? new DateTimeImmutable($command->dateNaissance)
                    : null;

                if ($this->consentementPolicy->estRequis($dateNaissance)) {
                    $user->enregistrerConsentementParental(
                        ConsentementParental::accorder(
                            parentId: $command->consentRecordedBy,
                            eleveId: (string) $user->id,
                            at: $now,
                            ipAddress: 'admin-creation',
                        ),
                    );
                }
            }

            $this->userRepository->save($user);

            // Affecter à la classe
            $assignment = ClassAssignment::affecter(
                tenantId: $tenantId,
                studentId: $user->id,
                classId: $classId,
                academicYearId: $academicYearId,
                assignedAt: $now,
            );

            $this->classAssignmentRepository->save($assignment);

            $this->connection->commit();
        } catch (Throwable $e) {
            $this->connection->rollBack();

            throw $e;
        }

        return $user;
    }
}
