<?php

declare(strict_types=1);

namespace App\Tests\Unit\Administration\Application\Service\Import;

use App\Administration\Application\Service\Import\DuplicateDetector;
use App\Administration\Domain\Model\Import\ImportRow;
use PHPUnit\Framework\Attributes\Test;
use PHPUnit\Framework\TestCase;

final class DuplicateDetectorTest extends TestCase
{
    private DuplicateDetector $detector;

    protected function setUp(): void
    {
        $this->detector = new DuplicateDetector();
    }

    #[Test]
    public function detectsDuplicateByEmail(): void
    {
        $existing = [
            $this->student('Jean', 'Dupont', 'jean@example.com', null, '6A'),
        ];
        $rows = [
            $this->createRow(['lastName' => 'Dupont', 'firstName' => 'Jean', 'email' => 'jean@example.com', 'className' => '6A']),
        ];

        $result = $this->detector->detecter($rows, $existing);

        self::assertFalse($result[0]->estValide());
        self::assertSame('_duplicate', $result[0]->errors[0]->column);
        self::assertStringContainsString('email', $result[0]->errors[0]->message);
    }

    #[Test]
    public function detectsDuplicateByEmailCaseInsensitive(): void
    {
        $existing = [
            $this->student('Jean', 'Dupont', 'Jean.Dupont@Example.COM', null, '6A'),
        ];
        $rows = [
            $this->createRow(['lastName' => 'Dupont', 'firstName' => 'Jean', 'email' => 'jean.dupont@example.com', 'className' => '6A']),
        ];

        $result = $this->detector->detecter($rows, $existing);

        self::assertFalse($result[0]->estValide());
        self::assertStringContainsString('email', $result[0]->errors[0]->message);
    }

    #[Test]
    public function detectsDuplicateByStudentNumber(): void
    {
        $existing = [
            $this->student('Jean', 'Dupont', null, 'STU-001', '6A'),
        ];
        $rows = [
            $this->createRow(['lastName' => 'Dupont', 'firstName' => 'Jean', 'studentNumber' => 'STU-001', 'className' => '6A']),
        ];

        $result = $this->detector->detecter($rows, $existing);

        self::assertFalse($result[0]->estValide());
        self::assertStringContainsString('numéro élève', $result[0]->errors[0]->message);
    }

    #[Test]
    public function detectsDuplicateByNameAndClass(): void
    {
        $existing = [
            $this->student('Jean', 'Dupont', null, null, '6A'),
        ];
        $rows = [
            $this->createRow(['lastName' => 'Dupont', 'firstName' => 'Jean', 'className' => '6A']),
        ];

        $result = $this->detector->detecter($rows, $existing);

        self::assertFalse($result[0]->estValide());
        self::assertStringContainsString('nom + classe', $result[0]->errors[0]->message);
    }

    #[Test]
    public function nameAndClassMatchIsCaseInsensitive(): void
    {
        $existing = [
            $this->student('JEAN', 'DUPONT', null, null, '6A'),
        ];
        $rows = [
            $this->createRow(['lastName' => 'dupont', 'firstName' => 'jean', 'className' => '6a']),
        ];

        $result = $this->detector->detecter($rows, $existing);

        self::assertFalse($result[0]->estValide());
    }

    #[Test]
    public function sameNameDifferentClassIsNotDuplicate(): void
    {
        $existing = [
            $this->student('Jean', 'Dupont', null, null, '6A'),
        ];
        $rows = [
            $this->createRow(['lastName' => 'Dupont', 'firstName' => 'Jean', 'className' => '6B']),
        ];

        $result = $this->detector->detecter($rows, $existing);

        self::assertTrue($result[0]->estValide());
    }

    #[Test]
    public function detectsIntraFileDuplicate(): void
    {
        $existing = [];
        $rows = [
            $this->createRow(['lastName' => 'Dupont', 'firstName' => 'Jean', 'className' => '6A'], 1),
            $this->createRow(['lastName' => 'Dupont', 'firstName' => 'Jean', 'className' => '6A'], 2),
        ];

        $result = $this->detector->detecter($rows, $existing);

        self::assertTrue($result[0]->estValide());
        self::assertFalse($result[1]->estValide());
        self::assertSame('_duplicate', $result[1]->errors[0]->column);
    }

    #[Test]
    public function detectsIntraFileDuplicateByEmail(): void
    {
        $existing = [];
        $rows = [
            $this->createRow(['lastName' => 'Dupont', 'firstName' => 'Jean', 'email' => 'jean@example.com', 'className' => '6A'], 1),
            $this->createRow(['lastName' => 'Martin', 'firstName' => 'Pierre', 'email' => 'jean@example.com', 'className' => '5B'], 2),
        ];

        $result = $this->detector->detecter($rows, $existing);

        self::assertTrue($result[0]->estValide());
        self::assertFalse($result[1]->estValide());
    }

    #[Test]
    public function rowWithoutEmailOrNumberOrClassIsNotDuplicate(): void
    {
        $existing = [
            $this->student('Jean', 'Dupont', null, null, '6A'),
        ];
        $rows = [
            $this->createRow(['lastName' => 'Dupont', 'firstName' => 'Jean', 'className' => '']),
        ];

        $result = $this->detector->detecter($rows, $existing);

        self::assertTrue($result[0]->estValide());
    }

    #[Test]
    public function multipleRowsMixedDuplicatesAndValid(): void
    {
        $existing = [
            $this->student('Jean', 'Dupont', 'jean@example.com', null, '6A'),
        ];
        $rows = [
            $this->createRow(['lastName' => 'Dupont', 'firstName' => 'Jean', 'email' => 'jean@example.com', 'className' => '6A'], 1),
            $this->createRow(['lastName' => 'Martin', 'firstName' => 'Pierre', 'className' => '6A'], 2),
            $this->createRow(['lastName' => 'Bernard', 'firstName' => 'Claire', 'className' => '6B'], 3),
        ];

        $result = $this->detector->detecter($rows, $existing);

        self::assertFalse($result[0]->estValide());
        self::assertTrue($result[1]->estValide());
        self::assertTrue($result[2]->estValide());
    }

    #[Test]
    public function emailMatchTakesPriorityOverNameClass(): void
    {
        $existing = [
            $this->student('Jean', 'Dupont', 'jean@example.com', null, '6A'),
        ];
        $rows = [
            $this->createRow(['lastName' => 'Dupont', 'firstName' => 'Jean', 'email' => 'jean@example.com', 'className' => '6A']),
        ];

        $result = $this->detector->detecter($rows, $existing);

        self::assertStringContainsString('email', $result[0]->errors[0]->message);
    }

    #[Test]
    public function preservesExistingValidationErrors(): void
    {
        $existing = [
            $this->student('Jean', 'Dupont', null, null, '6A'),
        ];

        $row = new ImportRow(
            lineNumber: 1,
            rawData: ['lastName' => 'Dupont', 'firstName' => 'Jean', 'className' => '6A'],
            mappedData: ['lastName' => 'Dupont', 'firstName' => 'Jean', 'className' => '6A'],
            errors: [new \App\Administration\Domain\Model\Import\ImportRowError('email', 'Email invalide.')],
        );

        $result = $this->detector->detecter([$row], $existing);

        self::assertCount(2, $result[0]->errors);
        self::assertSame('email', $result[0]->errors[0]->column);
        self::assertSame('_duplicate', $result[0]->errors[1]->column);
    }

    /**
     * @param array<string, string> $mappedData
     */
    private function createRow(array $mappedData, int $lineNumber = 1): ImportRow
    {
        return new ImportRow(
            lineNumber: $lineNumber,
            rawData: $mappedData,
            mappedData: $mappedData,
        );
    }

    /**
     * @return array{firstName: string, lastName: string, email: ?string, studentNumber: ?string, className: ?string}
     */
    private function student(string $firstName, string $lastName, ?string $email, ?string $studentNumber, ?string $className): array
    {
        return [
            'firstName' => $firstName,
            'lastName' => $lastName,
            'email' => $email,
            'studentNumber' => $studentNumber,
            'className' => $className,
        ];
    }
}
