<?php

declare(strict_types=1);

namespace App\Tests\Unit\Administration\Domain\Model\Import;

use App\Administration\Domain\Event\ImportEnseignantsLance;
use App\Administration\Domain\Event\ImportEnseignantsTermine;
use App\Administration\Domain\Exception\ImportNonDemarrableException;
use App\Administration\Domain\Model\Import\ImportBatchId;
use App\Administration\Domain\Model\Import\ImportRow;
use App\Administration\Domain\Model\Import\ImportRowError;
use App\Administration\Domain\Model\Import\ImportStatus;
use App\Administration\Domain\Model\Import\KnownImportFormat;
use App\Administration\Domain\Model\Import\TeacherColumnMapping;
use App\Administration\Domain\Model\Import\TeacherImportBatch;
use App\Administration\Domain\Model\Import\TeacherImportField;
use App\Shared\Domain\Tenant\TenantId;
use DateTimeImmutable;
use PHPUnit\Framework\Attributes\Test;
use PHPUnit\Framework\TestCase;

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

    #[Test]
    public function creerCreatesBatchWithPendingStatus(): void
    {
        $batch = $this->createBatch();

        self::assertSame(ImportStatus::PENDING, $batch->status);
        self::assertSame(0, $batch->importedCount);
        self::assertSame(0, $batch->errorCount);
        self::assertNull($batch->completedAt);
        self::assertNull($batch->mapping);
    }

    #[Test]
    public function creerSetsAllProperties(): void
    {
        $tenantId = TenantId::fromString(self::TENANT_ID);
        $createdAt = new DateTimeImmutable('2026-02-25 10:00:00');
        $columns = ['Nom', 'Prénom', 'Email'];

        $batch = TeacherImportBatch::creer(
            tenantId: $tenantId,
            originalFilename: 'enseignants.csv',
            totalRows: 20,
            detectedColumns: $columns,
            detectedFormat: KnownImportFormat::CUSTOM,
            createdAt: $createdAt,
        );

        self::assertTrue($batch->tenantId->equals($tenantId));
        self::assertSame('enseignants.csv', $batch->originalFilename);
        self::assertSame(20, $batch->totalRows);
        self::assertSame($columns, $batch->detectedColumns);
        self::assertSame(KnownImportFormat::CUSTOM, $batch->detectedFormat);
        self::assertEquals($createdAt, $batch->createdAt);
    }

    #[Test]
    public function creerDoesNotRecordAnyEvent(): void
    {
        $batch = $this->createBatch();

        self::assertEmpty($batch->pullDomainEvents());
    }

    #[Test]
    public function appliquerMappingSetsMapping(): void
    {
        $batch = $this->createBatch();
        $mapping = $this->createValidMapping();

        $batch->appliquerMapping($mapping);

        self::assertNotNull($batch->mapping);
        self::assertTrue($batch->mapping->equals($mapping));
    }

    #[Test]
    public function enregistrerLignesStoresRows(): void
    {
        $batch = $this->createBatch();
        $rows = [
            new ImportRow(1, ['Nom' => 'Dupont'], ['lastName' => 'Dupont']),
            new ImportRow(2, ['Nom' => 'Martin'], ['lastName' => 'Martin']),
        ];

        $batch->enregistrerLignes($rows);

        self::assertCount(2, $batch->lignes());
    }

    #[Test]
    public function demarrerTransitionsToProcessingAndRecordsEvent(): void
    {
        $batch = $this->createBatch();
        $batch->appliquerMapping($this->createValidMapping());
        $at = new DateTimeImmutable('2026-02-25 11:00:00');

        $batch->demarrer($at);

        self::assertSame(ImportStatus::PROCESSING, $batch->status);

        $events = $batch->pullDomainEvents();
        self::assertCount(1, $events);
        self::assertInstanceOf(ImportEnseignantsLance::class, $events[0]);
        self::assertTrue($events[0]->batchId->equals($batch->id));
        self::assertTrue($events[0]->tenantId->equals($batch->tenantId));
    }

    #[Test]
    public function demarrerSansMappingLeveException(): void
    {
        $batch = $this->createBatch();

        $this->expectException(ImportNonDemarrableException::class);

        $batch->demarrer(new DateTimeImmutable());
    }

    #[Test]
    public function demarrerDepuisStatutNonPendingLeveException(): void
    {
        $batch = $this->createBatch();
        $batch->appliquerMapping($this->createValidMapping());
        $batch->demarrer(new DateTimeImmutable());

        $this->expectException(ImportNonDemarrableException::class);

        $batch->demarrer(new DateTimeImmutable());
    }

    #[Test]
    public function terminerSetsCompletedStatusAndRecordsEvent(): void
    {
        $batch = $this->createBatch();
        $batch->appliquerMapping($this->createValidMapping());
        $batch->demarrer(new DateTimeImmutable());
        $batch->pullDomainEvents();

        $at = new DateTimeImmutable('2026-02-25 12:00:00');
        $batch->terminer(18, 2, $at);

        self::assertSame(ImportStatus::COMPLETED, $batch->status);
        self::assertSame(18, $batch->importedCount);
        self::assertSame(2, $batch->errorCount);
        self::assertEquals($at, $batch->completedAt);
        self::assertTrue($batch->estTermine());

        $events = $batch->pullDomainEvents();
        self::assertCount(1, $events);
        self::assertInstanceOf(ImportEnseignantsTermine::class, $events[0]);
        self::assertSame(18, $events[0]->importedCount);
        self::assertSame(2, $events[0]->errorCount);
    }

    #[Test]
    public function echouerSetsFailedStatus(): void
    {
        $batch = $this->createBatch();
        $batch->appliquerMapping($this->createValidMapping());
        $batch->demarrer(new DateTimeImmutable());
        $batch->pullDomainEvents();

        $at = new DateTimeImmutable('2026-02-25 12:00:00');
        $batch->echouer(20, $at);

        self::assertSame(ImportStatus::FAILED, $batch->status);
        self::assertSame(20, $batch->errorCount);
        self::assertEquals($at, $batch->completedAt);
        self::assertTrue($batch->estTermine());
    }

    #[Test]
    public function lignesValidesFiltersCorrectly(): void
    {
        $batch = $this->createBatch();
        $rows = [
            new ImportRow(1, [], ['lastName' => 'Dupont']),
            new ImportRow(2, [], ['lastName' => ''], [new ImportRowError('lastName', 'Nom vide')]),
            new ImportRow(3, [], ['lastName' => 'Martin']),
        ];

        $batch->enregistrerLignes($rows);

        self::assertCount(2, $batch->lignesValides());
        self::assertCount(1, $batch->lignesEnErreur());
    }

    #[Test]
    public function progressionCalculatesCorrectly(): void
    {
        $batch = $this->createBatch();
        $batch->appliquerMapping($this->createValidMapping());
        $batch->demarrer(new DateTimeImmutable());

        $batch->terminer(18, 2, new DateTimeImmutable());

        self::assertEqualsWithDelta(100.0, $batch->progression(), 0.01);
    }

    #[Test]
    public function reconstituteRestoresAllProperties(): void
    {
        $id = ImportBatchId::generate();
        $tenantId = TenantId::fromString(self::TENANT_ID);
        $mapping = $this->createValidMapping();
        $createdAt = new DateTimeImmutable('2026-02-25 10:00:00');
        $completedAt = new DateTimeImmutable('2026-02-25 12:00:00');

        $batch = TeacherImportBatch::reconstitute(
            id: $id,
            tenantId: $tenantId,
            originalFilename: 'enseignants.csv',
            totalRows: 20,
            detectedColumns: ['Nom', 'Prénom', 'Email'],
            detectedFormat: KnownImportFormat::CUSTOM,
            status: ImportStatus::COMPLETED,
            mapping: $mapping,
            importedCount: 18,
            errorCount: 2,
            createdAt: $createdAt,
            completedAt: $completedAt,
        );

        self::assertTrue($batch->id->equals($id));
        self::assertTrue($batch->tenantId->equals($tenantId));
        self::assertSame('enseignants.csv', $batch->originalFilename);
        self::assertSame(20, $batch->totalRows);
        self::assertSame(ImportStatus::COMPLETED, $batch->status);
        self::assertNotNull($batch->mapping);
        self::assertSame(18, $batch->importedCount);
        self::assertSame(2, $batch->errorCount);
        self::assertEquals($createdAt, $batch->createdAt);
        self::assertEquals($completedAt, $batch->completedAt);
        self::assertEmpty($batch->pullDomainEvents());
    }

    private function createBatch(): TeacherImportBatch
    {
        return TeacherImportBatch::creer(
            tenantId: TenantId::fromString(self::TENANT_ID),
            originalFilename: 'enseignants.csv',
            totalRows: 20,
            detectedColumns: ['Nom', 'Prénom', 'Email', 'Matières', 'Classes'],
            detectedFormat: KnownImportFormat::CUSTOM,
            createdAt: new DateTimeImmutable('2026-02-25 10:00:00'),
        );
    }

    private function createValidMapping(): TeacherColumnMapping
    {
        return TeacherColumnMapping::creer(
            [
                'Nom' => TeacherImportField::LAST_NAME,
                'Prénom' => TeacherImportField::FIRST_NAME,
                'Email' => TeacherImportField::EMAIL,
            ],
            KnownImportFormat::CUSTOM,
        );
    }
}
