<?php

declare(strict_types=1);

namespace App\Tests\Unit\Administration\Application\Command\ImportStudents;

use App\Administration\Application\Command\ImportStudents\ImportStudentsCommand;
use App\Administration\Application\Command\ImportStudents\ImportStudentsHandler;
use App\Administration\Domain\Model\Import\ColumnMapping;
use App\Administration\Domain\Model\Import\ImportRow;
use App\Administration\Domain\Model\Import\ImportStatus;
use App\Administration\Domain\Model\Import\KnownImportFormat;
use App\Administration\Domain\Model\Import\StudentImportBatch;
use App\Administration\Domain\Model\Import\StudentImportField;
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\Infrastructure\Persistence\InMemory\InMemoryClassAssignmentRepository;
use App\Administration\Infrastructure\Persistence\InMemory\InMemoryClassRepository;
use App\Administration\Infrastructure\Persistence\InMemory\InMemoryImportBatchRepository;
use App\Administration\Infrastructure\Persistence\InMemory\InMemoryUserRepository;
use App\Administration\Infrastructure\School\SchoolIdResolver;
use App\Shared\Domain\Clock;
use App\Shared\Domain\Tenant\TenantId;

use function count;

use DateTimeImmutable;
use Doctrine\DBAL\Connection;
use PHPUnit\Framework\Attributes\Test;
use PHPUnit\Framework\TestCase;
use Psr\Log\NullLogger;

final class ImportStudentsHandlerTest extends TestCase
{
    private const string TENANT_ID = '550e8400-e29b-41d4-a716-446655440001';
    private const string ACADEMIC_YEAR_ID = '550e8400-e29b-41d4-a716-446655440003';

    private InMemoryImportBatchRepository $importBatchRepository;
    private InMemoryUserRepository $userRepository;
    private InMemoryClassRepository $classRepository;
    private InMemoryClassAssignmentRepository $classAssignmentRepository;
    private ImportStudentsHandler $handler;
    private TenantId $tenantId;
    private AcademicYearId $academicYearId;

    protected function setUp(): void
    {
        $clock = new class implements Clock {
            public function now(): DateTimeImmutable
            {
                return new DateTimeImmutable('2026-02-24 10:00:00');
            }
        };

        $this->tenantId = TenantId::fromString(self::TENANT_ID);
        $this->academicYearId = AcademicYearId::fromString(self::ACADEMIC_YEAR_ID);

        $this->importBatchRepository = new InMemoryImportBatchRepository();
        $this->userRepository = new InMemoryUserRepository();
        $this->classRepository = new InMemoryClassRepository();
        $this->classAssignmentRepository = new InMemoryClassAssignmentRepository();

        $connection = $this->createMock(Connection::class);

        $this->handler = new ImportStudentsHandler(
            $this->importBatchRepository,
            $this->userRepository,
            $this->classRepository,
            $this->classAssignmentRepository,
            new SchoolIdResolver(),
            $connection,
            $clock,
            new NullLogger(),
        );
    }

    #[Test]
    public function importsStudentsWithExistingClasses(): void
    {
        $class = $this->createClass('6ème A');
        $batch = $this->createBatchWithRows([
            $this->createMappedRow(1, 'Dupont', 'Jean', '6ème A'),
            $this->createMappedRow(2, 'Martin', 'Marie', '6ème A'),
        ]);

        ($this->handler)(new ImportStudentsCommand(
            batchId: (string) $batch->id,
            tenantId: self::TENANT_ID,
            schoolName: 'École Test',
            academicYearId: self::ACADEMIC_YEAR_ID,
        ));

        $updatedBatch = $this->importBatchRepository->get($batch->id);

        self::assertSame(ImportStatus::COMPLETED, $updatedBatch->status);
        self::assertSame(2, $updatedBatch->importedCount);
        self::assertSame(0, $updatedBatch->errorCount);
    }

    #[Test]
    public function importsStudentsWithEmail(): void
    {
        $this->createClass('6ème A');
        $batch = $this->createBatchWithRows([
            $this->createMappedRow(1, 'Dupont', 'Jean', '6ème A', 'jean@test.com'),
        ]);

        ($this->handler)(new ImportStudentsCommand(
            batchId: (string) $batch->id,
            tenantId: self::TENANT_ID,
            schoolName: 'École Test',
            academicYearId: self::ACADEMIC_YEAR_ID,
        ));

        $students = $this->userRepository->findStudentsByTenant($this->tenantId);

        self::assertCount(1, $students);
        self::assertNotNull($students[0]->email);
    }

    #[Test]
    public function importsStudentsWithoutEmail(): void
    {
        $this->createClass('6ème A');
        $batch = $this->createBatchWithRows([
            $this->createMappedRow(1, 'Dupont', 'Jean', '6ème A'),
        ]);

        ($this->handler)(new ImportStudentsCommand(
            batchId: (string) $batch->id,
            tenantId: self::TENANT_ID,
            schoolName: 'École Test',
            academicYearId: self::ACADEMIC_YEAR_ID,
        ));

        $students = $this->userRepository->findStudentsByTenant($this->tenantId);

        self::assertCount(1, $students);
        self::assertNull($students[0]->email);
    }

    #[Test]
    public function createsMissingClassesWhenEnabled(): void
    {
        $batch = $this->createBatchWithRows([
            $this->createMappedRow(1, 'Dupont', 'Jean', '6ème A'),
        ]);

        ($this->handler)(new ImportStudentsCommand(
            batchId: (string) $batch->id,
            tenantId: self::TENANT_ID,
            schoolName: 'École Test',
            academicYearId: self::ACADEMIC_YEAR_ID,
            createMissingClasses: true,
        ));

        $updatedBatch = $this->importBatchRepository->get($batch->id);

        self::assertSame(ImportStatus::COMPLETED, $updatedBatch->status);
        self::assertSame(1, $updatedBatch->importedCount);

        $createdClass = $this->classRepository->findByName(
            new ClassName('6ème A'),
            $this->tenantId,
            $this->academicYearId,
        );
        self::assertNotNull($createdClass);
    }

    #[Test]
    public function countsErrorsForMissingClassesWhenNotEnabled(): void
    {
        $batch = $this->createBatchWithRows([
            $this->createMappedRow(1, 'Dupont', 'Jean', '6ème A'),
            $this->createMappedRow(2, 'Martin', 'Marie', '5ème B'),
        ]);

        ($this->handler)(new ImportStudentsCommand(
            batchId: (string) $batch->id,
            tenantId: self::TENANT_ID,
            schoolName: 'École Test',
            academicYearId: self::ACADEMIC_YEAR_ID,
            createMissingClasses: false,
        ));

        $updatedBatch = $this->importBatchRepository->get($batch->id);

        self::assertSame(ImportStatus::COMPLETED, $updatedBatch->status);
        self::assertSame(0, $updatedBatch->importedCount);
        self::assertSame(2, $updatedBatch->errorCount);
    }

    #[Test]
    public function createsClassAssignments(): void
    {
        $class = $this->createClass('6ème A');
        $batch = $this->createBatchWithRows([
            $this->createMappedRow(1, 'Dupont', 'Jean', '6ème A'),
        ]);

        ($this->handler)(new ImportStudentsCommand(
            batchId: (string) $batch->id,
            tenantId: self::TENANT_ID,
            schoolName: 'École Test',
            academicYearId: self::ACADEMIC_YEAR_ID,
        ));

        $students = $this->userRepository->findStudentsByTenant($this->tenantId);
        self::assertCount(1, $students);

        $assignment = $this->classAssignmentRepository->findByStudent(
            $students[0]->id,
            $this->academicYearId,
            $this->tenantId,
        );
        self::assertNotNull($assignment);
        self::assertTrue($assignment->classId->equals($class->id));
    }

    private function createClass(string $name): SchoolClass
    {
        $class = SchoolClass::creer(
            tenantId: $this->tenantId,
            schoolId: SchoolId::generate(),
            academicYearId: $this->academicYearId,
            name: new ClassName($name),
            level: null,
            capacity: null,
            createdAt: new DateTimeImmutable('2026-01-01'),
        );

        $this->classRepository->save($class);

        return $class;
    }

    /**
     * @param list<ImportRow> $rows
     */
    private function createBatchWithRows(array $rows): StudentImportBatch
    {
        $batch = StudentImportBatch::creer(
            tenantId: $this->tenantId,
            originalFilename: 'test.csv',
            totalRows: count($rows),
            detectedColumns: ['Nom', 'Prénom', 'Classe'],
            detectedFormat: KnownImportFormat::CUSTOM,
            createdAt: new DateTimeImmutable('2026-02-24 09:00:00'),
        );

        $mapping = ColumnMapping::creer(
            [
                'Nom' => StudentImportField::LAST_NAME,
                'Prénom' => StudentImportField::FIRST_NAME,
                'Classe' => StudentImportField::CLASS_NAME,
                'Email' => StudentImportField::EMAIL,
            ],
            KnownImportFormat::CUSTOM,
        );

        $batch->appliquerMapping($mapping);
        $batch->enregistrerLignes($rows);
        $this->importBatchRepository->save($batch);

        return $batch;
    }

    private function createMappedRow(
        int $line,
        string $lastName,
        string $firstName,
        string $className,
        ?string $email = null,
    ): ImportRow {
        $mappedData = [
            'lastName' => $lastName,
            'firstName' => $firstName,
            'className' => $className,
        ];

        if ($email !== null) {
            $mappedData['email'] = $email;
        }

        return new ImportRow(
            lineNumber: $line,
            rawData: $mappedData,
            mappedData: $mappedData,
        );
    }
}
