<?php


namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
use App\Entity\Place;
use App\Entity\Stats;
use App\Entity\StatsHistory;
use App\Service\Motocultrice;
use App\Service\BudgetService;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\HttpFoundation\Request;
use function uuid_create;
use Symfony\Component\Filesystem\Filesystem;
use Symfony\Component\HttpFoundation\JsonResponse;
use Twig\Environment;
use App\Service\ActionLogger;
use DateTime;
use App\Service\FollowUpService;

final class AdminController extends AbstractController
{

    private FollowUpService $followUpService;
    // Flag pour activer/désactiver la suppression de ville
    private $allowDeleteCity = false;

    public function __construct(
        private EntityManagerInterface $entityManager,
        private Motocultrice $motocultrice,
        private BudgetService $budgetService,
        private Environment $twig,
        private ActionLogger $actionLogger,
        FollowUpService $followUpService
    ) {
        $this->followUpService = $followUpService;
    }


    #[Route('/admin/labourer-toutes-les-zones', name: 'app_admin_labourer_tout')]
    public function labourer_tout(): Response
    {



        $this->actionLogger->log('labourer_toutes_les_zones', []);
        $updateExisting = true;

        $stats_all = $this->entityManager->getRepository(Stats::class)->findAll();

        echo 'on a trouvé ' . count($stats_all) . ' zones à labourer<br>';

        foreach ($stats_all as $stats) {

            echo '<br> on laboure la zone ' . $stats->getZone() . ' ';

            $processedCount = 0;
            $updatedCount = 0;
            $insee_code = $stats->getZone();
            // Vérifier si le code INSEE est un nombre valide
            // Vérifier si les stats ont été modifiées il y a moins de 24h
            if ($stats->getDateModified() !== null) {
                $now = new \DateTime();
                $diff = $now->diff($stats->getDateModified());
                $hours = $diff->h + ($diff->days * 24);

                if ($hours < 24) {
                    echo 'Stats modifiées il y a moins de 24h - on passe au suivant<br>';
                    continue;
                }
            }
            if (!is_numeric($insee_code) || $insee_code == 'undefined' || $insee_code == '') {
                echo 'Code INSEE invalide : ' . $insee_code . ' - on passe au suivant<br>';
                continue;
            }

            $places_overpass = $this->motocultrice->labourer($stats->getZone());
            $places = $places_overpass;
            foreach ($places as $placeData) {


                // Vérifier si le lieu existe déjà
                $existingPlace = $this->entityManager->getRepository(Place::class)
                    ->findOneBy(['osmId' => $placeData['id']]);

                if (!$existingPlace) {
                    $place = new Place();
                    $place->setOsmId($placeData['id'])
                        ->setOsmKind($placeData['type'])
                        ->setZipCode($insee_code)
                        ->setUuidForUrl($this->motocultrice->uuid_create())
                        ->setModifiedDate(new \DateTime())
                        ->setStats($stats)
                        ->setDead(false)
                        ->setOptedOut(false)
                        ->setMainTag($this->motocultrice->find_main_tag($placeData['tags']) ?? '')
                        ->setStreet($this->motocultrice->find_street($placeData['tags']) ?? '')
                        ->setHousenumber($this->motocultrice->find_housenumber($placeData['tags']) ?? '')
                        ->setSiret($this->motocultrice->find_siret($placeData['tags']) ?? '')
                        ->setAskedHumainsSupport(false)
                        ->setLastContactAttemptDate(null)
                        ->setPlaceCount(0)
                        //   ->setOsmData($placeData['modified'] ?? null)
                    ;

                    // Mettre à jour les données depuis Overpass
                    $place->update_place_from_overpass_data($placeData);

                    $this->entityManager->persist($place);
                    $stats->addPlace($place);
                    $processedCount++;

                    // Générer le contenu de l'email avec le template
                    $emailContent = $this->twig->render('admin/email_content.html.twig', ['place' => $place]);
                    $place->setEmailContent($emailContent);
                } elseif ($updateExisting) {
                    // Mettre à jour les données depuis Overpass uniquement si updateExisting est true
                    $existingPlace->update_place_from_overpass_data($placeData);
                    $stats->addPlace($existingPlace);
                    $this->entityManager->persist($existingPlace);
                    $updatedCount++;
                }
            }
            // mettre à jour les stats
            // Récupérer tous les commerces de la zone
            $commerces = $this->entityManager->getRepository(Place::class)->findBy(['zip_code' => $insee_code]);

            // Récupérer les stats existantes pour la zone
            $stats = $this->entityManager->getRepository(Stats::class)->findOneBy(['zone' => $insee_code]);
            if (!$stats) {
                $stats = new Stats();
                $stats->setZone($insee_code);
            }

            $urls = $stats->getAllCTCUrlsMap();

            $statsHistory = $this->entityManager->getRepository(StatsHistory::class)
                ->createQueryBuilder('sh')
                ->where('sh.stats = :stats')
                ->setParameter('stats', $stats)
                ->orderBy('sh.id', 'DESC')
                ->setMaxResults(365)
                ->getQuery()
                ->getResult();

            // Calculer les statistiques
            $calculatedStats = $this->motocultrice->calculateStats($commerces);

            // Mettre à jour les stats pour la zone donnée
            $stats->setPlacesCount($calculatedStats['places_count']);
            $stats->setAvecHoraires($calculatedStats['counters']['avec_horaires']);
            $stats->setAvecAdresse($calculatedStats['counters']['avec_adresse']);
            $stats->setAvecSite($calculatedStats['counters']['avec_site']);
            $stats->setAvecAccessibilite($calculatedStats['counters']['avec_accessibilite']);
            $stats->setAvecNote($calculatedStats['counters']['avec_note']);

            $stats->setCompletionPercent($calculatedStats['completion_percent']);

            // Associer les stats à chaque commerce
            foreach ($commerces as $commerce) {
                $commerce->setStats($stats);
                // Injection de l'emoji pour le template
                $mainTag = $commerce->getMainTag();
                $emoji = self::getTagEmoji($mainTag);
                $commerce->tagEmoji = $emoji;
                $this->entityManager->persist($commerce);
            }

            $stats->computeCompletionPercent();

            // Calculer les statistiques de fraîcheur des données OSM
            $timestamps = [];
            foreach ($stats->getPlaces() as $place) {
                if ($place->getOsmDataDate()) {
                    $timestamps[] = $place->getOsmDataDate()->getTimestamp();
                }
            }

            if (!empty($timestamps)) {
                // Date la plus ancienne (min)
                $minTimestamp = min($timestamps);
                $stats->setOsmDataDateMin(new \DateTime('@' . $minTimestamp));

                // Date la plus récente (max)
                $maxTimestamp = max($timestamps);
                $stats->setOsmDataDateMax(new \DateTime('@' . $maxTimestamp));

                // Date moyenne
                $avgTimestamp = array_sum($timestamps) / count($timestamps);
                $stats->setOsmDataDateAvg(new \DateTime('@' . (int)$avgTimestamp));
            }

            if ($stats->getDateCreated() == null) {
                $stats->setDateCreated(new \DateTime());
            }

            $stats->setDateModified(new \DateTime());

            // Créer un historique des statistiques
            $statsHistory = new StatsHistory();
            $statsHistory->setDate(new \DateTime())
                ->setStats($stats);

            // Compter les Places avec email et SIRET
            $placesWithEmail = 0;
            $placesWithSiret = 0;
            foreach ($stats->getPlaces() as $place) {
                if ($place->getEmail() && $place->getEmail() !== '') {
                    $placesWithEmail++;
                }
                if ($place->getSiret() && $place->getSiret() !== '') {
                    $placesWithSiret++;
                }
            }

            $statsHistory->setPlacesCount($stats->getPlaces()->count())
                ->setOpeningHoursCount($stats->getAvecHoraires())
                ->setAddressCount($stats->getAvecAdresse())
                ->setWebsiteCount($stats->getAvecSite())
                ->setSiretCount($placesWithSiret)
                ->setEmailsCount($placesWithEmail)
                ->setCompletionPercent($stats->getCompletionPercent())
                ->setStats($stats);

            $this->entityManager->persist($statsHistory);


            $this->entityManager->persist($stats);
            $this->entityManager->flush();

            // Générer les contenus d'email après le flush pour éviter les problèmes de mémoire
            $placesToUpdate = $this->entityManager->getRepository(Place::class)->findBy(['zip_code' => $insee_code]);
            foreach ($placesToUpdate as $place) {
                if (!$place->getEmailContent()) {
                    $emailContent = $this->twig->render('admin/email_content.html.twig', ['place' => $place]);
                    $place->setEmailContent($emailContent);
                    $this->entityManager->persist($place);
                }
            }
            $this->entityManager->flush();

            // Générer les suivis (followups) après la mise à jour des Places
            $this->followUpService->generateCityFollowUps($stats, $this->motocultrice, $this->entityManager);

            $message = 'Labourage terminé avec succès. ' . $processedCount . ' nouveaux lieux traités.';
            if ($updateExisting) {
                $message .= ' ' . $updatedCount . ' lieux existants mis à jour pour la zone ' . $stats->getName() . ' (' . $stats->getZone() . ').';
            }
            $this->addFlash('success', $message);
        }

        $this->entityManager->flush();

        $this->addFlash('success', 'Labourage des ' . count($stats_all) . ' zones terminé avec succès.');
        return $this->redirectToRoute('app_public_dashboard');
    }

    #[Route('/admin', name: 'app_admin')]
    public function index(): Response
    {
        return $this->render('admin/index.html.twig', [
            'controller_name' => 'AdminController',
        ]);
    }

    #[Route('/admin/stats/{insee_code}', name: 'app_admin_stats', requirements: ['insee_code' => '\\d+'])]
    public function stats(string $insee_code): Response
    {
        $stats = $this->entityManager->getRepository(Stats::class)->findOneBy(['zone' => $insee_code]);
        if (!$stats) {
            $this->addFlash('error', 'Aucune stats trouvée pour ce code INSEE.');
            return $this->redirectToRoute('app_admin');
        }
        $followups = $stats->getCityFollowUps();
        $refresh = false;
        if (!$followups->isEmpty()) {
            $latest = null;
            foreach ($followups as $fu) {
                if ($latest === null || $fu->getDate() > $latest->getDate()) {
                    $latest = $fu;
                }
            }
            if ($latest && $latest->getDate() < (new \DateTime('-1 day'))) {
                $refresh = true;
            }
        } else {
            $refresh = true;
        }
        if ($refresh) {
            $this->followUpService->generateCityFollowUps($stats, $this->motocultrice, $this->entityManager);
            $followups = $stats->getCityFollowUps();
        }
        $commerces = $stats->getPlaces();
        $this->actionLogger->log('stats_de_ville', ['insee_code' => $insee_code, 'nom' => $stats->getZone()]);
        // Récupérer tous les commerces de la zone
        // $commerces = $this->entityManager->getRepository(Place::class)->findBy(['zip_code' => $insee_code, 'dead' => false]);
        if (!$stats) {
            // Si aucune stat n'existe, on en crée une vide pour éviter les erreurs, mais sans la sauvegarder
            $stats = new Stats();
            $stats->setZone($insee_code);
            $stats->setName('Nouvelle zone non labourée');
        }

        $urls = $stats->getAllCTCUrlsMap();
        $statsHistory = $this->entityManager->getRepository(StatsHistory::class)
            ->createQueryBuilder('sh')
            ->where('sh.stats = :stats')
            ->setParameter('stats', $stats)
            ->orderBy('sh.id', 'DESC')
            ->setMaxResults(100)
            ->getQuery()
            ->getResult();

        // Données pour le graphique des modifications par trimestre
        $modificationsByQuarter = [];
        foreach ($commerces as $commerce) {
            if ($commerce->getOsmDataDate()) {
                $date = $commerce->getOsmDataDate();
                $year = $date->format('Y');
                $quarter = ceil($date->format('n') / 3);
                $key = $year . '-Q' . $quarter;
                if (!isset($modificationsByQuarter[$key])) {
                    $modificationsByQuarter[$key] = 0;
                }
                $modificationsByQuarter[$key]++;
            }
        }
        ksort($modificationsByQuarter); // Trier par clé (année-trimestre)

        $geojson = [
            'type' => 'FeatureCollection',
            'features' => []
        ];

        foreach ($commerces as $commerce) {
            if ($commerce->getLat() && $commerce->getLon()) {
                $geojson['features'][] = [
                    'type' => 'Feature',
                    'geometry' => [
                        'type' => 'Point',
                        'coordinates' => [$commerce->getLon(), $commerce->getLat()]
                    ],
                    'properties' => [
                        'id' => $commerce->getOsmId(),
                        'name' => $commerce->getName(),
                        'main_tag' => $commerce->getMainTag(),
                        'address' => $commerce->getStreet() . ' ' . $commerce->getHousenumber(),
                        'note' => $commerce->getNoteContent(),
                        'osm_url' => 'https://www.openstreetmap.org/' . $commerce->getOsmKind() . '/' . $commerce->getOsmId()
                    ]
                ];
            }
        }

        // Générer le podium local des contributeurs OSM pour cette ville
        $placeRepo = $this->entityManager->getRepository(\App\Entity\Place::class);
        $qb = $placeRepo->createQueryBuilder('p')
            ->select(
                'p.osm_user',
                'COUNT(p.id) as nb',
                'AVG((CASE WHEN p.has_opening_hours = true THEN 1 ELSE 0 END) +'
                    . ' (CASE WHEN p.has_address = true THEN 1 ELSE 0 END) +'
                    . ' (CASE WHEN p.has_website = true THEN 1 ELSE 0 END) +'
                    . ' (CASE WHEN p.has_wheelchair = true THEN 1 ELSE 0 END) +'
                    . ' (CASE WHEN p.has_note = true THEN 1 ELSE 0 END)) / 5 * 100 as completion_moyen'
            )
            ->where('p.osm_user IS NOT NULL')
            ->andWhere("p.osm_user != ''")
            ->andWhere('p.stats = :stats')
            ->setParameter('stats', $stats)
            ->groupBy('p.osm_user')
            ->orderBy('nb', 'DESC')
            ->setMaxResults(100);
        $podium_local = $qb->getQuery()->getResult();
        // Calcul du score pondéré et normalisation locale
        $maxPondere = 0;
        foreach ($podium_local as &$row) {
            $row['completion_moyen'] = $row['completion_moyen'] !== null ? round($row['completion_moyen'], 1) : null;
            $row['completion_pondere'] = ($row['completion_moyen'] !== null && $row['nb'] > 0)
                ? round($row['completion_moyen'] * $row['nb'], 1)
                : null;
            if ($row['completion_pondere'] !== null && $row['completion_pondere'] > $maxPondere) {
                $maxPondere = $row['completion_pondere'];
            }
        }
        unset($row);

        // Normalisation des scores pondérés entre 0 et 100
        foreach ($podium_local as &$row) {
            if ($maxPondere > 0 && $row['completion_pondere'] !== null) {
                $row['completion_pondere_normalisee'] = round($row['completion_pondere'] / $maxPondere * 100, 1);
            } else {
                $row['completion_pondere_normalisee'] = null;
            }
        }
        unset($row);

        // Tri décroissant sur le score normalisé
        usort($podium_local, function ($a, $b) {
            return ($b['completion_pondere_normalisee'] ?? 0) <=> ($a['completion_pondere_normalisee'] ?? 0);
        });

        // Récupérer les derniers followups pour chaque type
        $latestFollowups = [];
        $types = array_keys(\App\Service\FollowUpService::getFollowUpThemes());
        foreach ($types as $type) {
            $count = null;
            $completion = null;
            foreach ($stats->getCityFollowUps() as $fu) {
                if ($fu->getName() === $type . '_count') {
                    if ($count === null) {
                        $count = $fu;
                    } else if ($fu->getDate() > $count->getDate()) {
                        $count = $fu;
                    }
                }
                if ($fu->getName() === $type . '_completion') {
                    if ($completion === null) {
                        $completion = $fu;
                    } else if ($fu->getDate() > $completion->getDate()) {
                        $completion = $fu;
                    }
                }
            }
            $latestFollowups[$type] = [];
            if ($count) $latestFollowups[$type]['count'] = $count;
            if ($completion) $latestFollowups[$type]['completion'] = $completion;
        }
        // Pour les lieux (places_count et places_completion)
        $count = null;
        $completion = null;
        foreach ($stats->getCityFollowUps() as $fu) {
            if ($fu->getName() === 'places_count') {
                if ($count === null) {
                    $count = $fu;
                } else if ($fu->getDate() > $count->getDate()) {
                    $count = $fu;
                }
            }
            if ($fu->getName() === 'places_completion') {
                if ($completion === null) {
                    $completion = $fu;
                } else if ($fu->getDate() > $completion->getDate()) {
                    $completion = $fu;
                }
            }
        }
        $latestFollowups['places'] = [];
        if ($count) $latestFollowups['places']['count'] = $count;
        if ($completion) $latestFollowups['places']['completion'] = $completion;

        // Calculer la progression sur 7 jours pour chaque type
        $progression7Days = [];
        foreach ($types as $type) {
            $progression7Days[$type] = \App\Service\FollowUpService::calculate7DayProgression($stats, $type);
        }
        $progression7Days['places'] = \App\Service\FollowUpService::calculate7DayProgression($stats, 'places');
 


        return $this->render('admin/stats.html.twig', [
            'stats' => $stats,
            'commerces' => $commerces,
            'urls' => $urls,
            'geojson' => json_encode($geojson),
            'modificationsByQuarter' => json_encode($modificationsByQuarter),
            'maptiler_token' => $_ENV['MAPTILER_TOKEN'],
            'statsHistory' => $statsHistory,
            'CTC_urls' => $urls,
            'overpass' => '',
            'podium_local' => $podium_local,
            'latestFollowups' => $latestFollowups,
            'followup_labels' => \App\Service\FollowUpService::getFollowUpThemes(),
            'followup_icons' => \App\Service\FollowUpService::getFollowUpIcons(),
            'progression7Days' => $progression7Days, 
            'all_types' => \App\Service\FollowUpService::getFollowUpThemes(),
            'getTagEmoji' => [self::class, 'getTagEmoji'],
            'completion_tags' => \App\Service\FollowUpService::getFollowUpCompletionTags(),
        ]);
    }

    #[Route('/admin/stats/{insee_code}/followup-graph/{theme}', name: 'admin_followup_theme_graph', requirements: ['insee_code' => '\d+'])]
    public function followupThemeGraph(string $insee_code, string $theme): Response
    {
        $stats = $this->entityManager->getRepository(Stats::class)->findOneBy(['zone' => $insee_code]);
        if (!$stats) {
            $this->addFlash('error', 'Aucune stats trouvée pour ce code INSEE.');
            return $this->redirectToRoute('app_admin');
        }

        $themes = \App\Service\FollowUpService::getFollowUpThemes();
        if (!isset($themes[$theme])) {
            $this->addFlash('error', 'Thème non reconnu.');
            return $this->redirectToRoute('app_admin_stats', ['insee_code' => $insee_code]);
        }

        // Récupérer toutes les données de followup pour ce thème
        $followups = $stats->getCityFollowUps();
        $countData = [];
        $completionData = [];

        foreach ($followups as $fu) {
            if ($fu->getName() === $theme . '_count') {
                $countData[] = [
                    'date' => $fu->getDate()->format('Y-m-d'),
                    'value' => $fu->getMeasure()
                ];
            }
            if ($fu->getName() === $theme . '_completion') {
                $completionData[] = [
                    'date' => $fu->getDate()->format('Y-m-d'),
                    'value' => $fu->getMeasure()
                ];
            }
        }

        // Trier par date
        usort($countData, fn($a, $b) => $a['date'] <=> $b['date']);
        usort($completionData, fn($a, $b) => $a['date'] <=> $b['date']);

        // Récupérer les objets du thème (Place) pour la ville
        $places = $this->entityManager->getRepository(Place::class)->findBy(['zip_code' => $insee_code]);
        $motocultrice = $this->motocultrice;
        $objects = [];
        // Récupérer la correspondance thème <-> requête Overpass
        $themeQueries = \App\Service\FollowUpService::getFollowUpOverpassQueries();
        $overpass_type_query = $themeQueries[$theme] ?? '';
        if ($overpass_type_query) {
            $overpass_query = "[out:json][timeout:60];\narea[\"ref:INSEE\"=\"$insee_code\"]->.searchArea;\n($overpass_type_query);\n(._;>;);\nout meta;\n>;";
            $josm_url = 'http://127.0.0.1:8111/import?url=https://overpass-api.de/api/interpreter?data=' . urlencode($overpass_query);
        } else {
            $josm_url = null;
        }
        // Fonction utilitaire pour extraire clé/valeur de la requête Overpass
        $extractTag = function ($query) {
            if (preg_match('/\\[([a-zA-Z0-9:_-]+)\\]="([^"]+)"/', $query, $matches)) {
                return [$matches[1], $matches[2]];
            }
            return [null, null];
        };
        list($tagKey, $tagValue) = $extractTag($themeQueries[$theme] ?? '');
        // DEBUG : journaliser les main_tag et le filtrage
        $all_main_tags = array_map(fn($p) => $p->getMainTag(), $places);
        $debug_info = [
            'theme' => $theme,
            'tagKey' => $tagKey,
            'tagValue' => $tagValue,
            'main_tags' => $all_main_tags,
            'places_count' => count($places),
        ];
        $debug_filtered = [];
        foreach ($places as $place) {
            $match = false;
            $main_tag = $place->getMainTag();
            // Cas particuliers multi-valeurs (ex: healthcare)
            if ($theme === 'healthcare') {
                if ($main_tag && (
                    str_starts_with($main_tag, 'healthcare=') ||
                    in_array($main_tag, [
                        'amenity=doctors',
                        'amenity=pharmacy',
                        'amenity=hospital',
                        'amenity=clinic',
                        'amenity=social_facility'
                    ])
                )) {
                    $match = true;
                }
            } else {
                // Détection générique : si le mainTag correspond à la clé/valeur du thème
                if ($tagKey && $tagValue && $main_tag === "$tagKey=$tagValue") {
                    $match = true;
                }
            }
            // Ajouter l'objet si match
            if ($match && $place->getLat() && $place->getLon()) {
                $objects[] = [
                    'id' => $place->getOsmId(),
                    'osm_kind' => $place->getOsmKind(),
                    'lat' => $place->getLat(),
                    'lon' => $place->getLon(),
                    'name' => $place->getName(),
                    'tags' => ['main_tag' => $place->getMainTag()],
                    'is_complete' => !empty($place->getName()),
                    'osm_url' => 'https://www.openstreetmap.org/' . $place->getOsmKind() . '/' . $place->getOsmId(),
                    'uuid' => $place->getUuidForUrl(),
                    'zip_code' => $place->getZipCode(),
                ];
                $debug_filtered[] = $main_tag;
            }
        }
        $debug_info['filtered_count'] = count($objects);
        $debug_info['filtered_main_tags'] = $debug_filtered;
        if (property_exists($this, 'actionLogger') && $this->actionLogger) {
            $this->actionLogger->log('[DEBUG][followupThemeGraph]', $debug_info);
        } else {
            error_log('[DEBUG][followupThemeGraph] ' . json_encode($debug_info));
        }
        $geojson = [
            'type' => 'FeatureCollection',
            'features' => array_map(function ($obj) {
                return [
                    'type' => 'Feature',
                    'geometry' => [
                        'type' => 'Point',
                        'coordinates' => [$obj['lon'], $obj['lat']]
                    ],
                    'properties' => $obj
                ];
            }, $objects)
        ];

        // Centre de la carte : centre géographique des objets ou de la ville
        $center = null;
        if (count($objects) > 0) {
            $lat = array_sum(array_column($objects, 'lat')) / count($objects);
            $lon = array_sum(array_column($objects, 'lon')) / count($objects);
            $center = [$lon, $lat];
        } elseif ($stats->getPlaces()->count() > 0) {
            $first = $stats->getPlaces()->first();
            $center = [$first->getLon(), $first->getLat()];
        }

        return $this->render('admin/followup_theme_graph.html.twig', [
            'stats' => $stats,
            'theme' => $theme,
            'theme_label' => $themes[$theme],
            'count_data' => json_encode($countData),
            'completion_data' => json_encode($completionData),
            'icons' => \App\Service\FollowUpService::getFollowUpIcons(),
            'geojson' => json_encode($geojson),
            'overpass_query' => $overpass_query,
            'josm_url' => $josm_url,
            'center' => $center,
            'maptiler_token' => $_ENV['MAPTILER_TOKEN'] ?? null,
            'completion_tags' => \App\Service\FollowUpService::getFollowUpCompletionTags(),
            'debug_info' => $debug_info,
        ]);
    }

    #[Route('/admin/placeType/{osm_kind}/{osm_id}', name: 'app_admin_by_osm_id')]
    public function placeType(string $osm_kind, string $osm_id): Response
    {

        $place = $this->entityManager->getRepository(Place::class)->findOneBy(['osm_kind' => $osm_kind, 'osmId' => $osm_id]);
        if ($place) {
            $this->actionLogger->log('admin/placeType', [
                'osm_kind' => $osm_kind,
                'osm_id' => $osm_id,
                'name' => $place->getName(),
                'code_insee' => $place->getZipCode(),
                'uuid' => $place->getUuidForUrl()
            ]);
            return $this->redirectToRoute('app_admin_commerce', ['id' => $place->getId()]);
        } else {
            $this->actionLogger->log('ERROR_admin/placeType', ['osm_kind' => $osm_kind, 'osm_id' => $osm_id]);
            $this->addFlash('error', 'Le lieu n\'existe pas.');
            $this->actionLogger->log('ERROR_admin/placeType', ['osm_kind' => $osm_kind, 'osm_id' => $osm_id]);
            return $this->redirectToRoute('app_public_index');
        }
    }



    /**
     * rediriger vers l'url unique quand on est admin
     */
    #[Route('/admin/commerce/{id}', name: 'app_admin_commerce')]
    public function commerce(int $id): Response
    {


        // Vérifier si on est en prod
        if ($this->getParameter('kernel.environment') === 'prod') {
            $this->addFlash('error', 'Vous n\'avez pas accès à cette page en production.');
            return $this->redirectToRoute('app_public_index');
        }
        $commerce = $this->entityManager->getRepository(Place::class)->find($id);

        if (!$commerce) {
            throw $this->createNotFoundException('Commerce non trouvé');
            $this->actionLogger->log('ERROR_admin_show_commerce_form_id', ['id' => $id]);
        }
        $this->actionLogger->log('admin_show_commerce_form_id', [
            'id' => $id,
            'name' => $commerce->getName(),
            'code_insee' => $commerce->getZipCode(),
            'uuid' => $commerce->getUuidForUrl()
        ]);
        // Redirection vers la page de modification avec les paramètres nécessaires
        return $this->redirectToRoute('app_public_edit', [
            'zipcode' => $commerce->getZipCode(),
            'name' => $commerce->getName() != '' ? $commerce->getName() : '?',
            'uuid' => $commerce->getUuidForUrl()
        ]);
    }


    /**
     * récupérer les commerces de la zone selon le code INSEE, créer les nouveaux lieux, et mettre à jour les existants
     */
    #[Route('/admin/labourer/{insee_code}', name: 'app_admin_labourer')]
    public function labourer(Request $request, string $insee_code, bool $updateExisting = true): Response
    {
        $deleteMissing = $request->query->getBoolean('deleteMissing', true);
        $disableFollowUpCleanup = $request->query->getBoolean('disableFollowUpCleanup', false);
        $debug = $request->query->getBoolean('debug', false);

        $this->actionLogger->log('labourer', ['insee_code' => $insee_code]);

        // Vérifier si le code INSEE est valide (composé uniquement de chiffres)
        if (!ctype_digit($insee_code) || $insee_code == 'undefined' || $insee_code == '') {
            $this->addFlash('error', 'Code INSEE invalide : il doit être composé uniquement de chiffres.');
            $this->actionLogger->log('ERROR_labourer_bad_insee', ['insee_code' => $insee_code]);
            return $this->redirectToRoute('app_public_index');
        }
        $city = null;
        $city_insee_found = null;
        $city_debug = null;
        try {
            // Récupérer ou créer les stats pour cette zone
            $stats = $this->entityManager->getRepository(Stats::class)->findOneBy(['zone' => $insee_code]);

            $city = $this->motocultrice->get_city_osm_from_zip_code($insee_code);
            // Si la fonction retourne un tableau ou un objet, on tente d'en extraire le code INSEE
            if (is_array($city) && isset($city['insee'])) {
                $city_insee_found = $city['insee'];
                $city_debug = $city;
                $city = $city['name'] ?? $city_insee_found;
            } elseif (is_object($city) && isset($city->insee)) {
                $city_insee_found = $city->insee;
                $city_debug = (array)$city;
                $city = $city->name ?? $city_insee_found;
            } else {
                $city_insee_found = $insee_code;
            }

            // Si le code INSEE trouvé ne correspond pas à celui demandé, afficher un message et stopper
            if ($city_insee_found !== $insee_code) {
                $msg = "Attention : le code INSEE trouvé (" . $city_insee_found . ") ne correspond pas à celui demandé (" . $insee_code . "). Aucune modification effectuée.";
                if ($debug) {
                    return $this->render('admin/labourage_debug.html.twig', [
                        'insee_code' => $insee_code,
                        'city_insee_found' => $city_insee_found,
                        'city_debug' => $city_debug,
                        'city_name' => $city,
                        'message' => $msg,
                        'stats' => $stats,
                    ]);
                }
                $this->addFlash('error', $msg);
                return $this->render('admin/labourage_debug.html.twig', [
                    'insee_code' => $insee_code,
                    'city_insee_found' => $city_insee_found,
                    'city_debug' => $city_debug,
                    'city_name' => $city,
                    'message' => $msg,
                    'stats' => $stats,
                ]);
            }

            if (!$stats) {
                $stats = new Stats();
                $stats->setDateCreated(new \DateTime());
                $stats->setDateModified(new \DateTime());
                $stats->setZone($insee_code)
                    ->setPlacesCount(0)
                    ->setAvecHoraires(0)
                    ->setAvecAdresse(0)
                    ->setAvecSite(0)
                    ->setAvecAccessibilite(0)
                    ->setAvecNote(0)
                    ->setCompletionPercent(0);
                $this->entityManager->persist($stats);
                $this->entityManager->flush();
            }
            $stats->setName($city);

            // Récupérer la population via l'API
            $population = null;
            try {
                $apiUrl = 'https://geo.api.gouv.fr/communes/' . $insee_code;
                $response = file_get_contents($apiUrl);
                if ($response !== false) {
                    $data = json_decode($response, true);
                    if (isset($data['population'])) {
                        $population = (int)$data['population'];
                        $stats->setPopulation($population);
                    }
                    if (isset($data['siren'])) {
                        $stats->setSiren((int)$data['siren']);
                    }
                    if (isset($data['codeEpci'])) {
                        $stats->setCodeEpci((int)$data['codeEpci']);
                    }
                    if (isset($data['codesPostaux'])) {
                        $stats->setCodesPostaux(implode(';', $data['codesPostaux']));
                    }
                }
            } catch (\Exception $e) {
                $this->addFlash('error', 'Erreur lors de la récupération des données de l\'API : ' . $e->getMessage());

                $this->actionLogger->log('ERROR_labourer_geoapi', ['insee_code' => $insee_code, 'message' => $e->getMessage()]);
            }

            // Récupérer le budget annuel via l'API des finances publiques
            try {
                $budgetAnnuel = $this->budgetService->getBudgetAnnuel($insee_code);
                if ($budgetAnnuel !== null) {
                    $stats->setBudgetAnnuel((string) $budgetAnnuel);
                }
            } catch (\Exception $e) {
                // Pas de message d'erreur pour le budget, c'est optionnel
            }

            // OPTIMISATION : Récupérer seulement les osmId et osmKind des lieux existants pour économiser la mémoire
            $existingPlacesQuery = $this->entityManager->getRepository(Place::class)
                ->createQueryBuilder('p')
                ->select('p.osmId, p.osm_kind, p.id')
                ->where('p.zip_code = :zip_code')
                ->setParameter('zip_code', $insee_code)
                ->getQuery();

            $existingPlacesResult = $existingPlacesQuery->getResult();
            $placesByOsmKey = [];

            foreach ($existingPlacesResult as $placeData) {

                // var_dump($placeData);
                // die(    );
                // Clé unique combinant osmId ET osmKind pour éviter les conflits entre node/way
                $osmKey = $placeData['osm_kind'] . '_' . $placeData['osmId'];
                $placesByOsmKey[$osmKey] = $placeData['id'];
            }

            // Récupérer toutes les données
            $places_overpass = $this->motocultrice->labourer($insee_code);
            $processedCount = 0;
            $updatedCount = 0;
            $deletedCount = 0;

            $overpass_osm_ids = array_map(fn($place) => $place['id'], $places_overpass);

            // RÉDUCTION de la taille du batch pour éviter l'explosion mémoire
            $batchSize = 10000;
            $i = 0;
            $notFoundOsmKeys = [];
            foreach ($places_overpass as $placeData) {
                // Vérifier si le lieu existe déjà (optimisé) - utilise osmKind + osmId
                $osmKey = $placeData['type'] . '_' . $placeData['id'];
                $existingPlaceId = $placesByOsmKey[$osmKey] ?? null;
                if (!$existingPlaceId) {
                    $place = new Place();
                    $place->setOsmId($placeData['id'])
                        ->setOsmKind($placeData['type'])
                        ->setZipCode($insee_code)
                        ->setUuidForUrl($this->motocultrice->uuid_create())
                        ->setModifiedDate(new \DateTime())
                        ->setStats($stats)
                        ->setDead(false)
                        ->setOptedOut(false)
                        ->setMainTag($this->motocultrice->find_main_tag($placeData['tags']) ?? '')
                        ->setStreet($this->motocultrice->find_street($placeData['tags']) ?? '')
                        ->setHousenumber($this->motocultrice->find_housenumber($placeData['tags']) ?? '')
                        ->setSiret($this->motocultrice->find_siret($placeData['tags']) ?? '')
                        ->setAskedHumainsSupport(false)
                        ->setLastContactAttemptDate(null)
                        ->setPlaceCount(0)
                        //   ->setOsmData($placeData['modified'] ?? null)
                    ;
                    $place->update_place_from_overpass_data($placeData);
                    $this->entityManager->persist($place);
                    $stats->addPlace($place);
                    $processedCount++;

                    // Log des objets non trouvés
                    $notFoundOsmKeys[] = $osmKey;
                } elseif ($updateExisting) {
                    // Charger l'entité existante seulement si nécessaire
                    $existingPlace = $this->entityManager->getRepository(Place::class)->find($existingPlaceId);
                    if ($existingPlace) {
                        $existingPlace->setDead(false);
                        $existingPlace->update_place_from_overpass_data($placeData);
                        $stats->addPlace($existingPlace);
                        $this->entityManager->persist($existingPlace);
                        $updatedCount++;
                    }
                }
                $i++;

                // FLUSH/CLEAR plus fréquent pour éviter l'explosion mémoire
                if (($i % $batchSize) === 0) {
                    $this->entityManager->flush();
                    $this->entityManager->clear();

                    // Forcer le garbage collector
                    gc_collect_cycles();

                    // Recharger les stats après clear
                    $stats = $this->entityManager->getRepository(Stats::class)->findOneBy(['zone' => $insee_code]);
                }
            }

            $deleteMissing = 1;
            // SUPPRESSION des lieux non corrélés par leur osm kind et osm id avec les données overpass
            if ($deleteMissing) {
                // Créer un ensemble des clés osm présentes dans les données overpass
                $overpassOsmKeys = [];
                foreach ($places_overpass as $placeData) {
                    $osmKey = $placeData['type'] . '_' . $placeData['id'];
                    $overpassOsmKeys[$osmKey] = true;
                }

                // ÉLIMINER LES DOUBLONS dans les lieux existants avant suppression
                $uniquePlacesByOsmKey = [];
                $duplicatePlaceIds = [];

                foreach ($placesByOsmKey as $osmKey => $placeId) {
                    if (isset($uniquePlacesByOsmKey[$osmKey])) {
                        // Doublon détecté, garder le plus ancien (ID le plus petit)
                        if ($placeId < $uniquePlacesByOsmKey[$osmKey]) {
                            $duplicatePlaceIds[] = $uniquePlacesByOsmKey[$osmKey];
                            $uniquePlacesByOsmKey[$osmKey] = $placeId;
                        } else {
                            $duplicatePlaceIds[] = $placeId;
                        }
                    } else {
                        $uniquePlacesByOsmKey[$osmKey] = $placeId;
                    }
                }

                // Supprimer les doublons détectés
                if (!empty($duplicatePlaceIds)) {
                    $duplicateDeleteQuery = $this->entityManager->createQuery(
                        'DELETE FROM App\Entity\Place p WHERE p.id IN (:placeIds)'
                    );
                    $duplicateDeleteQuery->setParameter('placeIds', $duplicatePlaceIds);
                    $duplicateDeletedCount = $duplicateDeleteQuery->execute();
                }

                // Trouver les lieux existants uniques qui ne sont plus dans overpass
                $placesToDelete = [];
                foreach ($uniquePlacesByOsmKey as $osmKey => $placeId) {
                    if (!isset($overpassOsmKeys[$osmKey])) {
                        $placesToDelete[] = $placeId;
                    }
                }

                // Supprimer les lieux non trouvés dans overpass en une seule requête
                if (!empty($placesToDelete)) {
                    $deleteQuery = $this->entityManager->createQuery(
                        'DELETE FROM App\Entity\Place p WHERE p.id IN (:placeIds)'
                    );
                    $deleteQuery->setParameter('placeIds', $placesToDelete);
                    $deletedCount = $deleteQuery->execute();
                }
            }

            // Flush final
            $this->entityManager->flush();
            $this->entityManager->clear();

            // Générer les contenus d'email après le flush pour éviter les problèmes de mémoire
            $placesToUpdate = $this->entityManager->getRepository(Place::class)->findBy(['zip_code' => $insee_code]);
            foreach ($placesToUpdate as $place) {
                if (!$place->getEmailContent()) {
                    $emailContent = $this->twig->render('admin/email_content.html.twig', ['place' => $place]);
                    $place->setEmailContent($emailContent);
                    $this->entityManager->persist($place);
                }
            }
            $this->entityManager->flush();

            // NETTOYAGE D'UNICITÉ des Places après le clear pour éliminer les doublons persistants
            // Approche en deux étapes pour éviter l'erreur MySQL "target table for update in FROM clause"

            // Étape 1 : Identifier les doublons
            $duplicateIdsQuery = $this->entityManager->createQuery(
                'SELECT p.id FROM App\Entity\Place p 
                 WHERE p.id NOT IN (
                     SELECT MIN(p2.id) 
                     FROM App\Entity\Place p2 
                     GROUP BY p2.osmId, p2.osm_kind, p2.zip_code
                 )'
            );
            $duplicateIds = $duplicateIdsQuery->getResult();

            // Étape 2 : Supprimer les doublons identifiés
            if (!empty($duplicateIds)) {
                $duplicateIds = array_column($duplicateIds, 'id');
                $duplicateCleanupQuery = $this->entityManager->createQuery(
                    'DELETE App\Entity\Place p WHERE p.id IN (:duplicateIds)'
                );
                $duplicateCleanupQuery->setParameter('duplicateIds', $duplicateIds);
                $duplicateCleanupCount = $duplicateCleanupQuery->execute();
            } else {
                $duplicateCleanupCount = 0;
            }

            // Récupérer tous les commerces de la zone qui n'ont pas été supprimés
            $commerces = $this->entityManager->getRepository(Place::class)->findBy(['zip_code' => $insee_code]);

            // Récupérer les stats existantes pour la zone
            $stats = $this->entityManager->getRepository(Stats::class)->findOneBy(['zone' => $insee_code]);
            if (!$stats) {
                $stats = new Stats();
                $stats->setZone($insee_code);
            }

            $urls = $stats->getAllCTCUrlsMap();

            $statsHistory = $this->entityManager->getRepository(StatsHistory::class)
                ->createQueryBuilder('sh')
                ->where('sh.stats = :stats')
                ->setParameter('stats', $stats)
                ->orderBy('sh.id', 'DESC')
                ->setMaxResults(365)
                ->getQuery()
                ->getResult();

            // Calculer les statistiques
            $calculatedStats = $this->motocultrice->calculateStats($commerces);

            // Mettre à jour les stats pour la zone donnée
            $stats->setPlacesCount($calculatedStats['places_count']);
            $stats->setAvecHoraires($calculatedStats['counters']['avec_horaires']);
            $stats->setAvecAdresse($calculatedStats['counters']['avec_adresse']);
            $stats->setAvecSite($calculatedStats['counters']['avec_site']);
            $stats->setAvecAccessibilite($calculatedStats['counters']['avec_accessibilite']);
            $stats->setAvecNote($calculatedStats['counters']['avec_note']);
            $stats->setCompletionPercent($calculatedStats['completion_percent']);

            // Associer les stats à chaque commerce
            foreach ($commerces as $commerce) {
                $commerce->setStats($stats);
                // Injection de l'emoji pour le template
                $mainTag = $commerce->getMainTag();
                $emoji = self::getTagEmoji($mainTag);
                $commerce->tagEmoji = $emoji;
                $this->entityManager->persist($commerce);
            }

            $stats->computeCompletionPercent();

            // Calculer les statistiques de fraîcheur des données OSM
            $timestamps = [];
            foreach ($stats->getPlaces() as $place) {
                if ($place->getOsmDataDate()) {
                    $timestamps[] = $place->getOsmDataDate()->getTimestamp();
                }
            }

            if (!empty($timestamps)) {
                // Date la plus ancienne (min)
                $minTimestamp = min($timestamps);
                $stats->setOsmDataDateMin(new \DateTime('@' . $minTimestamp));

                // Date la plus récente (max)
                $maxTimestamp = max($timestamps);
                $stats->setOsmDataDateMax(new \DateTime('@' . $maxTimestamp));

                // Date moyenne
                $avgTimestamp = array_sum($timestamps) / count($timestamps);
                $stats->setOsmDataDateAvg(new \DateTime('@' . (int)$avgTimestamp));
            }

            if ($stats->getDateCreated() == null) {
                $stats->setDateCreated(new \DateTime());
            }

            $stats->setDateModified(new \DateTime());

            // Créer un historique des statistiques
            $statsHistory = new StatsHistory();
            $statsHistory->setDate(new \DateTime())
                ->setStats($stats);

            // Compter les Places avec email et SIRET
            $placesWithEmail = 0;
            $placesWithSiret = 0;
            $placesWithName = 0;

            foreach ($stats->getPlaces() as $place) {
                if ($place->getEmail() && $place->getEmail() !== '') {
                    $placesWithEmail++;
                }
                if ($place->getSiret() && $place->getSiret() !== '') {
                    $placesWithSiret++;
                }
                if ($place->getName() && $place->getName() !== '') {
                    $placesWithName++;
                }
            }

            $statsHistory->setPlacesCount($stats->getPlaces()->count())
                ->setOpeningHoursCount($stats->getAvecHoraires())
                ->setAddressCount($stats->getAvecAdresse())
                ->setWebsiteCount($stats->getAvecSite())
                ->setNamesCount($placesWithName)
                ->setSiretCount($placesWithSiret)
                ->setEmailsCount($placesWithEmail)
                ->setCompletionPercent($stats->getCompletionPercent())
                ->setStats($stats);

            $this->entityManager->persist($statsHistory);


            $this->entityManager->persist($stats);
            $this->entityManager->flush();

            // Générer les suivis (followups) après la mise à jour des Places
            $this->followUpService->generateCityFollowUps($stats, $this->motocultrice, $this->entityManager, $disableFollowUpCleanup);

            $message = 'Labourage terminé avec succès. ' . $processedCount . ' nouveaux lieux traités.';
            if ($updateExisting) {
                $message .= ' ' . $updatedCount . ' lieux existants mis à jour.';
            }
            if ($deletedCount > 0) {
                $message .= ' ' . $deletedCount . ' lieux ont été supprimés.';
            }
            $message .= ' Zone : ' . $stats->getName() . ' (' . $stats->getZone() . ').';
            $this->addFlash('success', $message);

            // Afficher le log des objets non trouvés à la fin
            // if (!empty($notFoundOsmKeys)) {
            //     $this->addFlash('info', count($notFoundOsmKeys).' objets OSM non trouvés lors du labourage.');
            // }
            // Rediriger dans tous les cas vers la page de stats de la ville
            return $this->redirectToRoute('app_admin_stats', ['insee_code' => $insee_code]);
        } catch (\Exception $e) {
            $this->addFlash('error', 'Erreur lors du labourage : ' . $e->getMessage());
            if ($debug) {
                return $this->render('admin/labourage_debug.html.twig', [
                    'insee_code' => $insee_code,
                    'city_insee_found' => $city_insee_found,
                    'city_debug' => $city_debug,
                    'city_name' => $city,
                    'message' => $e->getMessage(),
                    'stats' => $stats ?? null,
                ]);
            }
            return $this->redirectToRoute('app_admin_stats', ['insee_code' => $insee_code]);
        }
        // ... (fin normale du traitement, on peut ajouter un affichage debug si besoin)
        if ($debug) {
            return $this->render('admin/labourage_debug.html.twig', [
                'insee_code' => $insee_code,
                'city_insee_found' => $city_insee_found,
                'city_debug' => $city_debug,
                'city_name' => $city,
                'message' => null,
                'stats' => $stats,
            ]);
        }
        return $this->redirectToRoute('app_admin_stats', ['insee_code' => $insee_code]);
    }

    #[Route('/admin/delete/{id}', name: 'app_admin_delete')]
    public function delete(int $id): Response
    {
        $this->actionLogger->log('admin/delete_place', ['id' => $id]);
        $commerce = $this->entityManager->getRepository(Place::class)->find($id);
        if ($commerce) {
            $this->entityManager->remove($commerce);
            $this->entityManager->flush();

            $this->addFlash('success', 'Le lieu ' . $commerce->getName() . ' a été supprimé avec succès de OSM Mes commerces, mais pas dans OpenStreetMap.');
        } else {
            $this->addFlash('error', 'Le lieu n\'existe pas.');
        }

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

    #[Route('/admin/delete_by_zone/{insee_code}', name: 'app_admin_delete_by_zone')]
    public function delete_by_zone(string $insee_code): Response
    {
        $this->actionLogger->log('admin/delete_by_zone', ['insee_code' => $insee_code]);
        $stats = $this->entityManager->getRepository(Stats::class)->findOneBy(['zone' => $insee_code]);

        if (!$stats) {
            $this->addFlash('error', 'Aucune statistique trouvée pour la zone ' . $insee_code);
            return $this->redirectToRoute('app_public_dashboard');
        }

        try {
            // 1. Supprimer tous les StatsHistory associés
            foreach ($stats->getStatsHistories() as $history) {
                $this->entityManager->remove($history);
            }

            // 2. Supprimer tous les Places associées
            foreach ($stats->getPlaces() as $place) {
                $this->entityManager->remove($place);
            }

            // 3. Supprimer l'objet Stats lui-même
            $this->entityManager->remove($stats);

            // 4. Appliquer les changements à la base de données
            $this->entityManager->flush();

            $this->addFlash('success', 'La zone ' . $insee_code . ' et toutes les données associées ont été supprimées avec succès.');
        } catch (\Exception $e) {
            $this->addFlash('error', 'Une erreur est survenue lors de la suppression de la zone ' . $insee_code . ': ' . $e->getMessage());
        }

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


    #[Route('/admin/export', name: 'app_admin_export')]
    public function export(): Response
    {
        $this->actionLogger->log('export_all_places', []);
        $places = $this->entityManager->getRepository(Place::class)->findAll();

        $csvData = [];
        $csvData[] = [
            'Nom',
            'Email',
            'Code postal',
            'ID OSM',
            'Type OSM',
            'Date de modification',
            'Date dernier contact',
            'Note',
            'Désabonné',
            'Inactif',
            'Support humain demandé',
            'A des horaires',
            'A une adresse',
            'A un site web',
            'A accessibilité',
            'A une note'
        ];

        foreach ($places as $place) {
            $csvData[] = [
                $place->getName(),
                $place->getEmail(),
                $place->getZipCode(),
                $place->getOsmId(),
                $place->getOsmKind(),
                $place->getModifiedDate() ? $place->getModifiedDate()->format('Y-m-d H:i:s') : '',
                $place->getLastContactAttemptDate() ? $place->getLastContactAttemptDate()->format('Y-m-d H:i:s') : '',
                $place->getNote(),
                $place->isOptedOut() ? 'Oui' : 'Non',
                $place->isDead() ? 'Oui' : 'Non',
                $place->isAskedHumainsSupport() ? 'Oui' : 'Non',
                $place->hasOpeningHours() ? 'Oui' : 'Non',
                $place->hasAddress() ? 'Oui' : 'Non',
                $place->hasWebsite() ? 'Oui' : 'Non',
                $place->hasWheelchair() ? 'Oui' : 'Non',
                $place->hasNote() ? 'Oui' : 'Non'
            ];
        }

        $response = new Response();
        $response->headers->set('Content-Type', 'text/csv');
        $response->headers->set('Content-Disposition', 'attachment; filename="export_places.csv"');

        $handle = fopen('php://temp', 'r+');
        foreach ($csvData as $row) {
            fputcsv($handle, $row, ';');
        }
        rewind($handle);
        $response->setContent(stream_get_contents($handle));
        fclose($handle);



        return $response;
    }
    #[Route('/admin/export_csv/{insee_code}', name: 'app_admin_export_csv')]
    public function export_csv(string $insee_code): Response
    {
        $this->actionLogger->log('admin/export_csv', ['insee_code' => $insee_code]);

        $stats = $this->entityManager->getRepository(Stats::class)->findOneBy(['zone' => $insee_code]);
        $response = new Response($this->motocultrice->export($insee_code));
        $response->headers->set('Content-Type', 'text/csv');

        $slug_name = str_replace(' ', '-', $stats->getName());

        $this->actionLogger->log('export_csv', ['insee_code' => $insee_code, 'slug_name' => $slug_name]);

        $response->headers->set('Content-Disposition', 'attachment; filename="osm-commerces-export_' . $insee_code . '_' . $slug_name . '_' . date('Y-m-d_H-i-s') . '.csv"');

        return $response;
    }

    #[Route('/admin/make_email_for_place/{id}', name: 'app_admin_make_email_for_place')]
    public function make_email_for_place(Place $place): Response
    {
        $this->actionLogger->log('admin/make_email_for_place', ['insee_code' => $place->getId()]);

        return $this->render('admin/view_email_for_place.html.twig', ['place' => $place]);
    }

    #[Route('/admin/no_more_sollicitation_for_place/{id}', name: 'app_admin_no_more_sollicitation_for_place')]
    public function no_more_sollicitation_for_place(Place $place): Response
    {
        $this->actionLogger->log('no_more_sollicitation_for_place', ['place_id' => $place->getId()]);

        $place->setOptedOut(true);
        $this->entityManager->persist($place);
        $this->entityManager->flush();

        $this->addFlash('success', 'Votre lieu ' . $place->getName() . ' ne sera plus sollicité pour mettre à jour ses informations.');

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

    #[Route('/admin/send_email_to_place/{id}', name: 'app_admin_send_email_to_place')]
    public function send_email_to_place(Place $place, \Symfony\Component\Mailer\MailerInterface $mailer): Response
    {
        $this->actionLogger->log('send_email_to_place', ['place_id' => $place->getId()]);


        // Vérifier si le lieu est opted out
        if ($place->isOptedOut()) {
            $this->addFlash('error', 'Ce lieu a demandé à ne plus être sollicité pour mettre à jour ses informations.');
            $this->actionLogger->log('could_not_send_email_to_opted_out_place', ['place_id' => $place->getId()]);

            return $this->redirectToRoute('app_public_index');
        }
        // Vérifier si le lieu a déjà été contacté
        if ($place->getLastContactAttemptDate() !== null) {
            $this->addFlash('error', 'Ce lieu a déjà été contacté le ' . $place->getLastContactAttemptDate()->format('d/m/Y H:i:s'));
            return $this->redirectToRoute('app_public_index');
        }

        // Générer le contenu de l'email avec le template
        $emailContent = $this->renderView('admin/email_content.html.twig', [
            'place' => $place
        ]);

        // Envoyer l'email
        $email = (new \Symfony\Component\Mime\Email())
            ->from('contact@openstreetmap.fr')
            ->to('contact+send_email@cipherbliss.com')
            ->subject('Mise à jour des informations de votre établissement dans OpenStreetMap')
            ->html($emailContent);

        try {
            $mailer->send($email);
        } catch (\Throwable $e) {
            $this->actionLogger->log('ERROR_envoi_email', [
                'place_id' => $place->getId(),
                'message' => $e->getMessage(),
            ]);
            $this->addFlash('error', 'Erreur lors de l\'envoi de l\'email : ' . $e->getMessage());
            return $this->redirectToRoute('app_public_index');
        }

        // Mettre à jour la date de dernier contact
        $place->setLastContactAttemptDate(new \DateTime());
        $this->entityManager->persist($place);
        $this->entityManager->flush();

        $place->setLastContactAttemptDate(new \DateTime());

        $this->addFlash('success', 'Email envoyé avec succès à ' . $place->getName() . ' le ' . $place->getLastContactAttemptDate()->format('d/m/Y H:i:s'));
        return $this->redirectToRoute('app_public_index');
    }

    #[Route('/admin/fraicheur/histogramme', name: 'admin_fraicheur_histogramme')]
    public function showFraicheurHistogramme(): Response
    {
        $jsonPath = $this->getParameter('kernel.project_dir') . '/var/fraicheur_osm.json';
        if (!file_exists($jsonPath)) {
            // Générer le fichier si absent
            $this->calculateFraicheur();
        }
        return $this->render('admin/fraicheur_histogramme.html.twig');
    }

    #[Route('/admin/fraicheur/calculate', name: 'admin_fraicheur_calculate')]
    public function calculateFraicheur(): Response
    {
        // Ajout d'un log d'action avec le service ActionLogger
        $this->actionLogger->log('fraicheur/calculate', []);
        $filesystem = new Filesystem();
        $jsonPath = $this->getParameter('kernel.project_dir') . '/var/fraicheur_osm.json';
        $now = new \DateTime();
        // Si le fichier existe et a moins de 12h, on ne régénère pas
        if ($filesystem->exists($jsonPath)) {
            $fileMTime = filemtime($jsonPath);
            if ($fileMTime && ($now->getTimestamp() - $fileMTime) < 43200) { // 12h = 43200s
                return $this->redirectToRoute('admin_fraicheur_histogramme');
            }
        }
        $places = $this->entityManager->getRepository(Place::class)->findAll();
        $histogram = [];
        $total = 0;
        foreach ($places as $place) {
            $date = $place->getOsmDataDate();
            if ($date) {
                $key = $date->format('Y-m');
                if (!isset($histogram[$key])) {
                    $histogram[$key] = 0;
                }
                $histogram[$key]++;
                $total++;
            }
        }
        ksort($histogram);
        $data = [
            'generated_at' => $now->format('c'),
            'total' => $total,
            'histogram' => $histogram
        ];
        $filesystem->dumpFile($jsonPath, json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE));

        // --- Distribution villes selon lieux/habitants ---
        $distJsonPath = $this->getParameter('kernel.project_dir') . '/var/distribution_villes_lieux_par_habitant.json';
        // Toujours régénérer
        $statsRepo = $this->entityManager->getRepository(Stats::class);
        $allStats = $statsRepo->findAll();
        $histogram_lieux_par_habitant = [];
        $histogram_habitants_par_lieu = [];
        $totalVilles = 0;
        foreach ($allStats as $stat) {
            $places = $stat->getPlacesCount();
            $population = $stat->getPopulation();
            if ($places && $population && $population > 0) {
                // lieux par habitant (pas de 0.01)
                $ratio_lph = round($places / $population, 4);
                $bin_lph = round(floor($ratio_lph / 0.01) * 0.01, 2);
                if (!isset($histogram_lieux_par_habitant[$bin_lph])) $histogram_lieux_par_habitant[$bin_lph] = 0;
                $histogram_lieux_par_habitant[$bin_lph]++;
                // habitants par lieu (pas de 10)
                $ratio_hpl = ceil($population / $places);
                $bin_hpl = ceil($ratio_hpl / 10) * 10;
                if (!isset($histogram_habitants_par_lieu[$bin_hpl])) $histogram_habitants_par_lieu[$bin_hpl] = 0;
                $histogram_habitants_par_lieu[$bin_hpl]++;
                $totalVilles++;
            }
        }
        ksort($histogram_lieux_par_habitant);
        ksort($histogram_habitants_par_lieu);
        $distData = [
            'generated_at' => $now->format('c'),
            'total_villes' => $totalVilles,
            'histogram_001' => $histogram_lieux_par_habitant,
            'histogram_10' => $histogram_habitants_par_lieu
        ];
        $filesystem->dumpFile($distJsonPath, json_encode($distData, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE));

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

    #[Route('/admin/fraicheur/download', name: 'admin_fraicheur_download')]
    public function downloadFraicheur(): JsonResponse
    {
        $jsonPath = $this->getParameter('kernel.project_dir') . '/var/fraicheur_osm.json';
        if (!file_exists($jsonPath)) {
            return new JsonResponse(['error' => 'Fichier non généré'], 404);
        }
        $content = file_get_contents($jsonPath);
        $data = json_decode($content, true);
        return new JsonResponse($data);
    }

    #[Route('/admin/distribution_villes_lieux_par_habitant_download', name: 'admin_distribution_villes_lieux_par_habitant_download')]
    public function downloadDistributionVillesLieuxParHabitant(): JsonResponse
    {
        $jsonPath = $this->getParameter('kernel.project_dir') . '/var/distribution_villes_lieux_par_habitant.json';
        if (!file_exists($jsonPath)) {
            // Générer à la volée si absent
            $now = new \DateTime();
            $filesystem = new \Symfony\Component\Filesystem\Filesystem();
            $statsRepo = $this->entityManager->getRepository(\App\Entity\Stats::class);
            $allStats = $statsRepo->findAll();
            $distribution = [];
            $histogram = [];
            $totalVilles = 0;
            foreach ($allStats as $stat) {
                $places = $stat->getPlacesCount();
                $population = $stat->getPopulation();
                if ($places && $population && $population > 0) {
                    $ratio = round($places / $population, 4); // lieux par habitant
                    $bin = round(floor($ratio / 0.01) * 0.01, 2); // pas de 0.01
                    if (!isset($histogram[$bin])) $histogram[$bin] = 0;
                    $histogram[$bin]++;
                    $totalVilles++;
                }
            }
            ksort($histogram);
            $distData = [
                'generated_at' => $now->format('c'),
                'total_villes' => $totalVilles,
                'histogram_001' => $histogram
            ];
            $filesystem->dumpFile($jsonPath, json_encode($distData, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE));
        }
        $content = file_get_contents($jsonPath);
        $data = json_decode($content, true);
        return new JsonResponse($data);
    }

    #[Route('/admin/distribution_villes_lieux_par_habitant_villes', name: 'admin_distribution_villes_lieux_par_habitant_villes')]
    public function downloadDistributionVillesLieuxParHabitantVilles(): JsonResponse
    {
        $statsRepo = $this->entityManager->getRepository(\App\Entity\Stats::class);
        $allStats = $statsRepo->findAll();
        $villesByBin = [];
        foreach ($allStats as $stat) {
            $places = $stat->getPlacesCount();
            $population = $stat->getPopulation();
            $name = $stat->getName();
            if ($places && $population && $population > 0 && $name) {
                $ratio = round($places / $population, 4); // lieux par habitant
                $bin = round(floor($ratio / 0.01) * 0.01, 2); // pas de 0.01
                if (!isset($villesByBin[$bin])) $villesByBin[$bin] = [];
                $villesByBin[$bin][] = $name;
            }
        }
        ksort($villesByBin);
        return new JsonResponse(['villes_by_bin' => $villesByBin]);
    }

    #[Route('/admin/labourer-tous-les-budgets', name: 'app_admin_labourer_tous_les_budgets')]
    public function labourerTousLesBudgets(): Response
    {
        $statsRepo = $this->entityManager->getRepository(Stats::class);
        $query = $statsRepo->createQueryBuilder('s')->getQuery();
        $allStats = $query->toIterable();
        $budgetsMisAJour = 0;
        foreach ($allStats as $stat) {
            if (!$stat->getBudgetAnnuel() && $stat->getZone()) {
                $budget = $this->budgetService->getBudgetAnnuel($stat->getZone());
                if ($budget !== null) {
                    $stat->setBudgetAnnuel((string)$budget);
                    $this->entityManager->persist($stat);
                    $budgetsMisAJour++;
                    continue;
                }
            }
        }
        if ($budgetsMisAJour > 0) {
            $this->entityManager->flush();
        }
        $this->addFlash('success', $budgetsMisAJour . ' budgets mis à jour.');
        return $this->redirectToRoute('app_admin');
    }

    #[Route('/admin/podium-contributeurs-osm', name: 'app_admin_podium_contributeurs_osm')]
    public function podiumContributeursOsm(): Response
    {
        $this->actionLogger->log('admin/podium_contributeurs_osm', []);

        // Récupérer tous les lieux avec un utilisateur OSM
        $places = $this->entityManager->getRepository(Place::class)->findBy(['osm_user' => null], ['osm_user' => 'ASC']);
        $places = array_filter($places, fn($place) => $place->getOsmUser() !== null);

        // Compter les contributions par utilisateur
        $contributions = [];
        foreach ($places as $place) {
            $user = $place->getOsmUser();
            if ($user) {
                if (!isset($contributions[$user])) {
                    $contributions[$user] = 0;
                }
                $contributions[$user]++;
            }
        }

        // Trier par nombre de contributions décroissant
        arsort($contributions);

        // Prendre les 10 premiers
        $topContributors = array_slice($contributions, 0, 10, true);

        return $this->render('admin/podium_contributeurs_osm.html.twig', [
            'contributors' => $topContributors
        ]);
    }

    #[Route('/admin/import-stats', name: 'app_admin_import_stats', methods: ['GET', 'POST'])]
    public function importStats(Request $request): Response
    {
        $this->actionLogger->log('admin/import_stats', []);

        if ($request->isMethod('POST')) {
            $uploadedFile = $request->files->get('json_file');

            if (!$uploadedFile) {
                $this->addFlash('error', 'Aucun fichier JSON n\'a été fourni.');
                return $this->redirectToRoute('app_admin_import_stats');
            }

            // Vérifier le type de fichier
            if (
                $uploadedFile->getClientMimeType() !== 'application/json' &&
                $uploadedFile->getClientOriginalExtension() !== 'json'
            ) {
                $this->addFlash('error', 'Le fichier doit être au format JSON.');
                return $this->redirectToRoute('app_admin_import_stats');
            }

            try {
                // Lire le contenu du fichier
                $jsonContent = file_get_contents($uploadedFile->getPathname());
                $data = json_decode($jsonContent, true);

                if (json_last_error() !== JSON_ERROR_NONE) {
                    throw new \Exception('Erreur lors du décodage JSON: ' . json_last_error_msg());
                }

                if (!is_array($data)) {
                    throw new \Exception('Le fichier JSON doit contenir un tableau d\'objets Stats.');
                }

                $createdCount = 0;
                $skippedCount = 0;
                $errors = [];

                foreach ($data as $index => $statData) {
                    try {
                        // Vérifier que les champs requis sont présents
                        if (!isset($statData['zone']) || !isset($statData['name'])) {
                            $errors[] = "Ligne " . ($index + 1) . ": Champs 'zone' et 'name' requis";
                            continue;
                        }

                        $zone = $statData['zone'];
                        $name = $statData['name'];

                        // Vérifier si l'objet Stats existe déjà
                        $existingStats = $this->entityManager->getRepository(Stats::class)->findOneBy(['zone' => $zone]);

                        if ($existingStats) {
                            $skippedCount++;
                            continue; // Ignorer les objets existants
                        }

                        // Créer un nouvel objet Stats
                        $stats = new Stats();
                        $stats->setZone($zone)
                            ->setName($name)
                            ->setDateCreated(new \DateTime())
                            ->setDateModified(new \DateTime());

                        // Remplir les champs optionnels
                        if (isset($statData['population'])) {
                            $stats->setPopulation($statData['population']);
                        }
                        if (isset($statData['budgetAnnuel'])) {
                            $stats->setBudgetAnnuel($statData['budgetAnnuel']);
                        }
                        if (isset($statData['siren'])) {
                            $stats->setSiren($statData['siren']);
                        }
                        if (isset($statData['codeEpci'])) {
                            $stats->setCodeEpci($statData['codeEpci']);
                        }
                        if (isset($statData['codesPostaux'])) {
                            $stats->setCodesPostaux($statData['codesPostaux']);
                        }

                        // Remplir les décomptes si disponibles
                        if (isset($statData['decomptes'])) {
                            $decomptes = $statData['decomptes'];
                            if (isset($decomptes['placesCount'])) {
                                $stats->setPlacesCount($decomptes['placesCount']);
                            }
                            if (isset($decomptes['avecHoraires'])) {
                                $stats->setAvecHoraires($decomptes['avecHoraires']);
                            }
                            if (isset($decomptes['avecAdresse'])) {
                                $stats->setAvecAdresse($decomptes['avecAdresse']);
                            }
                            if (isset($decomptes['avecSite'])) {
                                $stats->setAvecSite($decomptes['avecSite']);
                            }
                            if (isset($decomptes['avecAccessibilite'])) {
                                $stats->setAvecAccessibilite($decomptes['avecAccessibilite']);
                            }
                            if (isset($decomptes['avecNote'])) {
                                $stats->setAvecNote($decomptes['avecNote']);
                            }
                            if (isset($decomptes['completionPercent'])) {
                                $stats->setCompletionPercent($decomptes['completionPercent']);
                            }
                        }

                        $this->entityManager->persist($stats);
                        $createdCount++;
                    } catch (\Exception $e) {
                        $errors[] = "Ligne " . ($index + 1) . ": " . $e->getMessage();
                    }
                }

                // Sauvegarder les changements
                $this->entityManager->flush();

                // Afficher les résultats
                $message = "Import terminé : $createdCount objet(s) créé(s), $skippedCount objet(s) ignoré(s) (déjà existants).";
                if (!empty($errors)) {
                    $message .= " Erreurs : " . count($errors);
                    foreach ($errors as $error) {
                        $this->addFlash('warning', $error);
                    }
                }

                $this->addFlash('success', $message);
                $this->actionLogger->log('admin/import_stats_success', [
                    'created' => $createdCount,
                    'skipped' => $skippedCount,
                    'errors' => count($errors)
                ]);
            } catch (\Exception $e) {
                $this->addFlash('error', 'Erreur lors de l\'import : ' . $e->getMessage());
                $this->actionLogger->log('admin/import_stats_error', ['error' => $e->getMessage()]);
            }

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

        return $this->render('admin/import_stats.html.twig');
    }

    #[Route('/admin/export-overpass-csv/{insee_code}', name: 'app_admin_export_overpass_csv')]
    public function exportOverpassCsv($insee_code): Response
    {
        $stats = $this->entityManager->getRepository(Stats::class)->findOneBy(['zone' => $insee_code]);

        if (!$stats) {
            throw $this->createNotFoundException('Stats non trouvées pour ce code INSEE');
        }

        // Construire la requête Overpass
        $overpassQuery = '[out:csv(::id,::type,name,amenity,shop,office,craft,leisure,healthcare,emergency,man_made,power,highway,railway,public_transport,landuse,historic,barrier,tourism,sport,place,waterway,natural,geological,route,military,traffic_sign,traffic_calming,seamark,route_master,water,airway,aerialway,building,other;true;false;false)]' . "\n";
        $overpassQuery .= 'area["ref:INSEE"="' . $insee_code . '"]->.searchArea;' . "\n";
        $overpassQuery .= 'nwr["amenity"]["name"](area.searchArea);' . "\n";
        $overpassQuery .= 'nwr["shop"]["name"](area.searchArea);' . "\n";
        $overpassQuery .= 'nwr["office"]["name"](area.searchArea);' . "\n";
        $overpassQuery .= 'nwr["craft"]["name"](area.searchArea);' . "\n";
        $overpassQuery .= 'nwr["leisure"]["name"](area.searchArea);' . "\n";
        $overpassQuery .= 'nwr["healthcare"]["name"](area.searchArea);' . "\n";
        $overpassQuery .= 'nwr["emergency"]["name"](area.searchArea);' . "\n";
        $overpassQuery .= 'nwr["man_made"]["name"](area.searchArea);' . "\n";
        $overpassQuery .= 'nwr["power"]["name"](area.searchArea);' . "\n";
        $overpassQuery .= 'nwr["highway"]["name"](area.searchArea);' . "\n";
        $overpassQuery .= 'nwr["railway"]["name"](area.searchArea);' . "\n";
        $overpassQuery .= 'nwr["public_transport"]["name"](area.searchArea);' . "\n";
        $overpassQuery .= 'nwr["landuse"]["name"](area.searchArea);' . "\n";
        $overpassQuery .= 'nwr["historic"]["name"](area.searchArea);' . "\n";
        $overpassQuery .= 'nwr["barrier"]["name"](area.searchArea);' . "\n";
        $overpassQuery .= 'nwr["tourism"]["name"](area.searchArea);' . "\n";
        $overpassQuery .= 'nwr["sport"]["name"](area.searchArea);' . "\n";
        $overpassQuery .= 'nwr["place"]["name"](area.searchArea);' . "\n";
        $overpassQuery .= 'nwr["waterway"]["name"](area.searchArea);' . "\n";
        $overpassQuery .= 'nwr["natural"]["name"](area.searchArea);' . "\n";
        $overpassQuery .= 'nwr["geological"]["name"](area.searchArea);' . "\n";
        $overpassQuery .= 'nwr["route"]["name"](area.searchArea);' . "\n";
        $overpassQuery .= 'nwr["military"]["name"](area.searchArea);' . "\n";
        $overpassQuery .= 'nwr["traffic_sign"]["name"](area.searchArea);' . "\n";
        $overpassQuery .= 'nwr["traffic_calming"]["name"](area.searchArea);' . "\n";
        $overpassQuery .= 'nwr["seamark"]["name"](area.searchArea);' . "\n";
        $overpassQuery .= 'nwr["route_master"]["name"](area.searchArea);' . "\n";
        $overpassQuery .= 'nwr["water"]["name"](area.searchArea);' . "\n";
        $overpassQuery .= 'nwr["airway"]["name"](area.searchArea);' . "\n";
        $overpassQuery .= 'nwr["aerialway"]["name"](area.searchArea);' . "\n";
        $overpassQuery .= 'nwr["building"]["name"](area.searchArea);' . "\n";
        $overpassQuery .= 'nwr["other"]["name"](area.searchArea);' . "\n";

        $url = 'https://overpass-api.de/api/interpreter?data=' . urlencode($overpassQuery);

        // Rediriger vers l'API Overpass
        return $this->redirect($url);
    }

    #[Route('/admin/export-table-csv/{insee_code}', name: 'app_admin_export_table_csv')]
    public function exportTableCsv($insee_code): Response
    {
        $stats = $this->entityManager->getRepository(Stats::class)->findOneBy(['zone' => $insee_code]);

        if (!$stats) {
            throw $this->createNotFoundException('Stats non trouvées pour ce code INSEE');
        }

        $response = new Response();
        $response->headers->set('Content-Type', 'text/csv; charset=utf-8');
        $response->headers->set('Content-Disposition', 'attachment; filename="lieux_' . $insee_code . '_' . date('Y-m-d') . '.csv"');

        $output = fopen('php://temp', 'r+');

        // En-têtes CSV
        fputcsv($output, [
            'Nom',
            'Email',
            'Contenu email',
            'Completion %',
            'Type',
            'Adresse',
            'Numéro',
            'Rue',
            'Site web',
            'Accès PMR',
            'Note',
            'Texte de la note',
            'SIRET',
            'SIRET clos',
            'Dernière modif. OSM',
            'Utilisateur OSM',
            'Lien OSM'
        ], ';');

        // Données
        foreach ($stats->getPlaces() as $place) {
            $osmKind = $place->getOsmKind();
            $osmId = $place->getOsmId();
            $osmLink = ($osmKind && $osmId) ? 'https://www.openstreetmap.org/' . $osmKind . '/' . $osmId : '';

            // Construire l'adresse complète
            $address = '';
            if ($place->getHousenumber() && $place->getStreet()) {
                $address = $place->getHousenumber() . ' ' . $place->getStreet();
            } elseif ($place->getStreet()) {
                $address = $place->getStreet();
            }

            fputcsv($output, [
                $place->getName() ?: '(sans nom)',
                $place->getEmail() ?: '',
                $place->getEmailContent() ?: '',
                $place->getCompletionPercentage(),
                $place->getMainTag() ?: '',
                $address,
                $place->getHousenumber() ?: '',
                $place->getStreet() ?: '',
                $place->hasWebsite() ? 'Oui' : 'Non',
                $place->hasWheelchair() ? 'Oui' : 'Non',
                $place->getNote() ? 'Oui' : 'Non',
                $place->getNoteContent() ?: '',
                $place->getSiret() ?: '',
                '', // SIRET clos - à implémenter si nécessaire
                $place->getOsmDataDate() ? $place->getOsmDataDate()->format('Y-m-d H:i') : '',
                $place->getOsmUser() ?: '',
                $osmLink
            ], ';');
        }

        rewind($output);
        $csv = stream_get_contents($output);
        fclose($output);

        $response->setContent($csv);
        return $response;
    }

    #[Route('/admin/test-ctc/{insee_code}', name: 'admin_test_ctc', requirements: ['insee_code' => '\d+'], defaults: ['insee_code' => null])]
    public function testCTC(Request $request, ?string $insee_code = null): Response
    {
        $json = null;
        $url = null;
        $error = null;
        $stats = null;
        if ($insee_code) {
            $stats = $this->entityManager->getRepository(\App\Entity\Stats::class)->findOneBy(['zone' => $insee_code]);
            if ($stats) {
                $url = $stats->getCTCurlBase();
                try {
                    $json = file_get_contents($url . '_last_stats.json');
                } catch (\Exception $e) {
                    $error = $e->getMessage();
                }
            } else {
                $error = "Aucune stats trouvée pour ce code INSEE.";
            }
        }
        return $this->render('admin/test_ctc.html.twig', [
            'insee_code' => $insee_code,
            'url' => $url ? $url . '_last_stats.json' : null,
            'json' => $json,
            'error' => $error,
            'stats' => $stats
        ]);
    }

    #[Route('/admin/export_csv', name: 'app_admin_export_csv_all')]
    public function export_csv_all(): Response
    {
        $statsList = $this->entityManager->getRepository(\App\Entity\Stats::class)->findAll();
        $handle = fopen('php://temp', 'r+');
        // En-tête CSV
        fputcsv($handle, [
            'zone', 'name', 'lat', 'lon', 'population', 'budgetAnnuel', 'completionPercent', 'placesCount', 'avecHoraires', 'avecAdresse', 'avecSite', 'avecAccessibilite', 'avecNote', 'siren', 'codeEpci', 'codesPostaux'
        ]);
        foreach ($statsList as $stat) {
            fputcsv($handle, [
                $stat->getZone(),
                $stat->getName(),
                $stat->getLat(),
                $stat->getLon(),
                $stat->getPopulation(),
                $stat->getBudgetAnnuel(),
                $stat->getCompletionPercent(),
                $stat->getPlacesCount(),
                $stat->getAvecHoraires(),
                $stat->getAvecAdresse(),
                $stat->getAvecSite(),
                $stat->getAvecAccessibilite(),
                $stat->getAvecNote(),
                $stat->getSiren(),
                $stat->getCodeEpci(),
                $stat->getCodesPostaux(),
            ]);
        }
        rewind($handle);
        $csv = stream_get_contents($handle);
        fclose($handle);
        $response = new Response($csv);
        $response->headers->set('Content-Type', 'text/csv');
        $response->headers->set('Content-Disposition', 'attachment; filename="osm-commerces-villes-export_' . date('Y-m-d_H-i-s') . '.csv"');
        return $response;
    }

    public static function getTagEmoji(string $mainTag): string
    {
        // Si c'est un tag clé=valeur, on garde le match existant
        if (str_contains($mainTag, '=')) {
            return match ($mainTag) {
                'amenity=restaurant', 'amenity=bar', 'amenity=cafe' => '🍽️',
                'amenity=townhall', 'amenity=community_centre' => '🏛️',
                'amenity=bank', 'amenity=atm' => '🏦',
                'amenity=pharmacy', 'amenity=hospital', 'amenity=clinic' => '🏥',
                'amenity=school', 'amenity=kindergarten', 'amenity=university' => '🎓',
                'amenity=library', 'amenity=museum', 'amenity=artwork' => '📚',
                'shop=car_repair', 'shop=car_parts', 'shop=car_wash' => '🚗',
                'amenity=post_office' => '📮',
                'shop=convenience' => '🏪',
                'shop=supermarket' => '🛒',
                'shop=clothes' => '👕',
                default => '🏷️',
            };
        }
        // Sinon, on regarde si c'est un tag principal simple
        return match ($mainTag) {
            'bicycle_parking' => '🚲',
            'building' => '🏢',
            'email' => '📧',
            'fire_hydrant' => '🚒',
            'charging_station' => '⚡',
            'toilets' => '🚻',
            'bus_stop' => '🚌',
            'defibrillator' => '❤️‍🩹',
            'camera' => '📷',
            'recycling' => '♻️',
            'substation' => '🏭',
            'laboratory' => '🧪',
            'school' => '🏫',
            'police' => '👮',
            'healthcare' => '🏥',
            'advertising_board' => '🪧',
            'bench' => '🪑',
            'waste_basket' => '🗑️',
            'street_lamp' => '💡',
            'drinking_water' => '🚰',
            'tree' => '🌳',
            'places' => '📍',
            'power_pole' => '⚡',
            default => '🏷️',
        };
    }

    public function followupEmbedGraph(Request $request, string $insee_code, string $theme): Response
    {
        $stats = $this->entityManager->getRepository(Stats::class)->findOneBy(['zone' => $insee_code]);
        if (!$stats) {
            $this->addFlash('error', 'Aucune stats trouvée pour ce code INSEE.');
            return $this->redirectToRoute('app_admin');
        }

        $themes = \App\Service\FollowUpService::getFollowUpThemes();
        if (!isset($themes[$theme])) {
            $this->addFlash('error', 'Thème non reconnu.');
            return $this->redirectToRoute('app_admin_stats', ['insee_code' => $insee_code]);
        }

        // Récupérer toutes les données de followup pour ce thème
        $followups = $stats->getCityFollowUps();
        $countData = [];
        $completionData = [];

        foreach ($followups as $fu) {
            if ($fu->getName() === $theme . '_count') {
                $countData[] = [
                    'date' => $fu->getDate()->format('Y-m-d'),
                    'value' => $fu->getMeasure()
                ];
            }
            if ($fu->getName() === $theme . '_completion') {
                $completionData[] = [
                    'date' => $fu->getDate()->format('Y-m-d'),
                    'value' => $fu->getMeasure()
                ];
            }
        }

        // Trier par date
        usort($countData, fn($a, $b) => $a['date'] <=> $b['date']);
        usort($completionData, fn($a, $b) => $a['date'] <=> $b['date']);

        // Récupérer les objets du thème (Place) pour la ville
        $places = $this->entityManager->getRepository(Place::class)->findBy(['zip_code' => $insee_code]);
        $motocultrice = $this->motocultrice;
        $objects = [];
        // Récupérer la correspondance thème <-> requête Overpass
        $themeQueries = \App\Service\FollowUpService::getFollowUpOverpassQueries();
        $overpass_type_query = $themeQueries[$theme] ?? '';
        if ($overpass_type_query) {
            $overpass_query = "[out:json][timeout:60];\narea[\"ref:INSEE\"=\"$insee_code\"]->.searchArea;\n($overpass_type_query);\n(._;>;);\nout meta;\n>;";
            $josm_url = 'http://127.0.0.1:8111/import?url=https://overpass-api.de/api/interpreter?data=' . urlencode($overpass_query);
        } else {
            $josm_url = null;
        }
        // Fonction utilitaire pour extraire clé/valeur de la requête Overpass
        $extractTag = function ($query) {
            if (preg_match('/\\[([a-zA-Z0-9:_-]+)\\]="([^"]+)"/', $query, $matches)) {
                return [$matches[1], $matches[2]];
            }
            return [null, null];
        };
        list($tagKey, $tagValue) = $extractTag($themeQueries[$theme] ?? '');
        foreach ($places as $place) {
            $match = false;
            $main_tag = $place->getMainTag();
            // Cas particuliers multi-valeurs (ex: healthcare)
            if ($theme === 'healthcare') {
                if ($main_tag && (
                    str_starts_with($main_tag, 'healthcare=') ||
                    in_array($main_tag, [
                        'amenity=doctors',
                        'amenity=pharmacy',
                        'amenity=hospital',
                        'amenity=clinic',
                        'amenity=social_facility'
                    ])
                )) {
                    $match = true;
                }
            } else {
                // Détection générique : si le mainTag correspond à la clé/valeur du thème
                if ($tagKey && $tagValue && $main_tag === "$tagKey=$tagValue") {
                    $match = true;
                }
            }
            // Ajouter l'objet si match
            if ($match && $place->getLat() && $place->getLon()) {
                $objects[] = [
                    'id' => $place->getOsmId(),
                    'osm_kind' => $place->getOsmKind(),
                    'lat' => $place->getLat(),
                    'lon' => $place->getLon(),
                    'name' => $place->getName(),
                    'tags' => ['main_tag' => $place->getMainTag()],
                    'is_complete' => !empty($place->getName()),
                    'osm_url' => 'https://www.openstreetmap.org/' . $place->getOsmKind() . '/' . $place->getOsmId(),
                    'uuid' => $place->getUuidForUrl(),
                    'zip_code' => $place->getZipCode(),
                ];
            }
        }
        $geojson = [
            'type' => 'FeatureCollection',
            'features' => array_map(function ($obj) {
                return [
                    'type' => 'Feature',
                    'geometry' => [
                        'type' => 'Point',
                        'coordinates' => [$obj['lon'], $obj['lat']]
                    ],
                    'properties' => $obj
                ];
            }, $objects)
        ];

        // Centre de la carte : centre géographique des objets ou de la ville
        $center = null;
        if (count($objects) > 0) {
            $lat = array_sum(array_column($objects, 'lat')) / count($objects);
            $lon = array_sum(array_column($objects, 'lon')) / count($objects);
            $center = [$lon, $lat];
        } elseif ($stats->getPlaces()->count() > 0) {
            $first = $stats->getPlaces()->first();
            $center = [$first->getLon(), $first->getLat()];
        }

        return $this->render('admin/followup_embed_graph.html.twig', [
            'stats' => $stats,
            'theme' => $theme,
            'theme_label' => $themes[$theme],
            'count_data' => json_encode($countData),
            'completion_data' => json_encode($completionData),
            'icons' => \App\Service\FollowUpService::getFollowUpIcons(),
            'geojson' => json_encode($geojson),
            'overpass_query' => $overpass_query,
            'josm_url' => $josm_url,
            'center' => $center,
            'maptiler_token' => $_ENV['MAPTILER_TOKEN'] ?? null,
            'completion_tags' => \App\Service\FollowUpService::getFollowUpCompletionTags(),
        ]);
    }

    #[Route('/admin/followup-graph/{insee_code}', name: 'admin_followup_graph', requirements: ['insee_code' => '\d+'])]
    public function followupGraph(Request $request, string $insee_code): Response
    {
        $stats = $this->entityManager->getRepository(Stats::class)->findOneBy(['zone' => $insee_code]);
        if (!$stats) {
            $this->addFlash('error', 'Aucune stats trouvée pour ce code INSEE.');
            return $this->redirectToRoute('app_admin');
        }
        $themes = \App\Service\FollowUpService::getFollowUpThemes();
        // On ne vérifie pas ici un thème unique, on boucle sur tous les thèmes plus loin

        return $this->render('admin/followup_graph.html.twig', [
            'stats' => $stats,
            'completion_tags' => \App\Service\FollowUpService::getFollowUpCompletionTags(),
            'followup_labels' => \App\Service\FollowUpService::getFollowUpThemes(),
            'followup_icons' => \App\Service\FollowUpService::getFollowUpIcons(),
            
        ]);
    }

    // Dans la méthode de suppression de ville (ex: deleteCity ou similaire)
    public function deleteCityAction(Request $request, $id): Response
    {
        if (!$this->allowDeleteCity) {
            $this->addFlash('danger', "La suppression de ville est désactivée par configuration.");
            return $this->redirectToRoute('admin_dashboard');
        }
        // ... logique de suppression existante ...
        // Pour éviter l'erreur, on retourne une redirection par défaut si rien n'est fait
        return $this->redirectToRoute('admin_dashboard');
    }

    #[Route('/admin/stats/{insee_code}/street-completion', name: 'admin_street_completion', requirements: ['insee_code' => '\d+'])]
    public function streetCompletion(string $insee_code): Response
    {
        $stats = $this->entityManager->getRepository(Stats::class)->findOneBy(['zone' => $insee_code]);
        if (!$stats) {
            $this->addFlash('error', 'Aucune stats trouvée pour ce code INSEE.');
            return $this->redirectToRoute('app_admin');
        }
        $places = $stats->getPlaces();
        $rues = [];
        foreach ($places as $place) {
            $rue = $place->getStreet() ?: '(sans nom)';
            if (!isset($rues[$rue])) {
                $rues[$rue] = [ 'places' => [], 'completion_sum' => 0 ];
            }
            $rues[$rue]['places'][] = $place;
            $rues[$rue]['completion_sum'] += $place->getCompletionPercentage();
        }
        $rues_data = [];
        foreach ($rues as $nom => $data) {
            $count = count($data['places']);
            $avg = $count > 0 ? round($data['completion_sum'] / $count, 1) : 0;
            $rues_data[] = [
                'name' => $nom,
                'count' => $count,
                'avg_completion' => $avg,
            ];
        }
        // Tri décroissant par complétion moyenne
        usort($rues_data, fn($a, $b) => $b['avg_completion'] <=> $a['avg_completion']);
        return $this->render('admin/street_completion.html.twig', [
            'stats' => $stats,
            'rues' => $rues_data,
            'insee_code' => $insee_code,
        ]);
    }
}
