<?php

namespace App\Controller;

use App\Entity\ActivityLog;
use App\Repository\StockVehicleRepository;
use App\Service\ActivityLogger;
use App\Service\ConcessionIdMigrator;
use App\Service\ConcessionImporter;
use App\Service\ConcessionsProvider;
use App\Service\ImportProgressTracker;
use App\Service\StockImporter;
use OpenSpout\Common\Entity\Row;
use OpenSpout\Writer\XLSX\Writer;
use Psr\Log\LoggerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\File\UploadedFile;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\Security\Http\Attribute\IsGranted;

/**
 * Menu Paramètres (réservé aux administrateurs) :
 * import du référentiel des concessions (ID_CONCESSION + nom) et de la photo du stock (Excel).
 */
#[IsGranted('ROLE_ADMIN')]
final class ParamController extends AbstractController
{
    private const MAX_STOCK_SIZE = 20 * 1024 * 1024;

    #[Route('/param', name: 'app_param', methods: ['GET'])]
    public function index(ConcessionsProvider $concessions, StockVehicleRepository $stock): Response
    {
        return $this->render('page/param.html.twig', [
            'count' => $concessions->count(),
            'sample' => \array_slice(array_values($concessions->all()), 0, 12),
            'stock' => $stock->summary(),
        ]);
    }

    #[Route('/param/concessions/export', name: 'app_param_export', methods: ['GET'])]
    public function exportList(ConcessionsProvider $concessions): Response
    {
        $path = tempnam(sys_get_temp_dir(), 'pv_conc_');
        if (false === $path) {
            throw new \RuntimeException('Impossible de créer un fichier temporaire.');
        }

        try {
            $writer = new Writer();
            $writer->openToFile($path);
            $writer->addRow(Row::fromValues(['ID_CONCESSION', 'NOM']));
            foreach ($concessions->all() as $id => $name) {
                $writer->addRow(Row::fromValues([$id, $name]));
            }
            $writer->close();

            $content = file_get_contents($path);
        } finally {
            if (is_file($path)) {
                @unlink($path);
            }
        }

        return new Response((string) $content, Response::HTTP_OK, [
            'Content-Type' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
            'Content-Disposition' => 'attachment; filename="concessions.xlsx"',
        ]);
    }

    #[Route('/param/concessions/import', name: 'app_param_import', methods: ['POST'])]
    public function import(
        Request $request,
        ConcessionImporter $importer,
        ConcessionIdMigrator $migrator,
        ImportProgressTracker $tracker,
        ActivityLogger $activity,
        LoggerInterface $logger,
    ): Response {
        if (!$this->isCsrfTokenValid('param_concessions', (string) $request->request->get('_token'))) {
            $this->addFlash('error', 'Jeton de sécurité invalide. Réessayez.');

            return $this->redirectToRoute('app_param');
        }

        $file = $request->files->get('file');
        if (!$file instanceof UploadedFile) {
            $this->addFlash('error', 'Veuillez sélectionner un fichier.');

            return $this->redirectToRoute('app_param');
        }

        $extension = strtolower($file->getClientOriginalExtension());
        if (!\in_array($extension, ['xlsx', 'csv'], true)) {
            $this->addFlash('error', 'Format non supporté. Utilisez un fichier .xlsx ou .csv.');

            return $this->redirectToRoute('app_param');
        }

        $track = $this->beginProgress($request, $tracker);
        $ok = true;

        try {
            $track('Lecture du fichier…');
            $count = $importer->importFromFile(
                $file->getPathname(),
                $extension,
                $request->request->getBoolean('skip_header'),
                static fn (int $read) => $track('Lecture du fichier…', $read),
            );
            $this->addFlash('success', sprintf('%d concession(s) importée(s) avec succès.', $count));

            // Bascule les anciennes données stockées par nom vers les IDs du référentiel.
            $track('Conversion des données existantes vers les IDs…');
            $migrated = $migrator->migrate();
            if ($migrated['users'] > 0 || $migrated['sessions'] > 0 || $migrated['stock'] > 0) {
                $this->addFlash('success', sprintf(
                    'Données existantes converties vers les IDs : %d utilisateur(s), %d inventaire(s), %d ligne(s) de stock.',
                    $migrated['users'],
                    $migrated['sessions'],
                    $migrated['stock'],
                ));
            }
            $activity->log(ActivityLog::PARAM_CONCESSIONS_IMPORT, null, [
                'concessions' => $count,
                'fichier' => $file->getClientOriginalName(),
                'migrees' => $migrated,
            ]);
            $track('Import terminé.', null, null, true);
        } catch (\Throwable $e) {
            $ok = false;
            $logger->error('Import concessions', ['exception' => $e]);
            $this->addFlash('error', $e->getMessage() ?: "Échec de l'import.");
            $track($e->getMessage() ?: "Échec de l'import.", null, null, true, true);
        }

        return $this->importResponse($request, $ok);
    }

    #[Route('/param/stock/import', name: 'app_param_stock_import', methods: ['POST'])]
    public function importStock(
        Request $request,
        StockImporter $importer,
        StockVehicleRepository $repository,
        ConcessionsProvider $concessions,
        ImportProgressTracker $tracker,
        ActivityLogger $activity,
        LoggerInterface $logger,
    ): Response {
        if (!$this->isCsrfTokenValid('param_stock', (string) $request->request->get('_token'))) {
            $this->addFlash('error', 'Jeton de sécurité invalide. Réessayez.');

            return $this->redirectToRoute('app_param');
        }

        $file = $request->files->get('file');
        if (!$file instanceof UploadedFile) {
            $this->addFlash('error', 'Veuillez sélectionner un fichier de stock (.xlsx).');

            return $this->redirectToRoute('app_param');
        }
        if ($file->getSize() > self::MAX_STOCK_SIZE) {
            $this->addFlash('error', 'Fichier de stock trop volumineux (max 20 Mo).');

            return $this->redirectToRoute('app_param');
        }

        $track = $this->beginProgress($request, $tracker);
        $ok = true;

        try {
            $track('Lecture du fichier…');
            $imported = $importer->import(
                $file->getPathname(),
                $file->getClientOriginalExtension(),
                static fn (int $read) => $track('Lecture du fichier…', $read),
            );
            $track('Enregistrement des véhicules…', 0, \count($imported['rows']));
            $count = $repository->replaceAll(
                $imported['rows'],
                static fn (int $saved, int $total) => $track('Enregistrement des véhicules…', $saved, $total),
            );
            $track('Finalisation…');
            $summary = $repository->summary();
            $dates = array_map(static fn (\DateTimeImmutable $d): string => $d->format('d/m/Y'), $summary['dates']);
            $this->addFlash('success', sprintf(
                'Photo du stock importée : %d véhicule(s), %d concession(s), date du stock : %s.',
                $count,
                $summary['concessions'],
                [] !== $dates ? implode(', ', $dates) : '-',
            ));
            if (!$imported['headerDetected']) {
                $this->addFlash('error', 'Aucune ligne d’en-tête reconnue : colonnes lues dans l’ordre ID_CONCESSION, DATE_STOCK, TYPE, IMMATRICULATION, VIN, LOCALISATION. Vérifiez le résultat.');
            }

            // Signale les IDs de concession du fichier inconnus du référentiel.
            $unknown = [];
            foreach ($imported['rows'] as $r) {
                if ('' !== $r['concessionId'] && !$concessions->isValid($r['concessionId'])) {
                    $unknown[$r['concessionId']] = true;
                }
            }
            if ([] !== $unknown) {
                $this->addFlash('error', sprintf(
                    '%d ID(s) de concession du fichier ne sont pas dans le référentiel : %s. Ces lignes ne seront rattachées à aucune concession connue.',
                    \count($unknown),
                    implode(', ', \array_slice(array_keys($unknown), 0, 10)),
                ));
            }
            $activity->log(ActivityLog::PARAM_STOCK_IMPORT, null, [
                'vehicules' => $count,
                'concessions' => $summary['concessions'],
                'fichier' => $file->getClientOriginalName(),
            ]);
            $track('Import terminé.', null, null, true);
        } catch (\Throwable $e) {
            $ok = false;
            $logger->error('Import photo du stock', ['exception' => $e]);
            $this->addFlash('error', $e->getMessage() ?: "Échec de l'import du stock.");
            $track($e->getMessage() ?: "Échec de l'import du stock.", null, null, true, true);
        }

        return $this->importResponse($request, $ok);
    }

    /**
     * Avancement d'un import en cours, interrogé par la page Paramètres
     * pendant le traitement.
     */
    #[Route('/param/import/progress/{token}', name: 'app_param_import_progress', methods: ['GET'], requirements: ['token' => '[A-Za-z0-9-]{8,64}'])]
    public function importProgress(string $token, ImportProgressTracker $tracker): JsonResponse
    {
        return new JsonResponse($tracker->read($token) ?? [
            'label' => 'En attente du serveur…',
            'current' => null,
            'total' => null,
            'done' => false,
            'error' => false,
        ]);
    }

    /**
     * Prépare le suivi de progression d'un import : renvoie une closure de mise à
     * jour (silencieuse si le navigateur n'a pas fourni de jeton, cas sans JS) et
     * libère le verrou de session PHP pour que les requêtes de suivi — qui portent
     * la même session — ne restent pas bloquées pendant le traitement.
     *
     * @return callable(string, ?int=, ?int=, bool=, bool=): void
     */
    private function beginProgress(Request $request, ImportProgressTracker $tracker): callable
    {
        $token = (string) $request->request->get('progress_token', '');
        $request->getSession()->save();

        return static function (string $label, ?int $current = null, ?int $total = null, bool $done = false, bool $error = false) use ($tracker, $token): void {
            if ('' !== $token) {
                $tracker->update($token, $label, $current, $total, $done, $error);
            }
        };
    }

    /**
     * Fin d'import : JSON pour le flux JS (qui recharge la page, les flashs
     * s'affichent alors), redirection classique sinon.
     */
    private function importResponse(Request $request, bool $ok): Response
    {
        if ('' !== (string) $request->request->get('progress_token', '')) {
            return new JsonResponse(['ok' => $ok]);
        }

        return $this->redirectToRoute('app_param');
    }

    #[Route('/param/stock/reset', name: 'app_param_stock_reset', methods: ['POST'])]
    public function resetStock(Request $request, StockVehicleRepository $repository, ActivityLogger $activity): Response
    {
        if (!$this->isCsrfTokenValid('param_stock', (string) $request->request->get('_token'))) {
            $this->addFlash('error', 'Jeton de sécurité invalide. Réessayez.');

            return $this->redirectToRoute('app_param');
        }

        $repository->replaceAll([]);
        $activity->log(ActivityLog::PARAM_STOCK_RESET);
        $this->addFlash('success', 'Photo du stock supprimée.');

        return $this->redirectToRoute('app_param');
    }
}
