<?php

declare(strict_types=1);

namespace App\Administration\Application\Service\Import;

use App\Administration\Domain\Model\Import\ColumnMapping;
use App\Administration\Domain\Model\Import\ImportRow;
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\Repository\ClassRepository;
use App\Administration\Domain\Repository\ImportBatchRepository;
use App\Administration\Domain\Repository\SavedColumnMappingRepository;
use App\Shared\Domain\Clock;
use App\Shared\Domain\Tenant\TenantId;

use function count;
use function in_array;

use InvalidArgumentException;

/**
 * Orchestre la chaîne d'import d'élèves : parse → détection → mapping → validation.
 *
 * Extrait la logique métier du contrôleur pour respecter l'architecture hexagonale.
 */
final readonly class StudentImportOrchestrator
{
    public function __construct(
        private CsvParser $csvParser,
        private XlsxParser $xlsxParser,
        private ImportFormatDetector $formatDetector,
        private ColumnMappingSuggester $mappingSuggester,
        private ClassRepository $classRepository,
        private ImportBatchRepository $importBatchRepository,
        private SavedColumnMappingRepository $savedMappingRepository,
        private ExistingStudentFinder $existingStudentFinder,
        private DuplicateDetector $duplicateDetector,
        private Clock $clock,
    ) {
    }

    /**
     * Analyse un fichier uploadé : parse, détecte le format, suggère un mapping,
     * crée le batch et enregistre les lignes mappées.
     *
     * @return array{batch: StudentImportBatch, suggestedMapping: array<string, StudentImportField>}
     */
    public function analyzeFile(string $filePath, string $extension, string $originalFilename, TenantId $tenantId): array
    {
        $parseResult = match ($extension) {
            'csv', 'txt' => $this->csvParser->parse($filePath),
            'xlsx', 'xls' => $this->xlsxParser->parse($filePath),
            default => throw new InvalidArgumentException('Format non supporté. Utilisez CSV ou XLSX.'),
        };

        $detectedFormat = $this->formatDetector->detecter($parseResult->columns);
        $suggestedMapping = $this->suggestMapping($parseResult->columns, $detectedFormat, $tenantId);

        $batch = StudentImportBatch::creer(
            tenantId: $tenantId,
            originalFilename: $originalFilename,
            totalRows: $parseResult->totalRows(),
            detectedColumns: $parseResult->columns,
            detectedFormat: $detectedFormat,
            createdAt: $this->clock->now(),
        );

        $rows = $this->mapRows($parseResult, $suggestedMapping);
        $batch->enregistrerLignes($rows);

        $this->importBatchRepository->save($batch);

        return ['batch' => $batch, 'suggestedMapping' => $suggestedMapping];
    }

    /**
     * Applique un mapping de colonnes sur un batch existant et re-mappe les lignes.
     */
    public function applyMapping(StudentImportBatch $batch, ColumnMapping $columnMapping): void
    {
        $batch->appliquerMapping($columnMapping);

        $remapped = [];
        foreach ($batch->lignes() as $row) {
            $mappedData = [];
            foreach ($columnMapping->mapping as $column => $field) {
                $mappedData[$field->value] = $row->rawData[$column] ?? '';
            }
            $remapped[] = new ImportRow($row->lineNumber, $row->rawData, $mappedData);
        }
        $batch->enregistrerLignes($remapped);

        $this->importBatchRepository->save($batch);
    }

    /**
     * Valide les lignes du batch et retourne les résultats avec les classes inconnues.
     *
     * @return array{validatedRows: list<ImportRow>, report: ImportReport, unknownClasses: list<string>}
     */
    public function generatePreview(StudentImportBatch $batch, TenantId $tenantId, ?AcademicYearId $academicYearId = null): array
    {
        $existingClasses = $this->getExistingClassNames($tenantId);
        $validator = new ImportRowValidator($existingClasses);

        $validatedRows = $validator->validerTout($batch->lignes());

        if ($academicYearId !== null) {
            $existingStudents = $this->existingStudentFinder->findAllForTenant($tenantId, $academicYearId);
            $validatedRows = $this->duplicateDetector->detecter($validatedRows, $existingStudents);
        }

        $batch->enregistrerLignes($validatedRows);
        $this->importBatchRepository->save($batch);

        $report = ImportReport::fromValidatedRows($validatedRows);
        $unknownClasses = $this->detectUnknownClasses($validatedRows, $existingClasses);

        return [
            'validatedRows' => $validatedRows,
            'report' => $report,
            'unknownClasses' => $unknownClasses,
        ];
    }

    /**
     * Prépare le batch pour la confirmation : re-valide si nécessaire
     * et filtre les lignes selon les options choisies par l'utilisateur.
     *
     * Quand createMissingClasses est activé, les erreurs de classe inconnue
     * sont retirées en re-validant sans vérification de classe.
     * La détection de doublons est ré-appliquée après re-validation
     * pour ne pas perdre les erreurs _duplicate.
     */
    public function prepareForConfirmation(
        StudentImportBatch $batch,
        bool $createMissingClasses,
        bool $importValidOnly,
        TenantId $tenantId,
        ?AcademicYearId $academicYearId = null,
    ): void {
        if ($createMissingClasses) {
            $validator = new ImportRowValidator();
            // Strip old errors before re-validating — the previous validation
            // may have added className errors that we no longer want.
            $cleanRows = array_map(
                static fn (ImportRow $row) => new ImportRow($row->lineNumber, $row->rawData, $row->mappedData),
                $batch->lignes(),
            );
            $revalidated = $validator->validerTout($cleanRows);

            if ($academicYearId !== null) {
                $existingStudents = $this->existingStudentFinder->findAllForTenant($tenantId, $academicYearId);
                $revalidated = $this->duplicateDetector->detecter($revalidated, $existingStudents);
            }

            $batch->enregistrerLignes($revalidated);
        }

        if ($importValidOnly) {
            $batch->enregistrerLignes($batch->lignesValides());
        }

        if ($batch->mapping !== null) {
            $this->savedMappingRepository->save(
                $batch->tenantId,
                $batch->mapping->format,
                $batch->mapping->mapping,
            );
        }

        $this->importBatchRepository->save($batch);
    }

    /**
     * Suggère un mapping en priorité depuis les mappings sauvegardés,
     * puis en fallback depuis la détection automatique.
     *
     * @param list<string> $columns
     *
     * @return array<string, StudentImportField>
     */
    private function suggestMapping(array $columns, KnownImportFormat $format, TenantId $tenantId): array
    {
        $saved = $this->savedMappingRepository->findByTenantAndFormat($tenantId, $format);

        if ($saved !== null && $this->savedMappingMatchesColumns($saved, $columns)) {
            return $saved;
        }

        return $this->mappingSuggester->suggerer($columns, $format);
    }

    /**
     * Vérifie que le mapping sauvegardé correspond exactement aux colonnes du fichier.
     *
     * Retourne false si le fichier contient des colonnes qui pourraient être mappées
     * mais ne le sont pas par le mapping sauvegardé.
     *
     * @param array<string, StudentImportField> $mapping
     * @param list<string>                      $columns
     */
    private function savedMappingMatchesColumns(array $mapping, array $columns): bool
    {
        foreach (array_keys($mapping) as $column) {
            if (!in_array($column, $columns, true)) {
                return false;
            }
        }

        $autoMapping = $this->mappingSuggester->suggerer($columns, KnownImportFormat::CUSTOM);
        if (count($autoMapping) > count($mapping)) {
            return false;
        }

        return true;
    }

    /**
     * @param array<string, StudentImportField> $mapping
     *
     * @return list<ImportRow>
     */
    private function mapRows(FileParseResult $parseResult, array $mapping): array
    {
        $rows = [];
        $lineNumber = 1;

        foreach ($parseResult->rows as $rawData) {
            $mappedData = [];
            foreach ($mapping as $column => $field) {
                $mappedData[$field->value] = $rawData[$column] ?? '';
            }

            $rows[] = new ImportRow($lineNumber, $rawData, $mappedData);
            ++$lineNumber;
        }

        return $rows;
    }

    /**
     * @return list<string>
     */
    private function getExistingClassNames(TenantId $tenantId): array
    {
        $classes = $this->classRepository->findAllActiveByTenant($tenantId);

        return array_values(array_map(
            static fn ($class) => (string) $class->name,
            $classes,
        ));
    }

    /**
     * @param list<ImportRow> $rows
     * @param list<string>    $existingClasses
     *
     * @return list<string>
     */
    private function detectUnknownClasses(array $rows, array $existingClasses): array
    {
        $unknown = [];

        foreach ($rows as $row) {
            $className = $row->valeurChamp(StudentImportField::CLASS_NAME);
            if ($className !== null
                && trim($className) !== ''
                && !in_array(trim($className), $existingClasses, true)
                && !in_array(trim($className), $unknown, true)
            ) {
                $unknown[] = trim($className);
            }
        }

        return $unknown;
    }
}
