<?php

declare(strict_types=1);

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

use App\Administration\Domain\Event\ImportElevesLance;
use App\Administration\Domain\Event\ImportElevesTermine;
use App\Administration\Domain\Exception\ImportNonDemarrableException;
use App\Administration\Domain\Model\Import\ColumnMapping;
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\StudentImportBatch;
use App\Administration\Domain\Model\Import\StudentImportField;
use App\Shared\Domain\Tenant\TenantId;
use DateTimeImmutable;
use PHPUnit\Framework\Attributes\Test;
use PHPUnit\Framework\TestCase;

final class StudentImportBatchTest 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-24 10:00:00');
        $columns = ['Nom', 'Prénom', 'Classe'];

        $batch = StudentImportBatch::creer(
            tenantId: $tenantId,
            originalFilename: 'eleves.csv',
            totalRows: 50,
            detectedColumns: $columns,
            detectedFormat: KnownImportFormat::PRONOTE,
            createdAt: $createdAt,
        );

        self::assertTrue($batch->tenantId->equals($tenantId));
        self::assertSame('eleves.csv', $batch->originalFilename);
        self::assertSame(50, $batch->totalRows);
        self::assertSame($columns, $batch->detectedColumns);
        self::assertSame(KnownImportFormat::PRONOTE, $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());
        self::assertSame(50, $batch->totalRows);
    }

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

        $batch->demarrer($at);

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

        $events = $batch->pullDomainEvents();
        self::assertCount(1, $events);
        self::assertInstanceOf(ImportElevesLance::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-24 12:00:00');
        $batch->terminer(45, 5, $at);

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

        $events = $batch->pullDomainEvents();
        self::assertCount(1, $events);
        self::assertInstanceOf(ImportElevesTermine::class, $events[0]);
        self::assertSame(45, $events[0]->importedCount);
        self::assertSame(5, $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-24 12:00:00');
        $batch->echouer(50, $at);

        self::assertSame(ImportStatus::FAILED, $batch->status);
        self::assertSame(50, $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(40, 10, new DateTimeImmutable());

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

    #[Test]
    public function progressionReturnsZeroForEmptyBatch(): void
    {
        $batch = StudentImportBatch::creer(
            tenantId: TenantId::fromString(self::TENANT_ID),
            originalFilename: 'empty.csv',
            totalRows: 0,
            detectedColumns: [],
            detectedFormat: null,
            createdAt: new DateTimeImmutable(),
        );

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

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

        $batch = StudentImportBatch::reconstitute(
            id: $id,
            tenantId: $tenantId,
            originalFilename: 'eleves.csv',
            totalRows: 50,
            detectedColumns: ['Nom', 'Prénom', 'Classe'],
            detectedFormat: KnownImportFormat::PRONOTE,
            status: ImportStatus::COMPLETED,
            mapping: $mapping,
            importedCount: 45,
            errorCount: 5,
            createdAt: $createdAt,
            completedAt: $completedAt,
        );

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

    private function createBatch(): StudentImportBatch
    {
        return StudentImportBatch::creer(
            tenantId: TenantId::fromString(self::TENANT_ID),
            originalFilename: 'eleves.csv',
            totalRows: 50,
            detectedColumns: ['Nom', 'Prénom', 'Classe', 'Email'],
            detectedFormat: KnownImportFormat::CUSTOM,
            createdAt: new DateTimeImmutable('2026-02-24 10:00:00'),
        );
    }

    private function createValidMapping(): ColumnMapping
    {
        return ColumnMapping::creer(
            [
                'Nom' => StudentImportField::LAST_NAME,
                'Prénom' => StudentImportField::FIRST_NAME,
                'Classe' => StudentImportField::CLASS_NAME,
            ],
            KnownImportFormat::CUSTOM,
        );
    }
}
