<?php

declare(strict_types=1);

namespace App\Administration\Application\Command\ImportStudents;

use App\Administration\Application\Service\Import\DateParser;
use App\Administration\Application\Service\Import\ImportRowValidator;
use App\Administration\Domain\Model\ClassAssignment\ClassAssignment;
use App\Administration\Domain\Model\Import\ImportBatchId;
use App\Administration\Domain\Model\Import\StudentImportField;
use App\Administration\Domain\Model\SchoolClass\AcademicYearId;
use App\Administration\Domain\Model\SchoolClass\ClassId;
use App\Administration\Domain\Model\SchoolClass\ClassName;
use App\Administration\Domain\Model\SchoolClass\SchoolClass;
use App\Administration\Domain\Model\SchoolClass\SchoolId;
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\Repository\ClassAssignmentRepository;
use App\Administration\Domain\Repository\ClassRepository;
use App\Administration\Domain\Repository\ImportBatchRepository;
use App\Administration\Domain\Repository\UserRepository;
use App\Administration\Infrastructure\School\SchoolIdResolver;
use App\Shared\Domain\Clock;
use App\Shared\Domain\Tenant\TenantId;
use DateTimeImmutable;
use Doctrine\DBAL\Connection;
use DomainException;
use Psr\Log\LoggerInterface;

use function sprintf;

use Symfony\Component\Messenger\Attribute\AsMessageHandler;
use Throwable;

/**
 * Handler pour l'import d'élèves en batch.
 *
 * Traite les lignes valides du batch, crée les élèves et les affecte aux classes.
 *
 * @see AC5: Import validé → élèves créés en base
 * @see NFR-SC6: 500 élèves importés en < 2 minutes
 */
#[AsMessageHandler(bus: 'command.bus')]
final readonly class ImportStudentsHandler
{
    public function __construct(
        private ImportBatchRepository $importBatchRepository,
        private UserRepository $userRepository,
        private ClassRepository $classRepository,
        private ClassAssignmentRepository $classAssignmentRepository,
        private SchoolIdResolver $schoolIdResolver,
        private Connection $connection,
        private Clock $clock,
        private LoggerInterface $logger,
    ) {
    }

    public function __invoke(ImportStudentsCommand $command): void
    {
        $batchId = ImportBatchId::fromString($command->batchId);
        $tenantId = TenantId::fromString($command->tenantId);
        $academicYearId = AcademicYearId::fromString($command->academicYearId);
        $now = $this->clock->now();

        $batch = $this->importBatchRepository->get($batchId);

        $batch->demarrer($now);
        $this->importBatchRepository->save($batch);

        $lignes = $batch->lignes();
        $importedCount = 0;
        $errorCount = 0;
        $processedCount = 0;
        $createdClasses = [];

        /** @var array<string, ClassId> */
        $classCache = [];

        foreach ($lignes as $row) {
            try {
                $className = trim($row->valeurChamp(StudentImportField::CLASS_NAME) ?? '');

                if (!isset($classCache[$className])) {
                    $classCache[$className] = $this->resolveClassId(
                        $className,
                        $tenantId,
                        $academicYearId,
                        $command->schoolName,
                        $command->createMissingClasses,
                        $now,
                        $createdClasses,
                    );
                }

                $classId = $classCache[$className];

                $firstName = trim($row->valeurChamp(StudentImportField::FIRST_NAME) ?? '');
                $lastName = trim($row->valeurChamp(StudentImportField::LAST_NAME) ?? '');

                if ($firstName === '' && $lastName === '') {
                    $fullName = trim($row->valeurChamp(StudentImportField::FULL_NAME) ?? '');
                    if ($fullName !== '') {
                        [$lastName, $firstName] = ImportRowValidator::splitFullName($fullName);
                    }
                }
                $emailRaw = $row->valeurChamp(StudentImportField::EMAIL);
                $birthDate = $row->valeurChamp(StudentImportField::BIRTH_DATE);
                $studentNumber = $row->valeurChamp(StudentImportField::STUDENT_NUMBER);
                $trimmedStudentNumber = $studentNumber !== null && trim($studentNumber) !== '' ? trim($studentNumber) : null;

                $this->connection->beginTransaction();

                try {
                    if ($emailRaw !== null && trim($emailRaw) !== '') {
                        $emailVO = new Email(trim($emailRaw));

                        if ($this->userRepository->findByEmail($emailVO, $tenantId) !== null) {
                            throw new DomainException(sprintf('L\'email "%s" est déjà utilisé.', trim($emailRaw)));
                        }

                        $user = User::inviter(
                            email: $emailVO,
                            role: Role::ELEVE,
                            tenantId: $tenantId,
                            schoolName: $command->schoolName,
                            firstName: $firstName,
                            lastName: $lastName,
                            invitedAt: $now,
                            dateNaissance: DateParser::parse($birthDate),
                            studentNumber: $trimmedStudentNumber,
                        );
                    } else {
                        $user = User::inscrire(
                            role: Role::ELEVE,
                            tenantId: $tenantId,
                            schoolName: $command->schoolName,
                            firstName: $firstName,
                            lastName: $lastName,
                            inscritAt: $now,
                            dateNaissance: DateParser::parse($birthDate),
                            studentNumber: $trimmedStudentNumber,
                        );
                    }

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

                    $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;
                }

                ++$importedCount;
            } catch (DomainException $e) {
                $this->logger->warning('Import ligne {line} échouée : {message}', [
                    'line' => $row->lineNumber,
                    'message' => $e->getMessage(),
                    'batch_id' => $command->batchId,
                ]);
                ++$errorCount;
            }

            ++$processedCount;
            if ($processedCount % 50 === 0) {
                $batch->mettreAJourProgression($importedCount, $errorCount);
                $this->importBatchRepository->save($batch);
            }
        }

        $batch->terminer($importedCount, $errorCount, $this->clock->now());
        $this->importBatchRepository->save($batch);
    }

    /**
     * @param list<string> $createdClasses
     */
    private function resolveClassId(
        string $className,
        TenantId $tenantId,
        AcademicYearId $academicYearId,
        string $schoolName,
        bool $createMissingClasses,
        DateTimeImmutable $now,
        array &$createdClasses,
    ): ClassId {
        $classNameVO = new ClassName($className);
        $class = $this->classRepository->findByName($classNameVO, $tenantId, $academicYearId);

        if ($class !== null) {
            return $class->id;
        }

        if (!$createMissingClasses) {
            throw new DomainException(sprintf('La classe "%s" n\'existe pas.', $className));
        }

        $newClass = SchoolClass::creer(
            tenantId: $tenantId,
            schoolId: SchoolId::fromString($this->schoolIdResolver->resolveForTenant((string) $tenantId)),
            academicYearId: $academicYearId,
            name: $classNameVO,
            level: null,
            capacity: null,
            createdAt: $now,
        );

        $this->classRepository->save($newClass);
        $createdClasses[] = $className;

        return $newClass->id;
    }
}
