<?php

declare(strict_types=1);

namespace App\Administration\Infrastructure\Service;

use App\Administration\Application\Port\OfficialCalendarProvider;
use App\Administration\Application\Port\PasswordHasher;
use App\Administration\Application\Service\Cache\PaginatedQueryCache;
use App\Administration\Domain\Model\AcademicYear\DefaultPeriods;
use App\Administration\Domain\Model\AcademicYear\PeriodType;
use App\Administration\Domain\Model\ClassAssignment\ClassAssignment;
use App\Administration\Domain\Model\GradingConfiguration\SchoolGradingConfiguration;
use App\Administration\Domain\Model\SchoolCalendar\SchoolCalendar;
use App\Administration\Domain\Model\SchoolCalendar\SchoolZone;
use App\Administration\Domain\Model\SchoolClass\AcademicYearId;
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\SchoolClass\SchoolLevel;
use App\Administration\Domain\Model\StudentGuardian\RelationshipType;
use App\Administration\Domain\Model\StudentGuardian\StudentGuardian;
use App\Administration\Domain\Model\Subject\Subject;
use App\Administration\Domain\Model\Subject\SubjectCode;
use App\Administration\Domain\Model\Subject\SubjectColor;
use App\Administration\Domain\Model\Subject\SubjectName;
use App\Administration\Domain\Model\TeacherAssignment\TeacherAssignment;
use App\Administration\Domain\Model\User\Email;
use App\Administration\Domain\Model\User\Role;
use App\Administration\Domain\Model\User\StatutCompte;
use App\Administration\Domain\Model\User\User;
use App\Administration\Domain\Model\User\UserId;
use App\Administration\Domain\Repository\ClassAssignmentRepository;
use App\Administration\Domain\Repository\ClassRepository;
use App\Administration\Domain\Repository\GradingConfigurationRepository;
use App\Administration\Domain\Repository\PeriodConfigurationRepository;
use App\Administration\Domain\Repository\StudentGuardianRepository;
use App\Administration\Domain\Repository\SubjectRepository;
use App\Administration\Domain\Repository\TeacherAssignmentRepository;
use App\Administration\Domain\Repository\UserRepository;
use App\Administration\Infrastructure\School\SchoolIdResolver;
use App\Scolarite\Domain\Model\Schedule\DayOfWeek;
use App\Scolarite\Domain\Model\Schedule\ScheduleSlot;
use App\Scolarite\Domain\Model\Schedule\TimeSlot;
use App\Scolarite\Domain\Repository\ScheduleSlotRepository;
use App\Shared\Domain\Clock;
use App\Shared\Domain\Tenant\TenantId;
use App\Shared\Infrastructure\Tenant\TenantConfig;
use App\Shared\Infrastructure\Tenant\TenantContext;
use DateTimeImmutable;
use RuntimeException;

use function sprintf;
use function str_replace;
use function strtolower;
use function trim;
use function ucwords;

final readonly class DemoDataGenerator
{
    public function __construct(
        private UserRepository $userRepository,
        private ClassRepository $classRepository,
        private SubjectRepository $subjectRepository,
        private ClassAssignmentRepository $classAssignmentRepository,
        private TeacherAssignmentRepository $teacherAssignmentRepository,
        private StudentGuardianRepository $studentGuardianRepository,
        private ScheduleSlotRepository $scheduleSlotRepository,
        private PeriodConfigurationRepository $periodConfigurationRepository,
        private \App\Administration\Domain\Model\SchoolCalendar\SchoolCalendarRepository $schoolCalendarRepository,
        private GradingConfigurationRepository $gradingConfigurationRepository,
        private PasswordHasher $passwordHasher,
        private PaginatedQueryCache $paginatedQueryCache,
        private Clock $clock,
        private TenantContext $tenantContext,
        private CurrentAcademicYearResolver $currentAcademicYearResolver,
        private SchoolIdResolver $schoolIdResolver,
        private OfficialCalendarProvider $officialCalendarProvider,
    ) {
    }

    public function generate(
        TenantConfig $tenantConfig,
        string $password,
        string $schoolName,
        SchoolZone $zone,
        PeriodType $periodType,
    ): DemoDataGenerationResult {
        $this->tenantContext->setCurrentTenant($tenantConfig);

        try {
            $tenantId = $tenantConfig->tenantId;
            $academicYearId = $this->resolveAcademicYearId();
            $academicYearStartYear = $this->resolveAcademicYearStartYear();
            $academicYearLabel = sprintf('%d-%d', $academicYearStartYear, $academicYearStartYear + 1);
            $schoolId = SchoolId::fromString($this->schoolIdResolver->resolveForTenant((string) $tenantId));
            $now = $this->clock->now();
            $hashedPassword = $this->passwordHasher->hash($password);

            $result = new DemoDataGenerationResult(
                tenantSubdomain: $tenantConfig->subdomain,
                schoolName: $schoolName,
                academicYearLabel: $academicYearLabel,
                sharedPassword: $password,
            );

            $this->ensurePeriodConfiguration($tenantId, $academicYearId, $academicYearStartYear, $periodType, $result);
            $this->ensureSchoolCalendar($tenantId, $academicYearId, $academicYearLabel, $zone, $now, $result);
            $this->ensureGradingConfiguration($tenantId, $schoolId, $academicYearId, $now, $result);

            $users = $this->seedUsers($tenantConfig, $schoolName, $hashedPassword, $now, $result);
            $subjects = $this->seedSubjects($tenantId, $schoolId, $now, $result);
            $classes = $this->seedClasses($tenantId, $schoolId, $academicYearId, $now, $result);

            $this->seedClassAssignments($tenantId, $academicYearId, $users, $classes, $now, $result);
            $this->seedTeacherAssignments($tenantId, $academicYearId, $users, $classes, $subjects, $now, $result);
            $this->seedStudentGuardianLinks($tenantId, $users, $now, $result);
            $this->seedScheduleSlots($tenantId, $classes, $subjects, $users, $academicYearStartYear, $now, $result);
            $this->invalidateReadModelCaches((string) $tenantId);

            return $result;
        } finally {
            $this->tenantContext->clear();
        }
    }

    private function ensurePeriodConfiguration(
        TenantId $tenantId,
        AcademicYearId $academicYearId,
        int $academicYearStartYear,
        PeriodType $periodType,
        DemoDataGenerationResult $result,
    ): void {
        if ($this->periodConfigurationRepository->findByAcademicYear($tenantId, $academicYearId) !== null) {
            return;
        }

        $configuration = DefaultPeriods::forType($periodType, $academicYearStartYear);
        $this->periodConfigurationRepository->save($tenantId, $academicYearId, $configuration);
        $result->periodConfigurationCreated = true;
    }

    private function ensureSchoolCalendar(
        TenantId $tenantId,
        AcademicYearId $academicYearId,
        string $academicYearLabel,
        SchoolZone $zone,
        DateTimeImmutable $now,
        DemoDataGenerationResult $result,
    ): void {
        if ($this->schoolCalendarRepository->findByTenantAndYear($tenantId, $academicYearId) !== null) {
            return;
        }

        $calendar = SchoolCalendar::initialiser($tenantId, $academicYearId);
        $calendar->configurer(
            $zone,
            $this->officialCalendarProvider->toutesEntreesOfficielles($zone, $academicYearLabel),
            $now,
        );

        $this->schoolCalendarRepository->save($calendar);
        $result->schoolCalendarCreated = true;
    }

    private function ensureGradingConfiguration(
        TenantId $tenantId,
        SchoolId $schoolId,
        AcademicYearId $academicYearId,
        DateTimeImmutable $now,
        DemoDataGenerationResult $result,
    ): void {
        if ($this->gradingConfigurationRepository->findBySchoolAndYear($tenantId, $schoolId, $academicYearId) !== null) {
            return;
        }

        $configuration = SchoolGradingConfiguration::configurer(
            tenantId: $tenantId,
            schoolId: $schoolId,
            academicYearId: $academicYearId,
            mode: SchoolGradingConfiguration::DEFAULT_MODE,
            hasExistingGrades: false,
            configuredAt: $now,
        );

        $this->gradingConfigurationRepository->save($configuration);
        $result->gradingConfigurationCreated = true;
    }

    /**
     * @return array<string, User>
     */
    private function seedUsers(
        TenantConfig $tenantConfig,
        string $schoolName,
        string $hashedPassword,
        DateTimeImmutable $now,
        DemoDataGenerationResult $result,
    ): array {
        $users = [];

        foreach ($this->staffBlueprints() as $blueprint) {
            $users[$blueprint['key']] = $this->ensureActiveUser(
                tenantConfig: $tenantConfig,
                schoolName: $schoolName,
                hashedPassword: $hashedPassword,
                role: $blueprint['role'],
                emailSlug: $blueprint['emailSlug'],
                firstName: $blueprint['firstName'],
                lastName: $blueprint['lastName'],
                now: $now,
                result: $result,
            );
        }

        foreach ($this->teacherBlueprints() as $blueprint) {
            $users[$blueprint['key']] = $this->ensureActiveUser(
                tenantConfig: $tenantConfig,
                schoolName: $schoolName,
                hashedPassword: $hashedPassword,
                role: Role::PROF,
                emailSlug: $blueprint['emailSlug'],
                firstName: $blueprint['firstName'],
                lastName: $blueprint['lastName'],
                now: $now,
                result: $result,
            );
        }

        foreach ($this->parentBlueprints() as $blueprint) {
            $users[$blueprint['key']] = $this->ensureActiveUser(
                tenantConfig: $tenantConfig,
                schoolName: $schoolName,
                hashedPassword: $hashedPassword,
                role: Role::PARENT,
                emailSlug: $blueprint['emailSlug'],
                firstName: $blueprint['firstName'],
                lastName: $blueprint['lastName'],
                now: $now,
                result: $result,
            );
        }

        foreach ($this->studentBlueprints() as $blueprint) {
            $users[$blueprint['key']] = $this->ensureActiveUser(
                tenantConfig: $tenantConfig,
                schoolName: $schoolName,
                hashedPassword: $hashedPassword,
                role: Role::ELEVE,
                emailSlug: $blueprint['emailSlug'],
                firstName: $blueprint['firstName'],
                lastName: $blueprint['lastName'],
                now: $now,
                result: $result,
                dateOfBirth: $blueprint['dateOfBirth'],
                studentNumber: $blueprint['studentNumber'],
            );
        }

        return $users;
    }

    /**
     * @return array<string, Subject>
     */
    private function seedSubjects(
        TenantId $tenantId,
        SchoolId $schoolId,
        DateTimeImmutable $now,
        DemoDataGenerationResult $result,
    ): array {
        $subjects = [];

        foreach ($this->subjectBlueprints() as $blueprint) {
            $code = new SubjectCode($blueprint['code']);
            $subject = $this->subjectRepository->findByCode($code, $tenantId, $schoolId);

            if ($subject === null) {
                $subject = Subject::creer(
                    tenantId: $tenantId,
                    schoolId: $schoolId,
                    name: new SubjectName($blueprint['name']),
                    code: $code,
                    color: new SubjectColor($blueprint['color']),
                    createdAt: $now,
                );
                $subject->decrire($blueprint['description'], $now);
                $this->subjectRepository->save($subject);
                ++$result->createdSubjects;
            }

            $subjects[$blueprint['code']] = $subject;
        }

        return $subjects;
    }

    /**
     * @return array<string, SchoolClass>
     */
    private function seedClasses(
        TenantId $tenantId,
        SchoolId $schoolId,
        AcademicYearId $academicYearId,
        DateTimeImmutable $now,
        DemoDataGenerationResult $result,
    ): array {
        $classes = [];

        foreach ($this->classBlueprints() as $blueprint) {
            $name = new ClassName($blueprint['name']);
            $class = $this->classRepository->findByName($name, $tenantId, $academicYearId);

            if ($class === null) {
                $class = SchoolClass::creer(
                    tenantId: $tenantId,
                    schoolId: $schoolId,
                    academicYearId: $academicYearId,
                    name: $name,
                    level: $blueprint['level'],
                    capacity: $blueprint['capacity'],
                    createdAt: $now,
                );
                $class->decrire($blueprint['description'], $now);
                $this->classRepository->save($class);
                ++$result->createdClasses;
            }

            $classes[$blueprint['name']] = $class;
        }

        return $classes;
    }

    /**
     * @param array<string, User>        $users
     * @param array<string, SchoolClass> $classes
     */
    private function seedClassAssignments(
        TenantId $tenantId,
        AcademicYearId $academicYearId,
        array $users,
        array $classes,
        DateTimeImmutable $now,
        DemoDataGenerationResult $result,
    ): void {
        foreach ($this->studentBlueprints() as $blueprint) {
            $student = $users[$blueprint['key']];
            $class = $classes[$blueprint['className']];
            $assignment = $this->classAssignmentRepository->findByStudent($student->id, $academicYearId, $tenantId);

            if ($assignment === null) {
                $assignment = ClassAssignment::affecter(
                    tenantId: $tenantId,
                    studentId: $student->id,
                    classId: $class->id,
                    academicYearId: $academicYearId,
                    assignedAt: $now,
                );
                $this->classAssignmentRepository->save($assignment);
                ++$result->createdClassAssignments;

                continue;
            }

            if (!$assignment->classId->equals($class->id)) {
                $assignment->changerClasse($class->id, $now);
                $this->classAssignmentRepository->save($assignment);
            }
        }
    }

    /**
     * @param array<string, User>        $users
     * @param array<string, SchoolClass> $classes
     * @param array<string, Subject>     $subjects
     */
    private function seedTeacherAssignments(
        TenantId $tenantId,
        AcademicYearId $academicYearId,
        array $users,
        array $classes,
        array $subjects,
        DateTimeImmutable $now,
        DemoDataGenerationResult $result,
    ): void {
        $teacherKeysBySubject = $this->teacherKeysBySubject();

        foreach ($classes as $className => $class) {
            foreach ($subjects as $subjectCode => $subject) {
                $teacher = $users[$teacherKeysBySubject[$subjectCode]];
                $assignment = $this->teacherAssignmentRepository->findByTeacherClassSubject(
                    teacherId: $teacher->id,
                    classId: $class->id,
                    subjectId: $subject->id,
                    academicYearId: $academicYearId,
                    tenantId: $tenantId,
                );

                if ($assignment !== null) {
                    continue;
                }

                $removedAssignment = $this->teacherAssignmentRepository->findRemovedByTeacherClassSubject(
                    teacherId: $teacher->id,
                    classId: $class->id,
                    subjectId: $subject->id,
                    academicYearId: $academicYearId,
                    tenantId: $tenantId,
                );

                if ($removedAssignment !== null) {
                    $removedAssignment->reactiver($now);
                    $this->teacherAssignmentRepository->save($removedAssignment);

                    continue;
                }

                $createdAssignment = TeacherAssignment::creer(
                    tenantId: $tenantId,
                    teacherId: $teacher->id,
                    classId: $class->id,
                    subjectId: $subject->id,
                    academicYearId: $academicYearId,
                    createdAt: $now,
                );
                $this->teacherAssignmentRepository->save($createdAssignment);
                ++$result->createdTeacherAssignments;
            }
        }
    }

    /**
     * @param array<string, User> $users
     */
    private function seedStudentGuardianLinks(
        TenantId $tenantId,
        array $users,
        DateTimeImmutable $now,
        DemoDataGenerationResult $result,
    ): void {
        $createdBy = $users['admin']->id;

        foreach ($this->guardianLinkBlueprints() as $blueprint) {
            $student = $users[$blueprint['studentKey']];
            $guardian = $users[$blueprint['guardianKey']];
            $existingLink = $this->studentGuardianRepository->findByStudentAndGuardian(
                studentId: $student->id,
                guardianId: $guardian->id,
                tenantId: $tenantId,
            );

            if ($existingLink !== null) {
                continue;
            }

            $link = StudentGuardian::lier(
                studentId: $student->id,
                guardianId: $guardian->id,
                relationshipType: $blueprint['relationshipType'],
                tenantId: $tenantId,
                createdAt: $now,
                createdBy: $createdBy,
            );
            $this->studentGuardianRepository->save($link);
            ++$result->createdGuardianLinks;
        }
    }

    /**
     * @param array<string, SchoolClass> $classes
     * @param array<string, Subject>     $subjects
     * @param array<string, User>        $users
     */
    private function seedScheduleSlots(
        TenantId $tenantId,
        array $classes,
        array $subjects,
        array $users,
        int $academicYearStartYear,
        DateTimeImmutable $now,
        DemoDataGenerationResult $result,
    ): void {
        $teacherKeysBySubject = $this->teacherKeysBySubject();
        $recurrenceStart = new DateTimeImmutable(sprintf('%d-09-01', $academicYearStartYear));
        $recurrenceEnd = new DateTimeImmutable(sprintf('%d-06-30', $academicYearStartYear + 1));

        foreach ($this->scheduleBlueprints() as $blueprint) {
            foreach ($blueprint['assignments'] as $assignmentBlueprint) {
                $class = $classes[$assignmentBlueprint['className']];
                $subject = $subjects[$assignmentBlueprint['subjectCode']];
                $teacher = $users[$teacherKeysBySubject[$assignmentBlueprint['subjectCode']]];

                if ($this->scheduleSlotExists(
                    tenantId: $tenantId,
                    classId: $class->id,
                    subjectId: $subject->id,
                    teacherId: $teacher->id,
                    dayOfWeek: $blueprint['dayOfWeek'],
                    startTime: $blueprint['startTime'],
                    endTime: $blueprint['endTime'],
                    room: $assignmentBlueprint['room'],
                    recurrenceStart: $recurrenceStart,
                    recurrenceEnd: $recurrenceEnd,
                )) {
                    continue;
                }

                $slot = ScheduleSlot::creer(
                    tenantId: $tenantId,
                    classId: $class->id,
                    subjectId: $subject->id,
                    teacherId: $teacher->id,
                    dayOfWeek: $blueprint['dayOfWeek'],
                    timeSlot: new TimeSlot($blueprint['startTime'], $blueprint['endTime']),
                    room: $assignmentBlueprint['room'],
                    isRecurring: true,
                    now: $now,
                    recurrenceStart: $recurrenceStart,
                    recurrenceEnd: $recurrenceEnd,
                );
                $this->scheduleSlotRepository->save($slot);
                ++$result->createdScheduleSlots;
            }
        }
    }

    private function scheduleSlotExists(
        TenantId $tenantId,
        \App\Administration\Domain\Model\SchoolClass\ClassId $classId,
        \App\Administration\Domain\Model\Subject\SubjectId $subjectId,
        UserId $teacherId,
        DayOfWeek $dayOfWeek,
        string $startTime,
        string $endTime,
        ?string $room,
        DateTimeImmutable $recurrenceStart,
        DateTimeImmutable $recurrenceEnd,
    ): bool {
        foreach ($this->scheduleSlotRepository->findRecurringByClass($classId, $tenantId) as $existingSlot) {
            if (!$existingSlot->subjectId->equals($subjectId)) {
                continue;
            }

            if (!$existingSlot->teacherId->equals($teacherId)) {
                continue;
            }

            if ($existingSlot->dayOfWeek !== $dayOfWeek) {
                continue;
            }

            if ($existingSlot->timeSlot->startTime !== $startTime || $existingSlot->timeSlot->endTime !== $endTime) {
                continue;
            }

            if ($existingSlot->room !== $room) {
                continue;
            }

            if ($existingSlot->recurrenceStart?->format('Y-m-d') !== $recurrenceStart->format('Y-m-d')) {
                continue;
            }

            if ($existingSlot->recurrenceEnd?->format('Y-m-d') !== $recurrenceEnd->format('Y-m-d')) {
                continue;
            }

            return true;
        }

        return false;
    }

    private function ensureActiveUser(
        TenantConfig $tenantConfig,
        string $schoolName,
        string $hashedPassword,
        Role $role,
        string $emailSlug,
        string $firstName,
        string $lastName,
        DateTimeImmutable $now,
        DemoDataGenerationResult $result,
        ?DateTimeImmutable $dateOfBirth = null,
        ?string $studentNumber = null,
    ): User {
        $email = new Email($this->emailFor($emailSlug, $tenantConfig->subdomain));
        $user = $this->userRepository->findByEmail($email, $tenantConfig->tenantId);

        if ($user === null) {
            $user = User::reconstitute(
                id: UserId::generate(),
                email: $email,
                roles: [$role],
                tenantId: $tenantConfig->tenantId,
                schoolName: $schoolName,
                statut: StatutCompte::ACTIF,
                dateNaissance: $dateOfBirth,
                createdAt: $now,
                hashedPassword: $hashedPassword,
                activatedAt: $now,
                consentementParental: null,
                firstName: $firstName,
                lastName: $lastName,
                studentNumber: $studentNumber,
            );
            $this->userRepository->save($user);
            ++$result->createdUsers;
        } else {
            $userWasChanged = false;

            if ($user->firstName !== $firstName || $user->lastName !== $lastName) {
                $user->mettreAJourInfos($firstName, $lastName);
                $userWasChanged = true;
            }

            if (!$user->aLeRole($role)) {
                $user->attribuerRole($role, $now);
                $userWasChanged = true;
            }

            if ($user->statut === StatutCompte::SUSPENDU) {
                $user->debloquer($now);
                $userWasChanged = true;
            }

            if ($user->statut === StatutCompte::ACTIF) {
                $user->changerMotDePasse($hashedPassword, $now);
                $userWasChanged = true;
            } else {
                $result->addWarning(sprintf(
                    'Le compte "%s" existe déjà avec le statut "%s". Le mot de passe commun n\'a pas pu être réinitialisé.',
                    (string) $email,
                    $user->statut->value,
                ));
            }

            if ($userWasChanged) {
                $this->userRepository->save($user);
            }
        }

        $result->addAccount(
            role: $role->label(),
            name: trim($firstName . ' ' . $lastName),
            email: (string) $email,
        );

        return $user;
    }

    private function resolveAcademicYearId(): AcademicYearId
    {
        $resolved = $this->currentAcademicYearResolver->resolve('current');
        if ($resolved === null) {
            throw new RuntimeException('Impossible de résoudre l\'année scolaire courante.');
        }

        return AcademicYearId::fromString($resolved);
    }

    private function resolveAcademicYearStartYear(): int
    {
        $startYear = $this->currentAcademicYearResolver->resolveStartYear('current');
        if ($startYear === null) {
            throw new RuntimeException('Impossible de résoudre le millésime de l\'année scolaire courante.');
        }

        return $startYear;
    }

    private function invalidateReadModelCaches(string $tenantId): void
    {
        foreach ([
            'users',
            'classes',
            'subjects',
            'assignments',
            'parent_invitations',
            'students_image_rights',
        ] as $entityType) {
            $this->paginatedQueryCache->invalidate($entityType, $tenantId);
        }
    }

    private function emailFor(string $emailSlug, string $subdomain): string
    {
        return sprintf('%s.%s@classeo.test', strtolower($emailSlug), strtolower($subdomain));
    }

    /**
     * @return list<array{key: string, role: Role, emailSlug: string, firstName: string, lastName: string}>
     */
    private function staffBlueprints(): array
    {
        return [
            [
                'key' => 'admin',
                'role' => Role::ADMIN,
                'emailSlug' => 'admin.direction',
                'firstName' => 'Camille',
                'lastName' => 'Martin',
            ],
            [
                'key' => 'vie-scolaire',
                'role' => Role::VIE_SCOLAIRE,
                'emailSlug' => 'vie.scolaire',
                'firstName' => 'Nora',
                'lastName' => 'Petit',
            ],
            [
                'key' => 'secretariat',
                'role' => Role::SECRETARIAT,
                'emailSlug' => 'secretariat',
                'firstName' => 'Lucie',
                'lastName' => 'Bernard',
            ],
        ];
    }

    /**
     * @return list<array{key: string, subjectCode: string, emailSlug: string, firstName: string, lastName: string}>
     */
    private function teacherBlueprints(): array
    {
        return [
            [
                'key' => 'teacher-math',
                'subjectCode' => 'MATH',
                'emailSlug' => 'prof.amina.benali',
                'firstName' => 'Amina',
                'lastName' => 'Benali',
            ],
            [
                'key' => 'teacher-fr',
                'subjectCode' => 'FR',
                'emailSlug' => 'prof.julie.caron',
                'firstName' => 'Julie',
                'lastName' => 'Caron',
            ],
            [
                'key' => 'teacher-hg',
                'subjectCode' => 'HG',
                'emailSlug' => 'prof.marc.garcia',
                'firstName' => 'Marc',
                'lastName' => 'Garcia',
            ],
            [
                'key' => 'teacher-svt',
                'subjectCode' => 'SVT',
                'emailSlug' => 'prof.sophie.lambert',
                'firstName' => 'Sophie',
                'lastName' => 'Lambert',
            ],
            [
                'key' => 'teacher-ang',
                'subjectCode' => 'ANG',
                'emailSlug' => 'prof.david.nguyen',
                'firstName' => 'David',
                'lastName' => 'Nguyen',
            ],
            [
                'key' => 'teacher-eps',
                'subjectCode' => 'EPS',
                'emailSlug' => 'prof.clara.petit',
                'firstName' => 'Clara',
                'lastName' => 'Petit',
            ],
        ];
    }

    /**
     * @return list<array{key: string, emailSlug: string, firstName: string, lastName: string}>
     */
    private function parentBlueprints(): array
    {
        return [
            ['key' => 'parent-nadia-martin', 'emailSlug' => 'parent.nadia.martin', 'firstName' => 'Nadia', 'lastName' => 'Martin'],
            ['key' => 'parent-karim-martin', 'emailSlug' => 'parent.karim.martin', 'firstName' => 'Karim', 'lastName' => 'Martin'],
            ['key' => 'parent-ines-lopez', 'emailSlug' => 'parent.ines.lopez', 'firstName' => 'Ines', 'lastName' => 'Lopez'],
            ['key' => 'parent-raul-lopez', 'emailSlug' => 'parent.raul.lopez', 'firstName' => 'Raul', 'lastName' => 'Lopez'],
            ['key' => 'parent-claire-bernard', 'emailSlug' => 'parent.claire.bernard', 'firstName' => 'Claire', 'lastName' => 'Bernard'],
            ['key' => 'parent-mehdi-petit', 'emailSlug' => 'parent.mehdi.petit', 'firstName' => 'Mehdi', 'lastName' => 'Petit'],
            ['key' => 'parent-laura-moreau', 'emailSlug' => 'parent.laura.moreau', 'firstName' => 'Laura', 'lastName' => 'Moreau'],
            ['key' => 'parent-olivier-moreau', 'emailSlug' => 'parent.olivier.moreau', 'firstName' => 'Olivier', 'lastName' => 'Moreau'],
            ['key' => 'parent-celine-dupont', 'emailSlug' => 'parent.celine.dupont', 'firstName' => 'Celine', 'lastName' => 'Dupont'],
        ];
    }

    /**
     * @return list<array{
     *     key: string,
     *     emailSlug: string,
     *     firstName: string,
     *     lastName: string,
     *     className: string,
     *     dateOfBirth: DateTimeImmutable,
     *     studentNumber: string
     * }>
     */
    private function studentBlueprints(): array
    {
        return [
            ['key' => 'student-lina-martin', 'emailSlug' => 'eleve.lina.martin', 'firstName' => 'Lina', 'lastName' => 'Martin', 'className' => '6A', 'dateOfBirth' => new DateTimeImmutable('2013-02-14'), 'studentNumber' => 'DEMO-001'],
            ['key' => 'student-chloe-lopez', 'emailSlug' => 'eleve.chloe.lopez', 'firstName' => 'Chloe', 'lastName' => 'Lopez', 'className' => '6A', 'dateOfBirth' => new DateTimeImmutable('2013-05-02'), 'studentNumber' => 'DEMO-002'],
            ['key' => 'student-hugo-lopez', 'emailSlug' => 'eleve.hugo.lopez', 'firstName' => 'Hugo', 'lastName' => 'Lopez', 'className' => '6A', 'dateOfBirth' => new DateTimeImmutable('2013-07-22'), 'studentNumber' => 'DEMO-003'],
            ['key' => 'student-zoe-moreau', 'emailSlug' => 'eleve.zoe.moreau', 'firstName' => 'Zoe', 'lastName' => 'Moreau', 'className' => '6A', 'dateOfBirth' => new DateTimeImmutable('2013-11-09'), 'studentNumber' => 'DEMO-004'],
            ['key' => 'student-jade-bernard', 'emailSlug' => 'eleve.jade.bernard', 'firstName' => 'Jade', 'lastName' => 'Bernard', 'className' => '5A', 'dateOfBirth' => new DateTimeImmutable('2012-01-18'), 'studentNumber' => 'DEMO-005'],
            ['key' => 'student-ethan-bernard', 'emailSlug' => 'eleve.ethan.bernard', 'firstName' => 'Ethan', 'lastName' => 'Bernard', 'className' => '5A', 'dateOfBirth' => new DateTimeImmutable('2012-03-27'), 'studentNumber' => 'DEMO-006'],
            ['key' => 'student-nino-moreau', 'emailSlug' => 'eleve.nino.moreau', 'firstName' => 'Nino', 'lastName' => 'Moreau', 'className' => '5A', 'dateOfBirth' => new DateTimeImmutable('2012-09-04'), 'studentNumber' => 'DEMO-007'],
            ['key' => 'student-sarah-dupont', 'emailSlug' => 'eleve.sarah.dupont', 'firstName' => 'Sarah', 'lastName' => 'Dupont', 'className' => '5A', 'dateOfBirth' => new DateTimeImmutable('2012-12-30'), 'studentNumber' => 'DEMO-008'],
            ['key' => 'student-noah-martin', 'emailSlug' => 'eleve.noah.martin', 'firstName' => 'Noah', 'lastName' => 'Martin', 'className' => '4A', 'dateOfBirth' => new DateTimeImmutable('2011-02-11'), 'studentNumber' => 'DEMO-009'],
            ['key' => 'student-adam-petit', 'emailSlug' => 'eleve.adam.petit', 'firstName' => 'Adam', 'lastName' => 'Petit', 'className' => '4A', 'dateOfBirth' => new DateTimeImmutable('2011-06-19'), 'studentNumber' => 'DEMO-010'],
            ['key' => 'student-yasmine-petit', 'emailSlug' => 'eleve.yasmine.petit', 'firstName' => 'Yasmine', 'lastName' => 'Petit', 'className' => '4A', 'dateOfBirth' => new DateTimeImmutable('2011-08-25'), 'studentNumber' => 'DEMO-011'],
            ['key' => 'student-mael-dupont', 'emailSlug' => 'eleve.mael.dupont', 'firstName' => 'Mael', 'lastName' => 'Dupont', 'className' => '4A', 'dateOfBirth' => new DateTimeImmutable('2011-10-03'), 'studentNumber' => 'DEMO-012'],
        ];
    }

    /**
     * @return list<array{
     *     code: non-empty-string,
     *     name: non-empty-string,
     *     color: non-empty-string,
     *     description: non-empty-string
     * }>
     */
    private function subjectBlueprints(): array
    {
        return [
            ['code' => 'MATH', 'name' => 'Mathématiques', 'color' => '#2563EB', 'description' => 'Cours de mathématiques pour la démonstration.'],
            ['code' => 'FR', 'name' => 'Français', 'color' => '#DC2626', 'description' => 'Lecture, grammaire et expression écrite.'],
            ['code' => 'HG', 'name' => 'Histoire-Géographie', 'color' => '#D97706', 'description' => 'Repères historiques et géographiques.'],
            ['code' => 'SVT', 'name' => 'SVT', 'color' => '#16A34A', 'description' => 'Sciences de la vie et de la Terre.'],
            ['code' => 'ANG', 'name' => 'Anglais', 'color' => '#7C3AED', 'description' => 'Cours d’anglais pour tous les niveaux.'],
            ['code' => 'EPS', 'name' => 'EPS', 'color' => '#0891B2', 'description' => 'Éducation physique et sportive.'],
        ];
    }

    /**
     * @return list<array{name: string, level: SchoolLevel, capacity: int, description: string}>
     */
    private function classBlueprints(): array
    {
        return [
            ['name' => '6A', 'level' => SchoolLevel::SIXIEME, 'capacity' => 28, 'description' => 'Classe de sixième utilisée pour les démonstrations.'],
            ['name' => '5A', 'level' => SchoolLevel::CINQUIEME, 'capacity' => 28, 'description' => 'Classe de cinquième utilisée pour les démonstrations.'],
            ['name' => '4A', 'level' => SchoolLevel::QUATRIEME, 'capacity' => 28, 'description' => 'Classe de quatrième utilisée pour les démonstrations.'],
        ];
    }

    /**
     * @return list<array{studentKey: string, guardianKey: string, relationshipType: RelationshipType}>
     */
    private function guardianLinkBlueprints(): array
    {
        return [
            ['studentKey' => 'student-lina-martin', 'guardianKey' => 'parent-nadia-martin', 'relationshipType' => RelationshipType::MOTHER],
            ['studentKey' => 'student-lina-martin', 'guardianKey' => 'parent-karim-martin', 'relationshipType' => RelationshipType::FATHER],
            ['studentKey' => 'student-noah-martin', 'guardianKey' => 'parent-nadia-martin', 'relationshipType' => RelationshipType::MOTHER],
            ['studentKey' => 'student-noah-martin', 'guardianKey' => 'parent-karim-martin', 'relationshipType' => RelationshipType::FATHER],
            ['studentKey' => 'student-chloe-lopez', 'guardianKey' => 'parent-ines-lopez', 'relationshipType' => RelationshipType::MOTHER],
            ['studentKey' => 'student-chloe-lopez', 'guardianKey' => 'parent-raul-lopez', 'relationshipType' => RelationshipType::FATHER],
            ['studentKey' => 'student-hugo-lopez', 'guardianKey' => 'parent-ines-lopez', 'relationshipType' => RelationshipType::MOTHER],
            ['studentKey' => 'student-hugo-lopez', 'guardianKey' => 'parent-raul-lopez', 'relationshipType' => RelationshipType::FATHER],
            ['studentKey' => 'student-jade-bernard', 'guardianKey' => 'parent-claire-bernard', 'relationshipType' => RelationshipType::MOTHER],
            ['studentKey' => 'student-ethan-bernard', 'guardianKey' => 'parent-claire-bernard', 'relationshipType' => RelationshipType::MOTHER],
            ['studentKey' => 'student-adam-petit', 'guardianKey' => 'parent-mehdi-petit', 'relationshipType' => RelationshipType::FATHER],
            ['studentKey' => 'student-yasmine-petit', 'guardianKey' => 'parent-mehdi-petit', 'relationshipType' => RelationshipType::FATHER],
            ['studentKey' => 'student-zoe-moreau', 'guardianKey' => 'parent-laura-moreau', 'relationshipType' => RelationshipType::MOTHER],
            ['studentKey' => 'student-zoe-moreau', 'guardianKey' => 'parent-olivier-moreau', 'relationshipType' => RelationshipType::FATHER],
            ['studentKey' => 'student-nino-moreau', 'guardianKey' => 'parent-laura-moreau', 'relationshipType' => RelationshipType::MOTHER],
            ['studentKey' => 'student-nino-moreau', 'guardianKey' => 'parent-olivier-moreau', 'relationshipType' => RelationshipType::FATHER],
            ['studentKey' => 'student-sarah-dupont', 'guardianKey' => 'parent-celine-dupont', 'relationshipType' => RelationshipType::TUTOR_F],
            ['studentKey' => 'student-mael-dupont', 'guardianKey' => 'parent-celine-dupont', 'relationshipType' => RelationshipType::TUTOR_F],
        ];
    }

    /**
     * @return list<array{
     *     dayOfWeek: DayOfWeek,
     *     startTime: string,
     *     endTime: string,
     *     assignments: list<array{className: string, subjectCode: string, room: string}>
     * }>
     */
    private function scheduleBlueprints(): array
    {
        return [
            [
                'dayOfWeek' => DayOfWeek::MONDAY,
                'startTime' => '08:00',
                'endTime' => '09:00',
                'assignments' => [
                    ['className' => '6A', 'subjectCode' => 'MATH', 'room' => 'B12'],
                    ['className' => '5A', 'subjectCode' => 'FR', 'room' => 'B14'],
                    ['className' => '4A', 'subjectCode' => 'HG', 'room' => 'B16'],
                ],
            ],
            [
                'dayOfWeek' => DayOfWeek::MONDAY,
                'startTime' => '09:00',
                'endTime' => '10:00',
                'assignments' => [
                    ['className' => '6A', 'subjectCode' => 'SVT', 'room' => 'LAB1'],
                    ['className' => '5A', 'subjectCode' => 'ANG', 'room' => 'B15'],
                    ['className' => '4A', 'subjectCode' => 'EPS', 'room' => 'GYM1'],
                ],
            ],
            [
                'dayOfWeek' => DayOfWeek::TUESDAY,
                'startTime' => '08:00',
                'endTime' => '09:00',
                'assignments' => [
                    ['className' => '6A', 'subjectCode' => 'FR', 'room' => 'B13'],
                    ['className' => '5A', 'subjectCode' => 'HG', 'room' => 'B16'],
                    ['className' => '4A', 'subjectCode' => 'MATH', 'room' => 'B12'],
                ],
            ],
            [
                'dayOfWeek' => DayOfWeek::TUESDAY,
                'startTime' => '09:00',
                'endTime' => '10:00',
                'assignments' => [
                    ['className' => '6A', 'subjectCode' => 'ANG', 'room' => 'B15'],
                    ['className' => '5A', 'subjectCode' => 'EPS', 'room' => 'GYM1'],
                    ['className' => '4A', 'subjectCode' => 'SVT', 'room' => 'LAB1'],
                ],
            ],
            [
                'dayOfWeek' => DayOfWeek::THURSDAY,
                'startTime' => '13:00',
                'endTime' => '14:00',
                'assignments' => [
                    ['className' => '6A', 'subjectCode' => 'HG', 'room' => 'B16'],
                    ['className' => '5A', 'subjectCode' => 'MATH', 'room' => 'B12'],
                    ['className' => '4A', 'subjectCode' => 'FR', 'room' => 'B14'],
                ],
            ],
            [
                'dayOfWeek' => DayOfWeek::FRIDAY,
                'startTime' => '14:00',
                'endTime' => '15:00',
                'assignments' => [
                    ['className' => '6A', 'subjectCode' => 'EPS', 'room' => 'GYM1'],
                    ['className' => '5A', 'subjectCode' => 'SVT', 'room' => 'LAB1'],
                    ['className' => '4A', 'subjectCode' => 'ANG', 'room' => 'B15'],
                ],
            ],
        ];
    }

    /**
     * @return array<string, string>
     */
    private function teacherKeysBySubject(): array
    {
        $mapping = [];

        foreach ($this->teacherBlueprints() as $blueprint) {
            $mapping[$blueprint['subjectCode']] = $blueprint['key'];
        }

        return $mapping;
    }

    public function defaultSchoolNameForSubdomain(string $subdomain): string
    {
        return sprintf(
            'Établissement de démo %s',
            ucwords(str_replace('-', ' ', trim($subdomain))),
        );
    }
}
