<?php

declare(strict_types=1);

namespace App\Administration\Application\Service\Import;

use App\Administration\Domain\Model\Import\ImportRow;
use App\Administration\Domain\Model\Import\ImportRowError;
use App\Administration\Domain\Model\Import\StudentImportField;

use const FILTER_VALIDATE_EMAIL;

use function in_array;
use function sprintf;

/**
 * Valide les lignes d'import après mapping.
 *
 * Vérifie que les champs obligatoires sont remplis,
 * les formats sont corrects (email, dates), et les classes existent.
 *
 * @see AC4: Lignes valides en vert, lignes avec erreurs en rouge
 */
final readonly class ImportRowValidator
{
    /**
     * @param list<string>|null $existingClassNames Noms des classes existantes. null = pas de vérification.
     */
    public function __construct(
        private ?array $existingClassNames = null,
    ) {
    }

    public function valider(ImportRow $row): ImportRow
    {
        $row = $this->expanderNomComplet($row);

        $errors = [];

        $errors = [...$errors, ...$this->validerChampsObligatoires($row)];
        $errors = [...$errors, ...$this->validerEmail($row)];
        $errors = [...$errors, ...$this->validerDateNaissance($row)];
        $errors = [...$errors, ...$this->validerClasse($row)];

        if ($errors !== []) {
            return $row->avecErreurs(...$errors);
        }

        return $row;
    }

    /**
     * @param list<ImportRow> $rows
     *
     * @return list<ImportRow>
     */
    public function validerTout(array $rows): array
    {
        return array_map($this->valider(...), $rows);
    }

    /**
     * @return list<ImportRowError>
     */
    private function validerChampsObligatoires(ImportRow $row): array
    {
        $errors = [];

        foreach (StudentImportField::champsObligatoires() as $field) {
            $value = $row->valeurChamp($field);

            if ($value === null || trim($value) === '') {
                $errors[] = new ImportRowError(
                    $field->value,
                    sprintf('Le champ "%s" est obligatoire.', $field->label()),
                );
            }
        }

        return $errors;
    }

    /**
     * @return list<ImportRowError>
     */
    private function validerEmail(ImportRow $row): array
    {
        $email = $row->valeurChamp(StudentImportField::EMAIL);

        if ($email === null || trim($email) === '') {
            return [];
        }

        if (filter_var($email, FILTER_VALIDATE_EMAIL) === false) {
            return [new ImportRowError(
                StudentImportField::EMAIL->value,
                sprintf('L\'adresse email "%s" est invalide.', $email),
            )];
        }

        return [];
    }

    /**
     * @return list<ImportRowError>
     */
    private function validerDateNaissance(ImportRow $row): array
    {
        $date = $row->valeurChamp(StudentImportField::BIRTH_DATE);

        if ($date === null || trim($date) === '') {
            return [];
        }

        if (DateParser::parse($date) === null) {
            return [new ImportRowError(
                StudentImportField::BIRTH_DATE->value,
                sprintf('La date "%s" est invalide. Formats acceptés : JJ/MM/AAAA, AAAA-MM-JJ.', $date),
            )];
        }

        return [];
    }

    /**
     * @return list<ImportRowError>
     */
    private function validerClasse(ImportRow $row): array
    {
        if ($this->existingClassNames === null) {
            return [];
        }

        $className = $row->valeurChamp(StudentImportField::CLASS_NAME);

        if ($className === null || trim($className) === '') {
            return [];
        }

        if (!in_array(trim($className), $this->existingClassNames, true)) {
            return [new ImportRowError(
                StudentImportField::CLASS_NAME->value,
                sprintf('La classe "%s" n\'existe pas.', $className),
            )];
        }

        return [];
    }

    /**
     * Si FULL_NAME est renseigné et que LAST_NAME/FIRST_NAME sont vides,
     * on dérive nom et prénom depuis le nom complet (format "NOM Prénom").
     */
    private function expanderNomComplet(ImportRow $row): ImportRow
    {
        $fullName = $row->valeurChamp(StudentImportField::FULL_NAME);

        if ($fullName === null || trim($fullName) === '') {
            return $row;
        }

        $lastName = $row->valeurChamp(StudentImportField::LAST_NAME);
        $firstName = $row->valeurChamp(StudentImportField::FIRST_NAME);

        if (($lastName !== null && trim($lastName) !== '') || ($firstName !== null && trim($firstName) !== '')) {
            return $row;
        }

        [$derivedLast, $derivedFirst] = self::splitFullName(trim($fullName));

        $mappedData = $row->mappedData;
        $mappedData[StudentImportField::LAST_NAME->value] = $derivedLast;
        $mappedData[StudentImportField::FIRST_NAME->value] = $derivedFirst;

        return new ImportRow($row->lineNumber, $row->rawData, $mappedData, $row->errors);
    }

    /**
     * Sépare un nom complet au format "NOM Prénom" en [nom, prénom].
     *
     * Convention Pronote : le nom de famille est en majuscules, le prénom en casse mixte.
     * Si la convention n'est pas détectable, on prend le premier mot comme nom.
     *
     * @return array{0: string, 1: string}
     */
    public static function splitFullName(string $fullName): array
    {
        $parts = preg_split('/\s+/', trim($fullName));
        if ($parts === false || $parts === []) {
            return [$fullName, ''];
        }

        $uppercaseParts = [];
        $rest = [];
        $foundNonUpper = false;

        foreach ($parts as $part) {
            if (!$foundNonUpper && mb_strtoupper($part) === $part && preg_match('/\p{L}/u', $part)) {
                $uppercaseParts[] = $part;
            } else {
                $foundNonUpper = true;
                $rest[] = $part;
            }
        }

        if ($uppercaseParts !== [] && $rest !== []) {
            return [implode(' ', $uppercaseParts), implode(' ', $rest)];
        }

        $lastName = array_shift($parts);

        return [$lastName ?? $fullName, implode(' ', $parts)];
    }
}
