<?php

declare(strict_types=1);

namespace App\Administration\Application\Command\LinkParentToStudent;

use App\Administration\Domain\Exception\InvalidGuardianRoleException;
use App\Administration\Domain\Exception\InvalidStudentRoleException;
use App\Administration\Domain\Exception\LiaisonDejaExistanteException;
use App\Administration\Domain\Exception\MaxGuardiansReachedException;
use App\Administration\Domain\Exception\TenantMismatchException;
use App\Administration\Domain\Model\StudentGuardian\RelationshipType;
use App\Administration\Domain\Model\StudentGuardian\StudentGuardian;
use App\Administration\Domain\Model\User\Role;
use App\Administration\Domain\Model\User\UserId;
use App\Administration\Domain\Repository\StudentGuardianRepository;
use App\Administration\Domain\Repository\UserRepository;
use App\Shared\Domain\Clock;
use App\Shared\Domain\Tenant\TenantId;
use InvalidArgumentException;
use Symfony\Component\Messenger\Attribute\AsMessageHandler;

/**
 * Handler pour lier un parent/tuteur à un élève.
 *
 * Vérifie :
 * - Que la liaison n'existe pas déjà
 * - Que le maximum de parents/tuteurs n'est pas atteint
 */
#[AsMessageHandler(bus: 'command.bus')]
final readonly class LinkParentToStudentHandler
{
    public function __construct(
        private StudentGuardianRepository $repository,
        private UserRepository $userRepository,
        private Clock $clock,
    ) {
    }

    public function __invoke(LinkParentToStudentCommand $command): StudentGuardian
    {
        $studentId = UserId::fromString($command->studentId);
        $guardianId = UserId::fromString($command->guardianId);
        $tenantId = TenantId::fromString($command->tenantId);
        $relationshipType = RelationshipType::tryFrom($command->relationshipType);
        if ($relationshipType === null) {
            throw new InvalidArgumentException("Type de relation invalide : \"{$command->relationshipType}\".");
        }
        $createdBy = $command->createdBy !== null
            ? UserId::fromString($command->createdBy)
            : null;

        $guardian = $this->userRepository->get($guardianId);
        if (!$guardian->tenantId->equals($tenantId)) {
            throw TenantMismatchException::pourUtilisateur($guardianId, $tenantId);
        }
        if (!$guardian->aLeRole(Role::PARENT)) {
            throw InvalidGuardianRoleException::pourUtilisateur($guardianId);
        }

        $student = $this->userRepository->get($studentId);
        if (!$student->tenantId->equals($tenantId)) {
            throw TenantMismatchException::pourUtilisateur($studentId, $tenantId);
        }
        if (!$student->aLeRole(Role::ELEVE)) {
            throw InvalidStudentRoleException::pourUtilisateur($studentId);
        }

        $existingLink = $this->repository->findByStudentAndGuardian($studentId, $guardianId, $tenantId);
        if ($existingLink !== null) {
            throw LiaisonDejaExistanteException::pourParentEtEleve($guardianId, $studentId);
        }

        // Note: this count-then-insert pattern is subject to a race condition under concurrent
        // requests. The DB unique constraint prevents duplicate (student, guardian, tenant) pairs,
        // but cannot enforce a max-count. In practice, simultaneous additions by different admins
        // for the same student are extremely unlikely in a school context.
        $count = $this->repository->countGuardiansForStudent($studentId, $tenantId);
        if ($count >= StudentGuardian::MAX_GUARDIANS_PER_STUDENT) {
            throw MaxGuardiansReachedException::pourEleve($studentId);
        }

        $link = StudentGuardian::lier(
            studentId: $studentId,
            guardianId: $guardianId,
            relationshipType: $relationshipType,
            tenantId: $tenantId,
            createdAt: $this->clock->now(),
            createdBy: $createdBy,
        );

        $this->repository->save($link);

        return $link;
    }
}
