<?php

declare(strict_types=1);

namespace App\Administration\Infrastructure\Api\Controller;

use App\Administration\Application\Service\Import\CsvParser;
use App\Administration\Application\Service\Import\FileParseResult;
use App\Administration\Application\Service\Import\XlsxParser;
use App\Administration\Domain\Exception\FichierImportInvalideException;
use App\Administration\Domain\Model\Import\ParentInvitationImportField;
use App\Administration\Domain\Model\User\User;
use App\Administration\Domain\Repository\UserRepository;
use App\Administration\Infrastructure\Security\SecurityUser;
use App\Shared\Domain\Tenant\TenantId;

use const FILTER_VALIDATE_EMAIL;

use function filter_var;
use function in_array;

use InvalidArgumentException;

use function is_array;
use function is_string;
use function mb_strtolower;
use function str_contains;
use function strtolower;

use Symfony\Component\HttpFoundation\File\UploadedFile;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Http\Attribute\CurrentUser;
use Symfony\Component\Security\Http\Attribute\IsGranted;

use function trim;

/**
 * Endpoints pour l'import d'invitations parents via fichier CSV/XLSX.
 *
 * Approche légère sans batch persistant :
 * - analyze : parse le fichier, retourne colonnes + données + mapping suggéré
 * - validate : valide les données mappées contre les élèves du tenant
 */
#[Route('/api/import/parents')]
#[IsGranted('ROLE_ADMIN')]
final readonly class ParentInvitationImportController
{
    private const int MAX_FILE_SIZE = 10 * 1024 * 1024; // 10 Mo

    public function __construct(
        private CsvParser $csvParser,
        private XlsxParser $xlsxParser,
        private UserRepository $userRepository,
    ) {
    }

    /**
     * Upload et analyse d'un fichier CSV ou XLSX.
     *
     * Retourne les colonnes détectées, les données brutes et un mapping suggéré.
     */
    #[Route('/analyze', methods: ['POST'], name: 'api_import_parents_analyze')]
    public function analyze(
        Request $request,
        #[CurrentUser] UserInterface $user,
    ): JsonResponse {
        if (!$user instanceof SecurityUser) {
            throw new AccessDeniedHttpException();
        }

        $file = $request->files->get('file');
        if (!$file instanceof UploadedFile) {
            throw new BadRequestHttpException('Un fichier CSV ou XLSX est requis.');
        }

        if ($file->getSize() > self::MAX_FILE_SIZE) {
            throw new BadRequestHttpException('Le fichier dépasse la taille maximale de 10 Mo.');
        }

        $extension = strtolower($file->getClientOriginalExtension());
        if (!in_array($extension, ['csv', 'txt', 'xlsx', 'xls'], true)) {
            throw new BadRequestHttpException('Extension non supportée. Utilisez CSV ou XLSX.');
        }

        try {
            $parseResult = $this->parseFile($file->getPathname(), $extension);
        } catch (FichierImportInvalideException|InvalidArgumentException $e) {
            throw new BadRequestHttpException($e->getMessage());
        }

        $suggestedMapping = $this->suggestMapping($parseResult->columns);

        return new JsonResponse([
            'columns' => $parseResult->columns,
            'rows' => $parseResult->rows,
            'totalRows' => $parseResult->totalRows(),
            'filename' => $file->getClientOriginalName(),
            'suggestedMapping' => $suggestedMapping,
        ]);
    }

    /**
     * Valide les lignes mappées contre les élèves existants du tenant.
     */
    #[Route('/validate', methods: ['POST'], name: 'api_import_parents_validate')]
    public function validate(
        Request $request,
        #[CurrentUser] UserInterface $user,
    ): JsonResponse {
        if (!$user instanceof SecurityUser) {
            throw new AccessDeniedHttpException();
        }

        $tenantId = TenantId::fromString($user->tenantId());

        $body = json_decode((string) $request->getContent(), true);
        if (!is_array($body) || !isset($body['rows']) || !is_array($body['rows'])) {
            throw new BadRequestHttpException('Le champ "rows" est requis.');
        }

        $students = $this->userRepository->findStudentsByTenant($tenantId);

        $validatedRows = [];
        $validCount = 0;
        $errorCount = 0;

        /** @var mixed $row */
        foreach ($body['rows'] as $row) {
            if (!is_array($row)) {
                continue;
            }

            $studentName = is_string($row['studentName'] ?? null) ? trim($row['studentName']) : '';
            $email1 = is_string($row['email1'] ?? null) ? trim($row['email1']) : '';
            $email2 = is_string($row['email2'] ?? null) ? trim($row['email2']) : '';

            $errors = [];

            if ($studentName === '') {
                $errors[] = 'Nom élève requis';
            }

            if ($email1 === '') {
                $errors[] = 'Email parent 1 requis';
            } elseif (filter_var($email1, FILTER_VALIDATE_EMAIL) === false) {
                $errors[] = 'Email parent 1 invalide';
            }

            if ($email2 !== '' && filter_var($email2, FILTER_VALIDATE_EMAIL) === false) {
                $errors[] = 'Email parent 2 invalide';
            }

            $studentId = null;
            $studentMatch = null;

            if ($studentName !== '' && $errors === []) {
                $matched = $this->matchStudent($studentName, $students);
                if ($matched !== null) {
                    $studentId = (string) $matched->id;
                    $studentMatch = $matched->firstName . ' ' . $matched->lastName;
                } else {
                    $errors[] = 'Élève "' . $studentName . '" non trouvé';
                }
            }

            $hasError = $errors !== [];
            if ($hasError) {
                ++$errorCount;
            } else {
                ++$validCount;
            }

            $validatedRows[] = [
                'studentName' => $studentName,
                'email1' => $email1,
                'email2' => $email2,
                'studentId' => $studentId,
                'studentMatch' => $studentMatch,
                'error' => $hasError ? implode(', ', $errors) : null,
            ];
        }

        return new JsonResponse([
            'validatedRows' => $validatedRows,
            'validCount' => $validCount,
            'errorCount' => $errorCount,
        ]);
    }

    private function parseFile(string $filePath, string $extension): FileParseResult
    {
        return match ($extension) {
            'xlsx', 'xls' => $this->xlsxParser->parse($filePath),
            default => $this->csvParser->parse($filePath),
        };
    }

    /**
     * @param list<string> $columns
     *
     * @return array<string, string>
     */
    private function suggestMapping(array $columns): array
    {
        $mapping = [];
        $email1Found = false;

        foreach ($columns as $column) {
            $lower = mb_strtolower($column);

            if ($this->isStudentNameColumn($lower) && !isset($mapping[$column])) {
                $mapping[$column] = ParentInvitationImportField::STUDENT_NAME->value;
            } elseif (str_contains($lower, 'email') || str_contains($lower, 'mail') || str_contains($lower, 'courriel')) {
                if (str_contains($lower, '2') || str_contains($lower, 'parent 2')) {
                    $mapping[$column] = ParentInvitationImportField::EMAIL_2->value;
                } elseif (!$email1Found) {
                    $mapping[$column] = ParentInvitationImportField::EMAIL_1->value;
                    $email1Found = true;
                } else {
                    $mapping[$column] = ParentInvitationImportField::EMAIL_2->value;
                }
            }
        }

        return $mapping;
    }

    private function isStudentNameColumn(string $lower): bool
    {
        return str_contains($lower, 'élève')
            || str_contains($lower, 'eleve')
            || str_contains($lower, 'étudiant')
            || str_contains($lower, 'etudiant')
            || str_contains($lower, 'student')
            || $lower === 'nom';
    }

    /**
     * @param User[] $students
     */
    private function matchStudent(string $name, array $students): ?User
    {
        $nameLower = mb_strtolower(trim($name));
        if ($nameLower === '') {
            return null;
        }

        // Exact match "LastName FirstName" or "FirstName LastName"
        foreach ($students as $student) {
            if (trim($student->firstName) === '' && trim($student->lastName) === '') {
                continue;
            }
            $full1 = mb_strtolower($student->lastName . ' ' . $student->firstName);
            $full2 = mb_strtolower($student->firstName . ' ' . $student->lastName);
            if ($nameLower === $full1 || $nameLower === $full2) {
                return $student;
            }
        }

        // Partial match (skip students with empty names)
        foreach ($students as $student) {
            if (trim($student->firstName) === '' && trim($student->lastName) === '') {
                continue;
            }
            $full1 = mb_strtolower($student->lastName . ' ' . $student->firstName);
            $full2 = mb_strtolower($student->firstName . ' ' . $student->lastName);
            if (str_contains($full1, $nameLower) || str_contains($full2, $nameLower)
                || str_contains($nameLower, $full1) || str_contains($nameLower, $full2)) {
                return $student;
            }
        }

        return null;
    }
}
