<?php

declare(strict_types=1);

namespace App\Administration\Domain\Model\Import;

use App\Administration\Domain\Event\ImportEnseignantsEchoue;
use App\Administration\Domain\Event\ImportEnseignantsLance;
use App\Administration\Domain\Event\ImportEnseignantsTermine;
use App\Administration\Domain\Exception\ImportNonDemarrableException;
use App\Shared\Domain\AggregateRoot;
use App\Shared\Domain\Tenant\TenantId;

use function array_filter;
use function array_values;

use DateTimeImmutable;

use function min;

/**
 * Aggregate Root représentant un lot d'import d'enseignants.
 *
 * Gère le cycle de vie d'un import depuis l'upload du fichier
 * jusqu'à la confirmation finale.
 *
 * @see FR77: Import enseignants via CSV
 */
final class TeacherImportBatch extends AggregateRoot
{
    public private(set) ?TeacherColumnMapping $mapping = null;
    public private(set) int $importedCount = 0;
    public private(set) int $errorCount = 0;
    public private(set) ?DateTimeImmutable $completedAt = null;

    /** @var list<ImportRow> */
    private array $rows = [];

    private function __construct(
        public private(set) ImportBatchId $id,
        public private(set) TenantId $tenantId,
        public private(set) string $originalFilename,
        public private(set) int $totalRows,
        /** @var list<string> */
        public private(set) array $detectedColumns,
        public private(set) ?KnownImportFormat $detectedFormat,
        public private(set) ImportStatus $status,
        public private(set) DateTimeImmutable $createdAt,
    ) {
    }

    /**
     * @param list<string> $detectedColumns Colonnes détectées dans le fichier
     */
    public static function creer(
        TenantId $tenantId,
        string $originalFilename,
        int $totalRows,
        array $detectedColumns,
        ?KnownImportFormat $detectedFormat,
        DateTimeImmutable $createdAt,
    ): self {
        return new self(
            id: ImportBatchId::generate(),
            tenantId: $tenantId,
            originalFilename: $originalFilename,
            totalRows: $totalRows,
            detectedColumns: $detectedColumns,
            detectedFormat: $detectedFormat,
            status: ImportStatus::PENDING,
            createdAt: $createdAt,
        );
    }

    public function appliquerMapping(TeacherColumnMapping $mapping): void
    {
        $this->mapping = $mapping;
    }

    /**
     * @param list<ImportRow> $rows
     */
    public function enregistrerLignes(array $rows): void
    {
        $this->rows = $rows;
    }

    public function demarrer(DateTimeImmutable $at): void
    {
        if (!$this->status->peutDemarrer()) {
            throw ImportNonDemarrableException::pourStatut($this->id, $this->status);
        }

        if ($this->mapping === null) {
            throw ImportNonDemarrableException::mappingManquant($this->id);
        }

        $this->status = ImportStatus::PROCESSING;

        $this->recordEvent(new ImportEnseignantsLance(
            batchId: $this->id,
            tenantId: $this->tenantId,
            totalRows: $this->totalRows,
            occurredOn: $at,
        ));
    }

    public function mettreAJourProgression(int $importedCount, int $errorCount): void
    {
        $this->importedCount = $importedCount;
        $this->errorCount = $errorCount;
    }

    public function terminer(int $importedCount, int $errorCount, DateTimeImmutable $at): void
    {
        $this->status = ImportStatus::COMPLETED;
        $this->importedCount = $importedCount;
        $this->errorCount = $errorCount;
        $this->completedAt = $at;

        $this->recordEvent(new ImportEnseignantsTermine(
            batchId: $this->id,
            tenantId: $this->tenantId,
            importedCount: $importedCount,
            errorCount: $errorCount,
            occurredOn: $at,
        ));
    }

    public function echouer(int $errorCount, DateTimeImmutable $at): void
    {
        $this->status = ImportStatus::FAILED;
        $this->errorCount = $errorCount;
        $this->completedAt = $at;

        $this->recordEvent(new ImportEnseignantsEchoue(
            batchId: $this->id,
            tenantId: $this->tenantId,
            errorCount: $errorCount,
            occurredOn: $at,
        ));
    }

    /**
     * @return list<ImportRow>
     */
    public function lignes(): array
    {
        return $this->rows;
    }

    /**
     * @return list<ImportRow>
     */
    public function lignesValides(): array
    {
        return array_values(array_filter(
            $this->rows,
            static fn (ImportRow $row): bool => $row->estValide(),
        ));
    }

    /**
     * @return list<ImportRow>
     */
    public function lignesEnErreur(): array
    {
        return array_values(array_filter(
            $this->rows,
            static fn (ImportRow $row): bool => !$row->estValide(),
        ));
    }

    public function estTermine(): bool
    {
        return $this->status->estTermine();
    }

    public function progression(): float
    {
        if ($this->totalRows === 0) {
            return 0.0;
        }

        return min(100.0, ($this->importedCount + $this->errorCount) / $this->totalRows * 100);
    }

    /**
     * @internal Pour usage Infrastructure uniquement
     *
     * @param list<string> $detectedColumns
     */
    public static function reconstitute(
        ImportBatchId $id,
        TenantId $tenantId,
        string $originalFilename,
        int $totalRows,
        array $detectedColumns,
        ?KnownImportFormat $detectedFormat,
        ImportStatus $status,
        ?TeacherColumnMapping $mapping,
        int $importedCount,
        int $errorCount,
        DateTimeImmutable $createdAt,
        ?DateTimeImmutable $completedAt,
    ): self {
        $batch = new self(
            id: $id,
            tenantId: $tenantId,
            originalFilename: $originalFilename,
            totalRows: $totalRows,
            detectedColumns: $detectedColumns,
            detectedFormat: $detectedFormat,
            status: $status,
            createdAt: $createdAt,
        );

        $batch->mapping = $mapping;
        $batch->importedCount = $importedCount;
        $batch->errorCount = $errorCount;
        $batch->completedAt = $completedAt;

        return $batch;
    }
}
