# language: fr

@api @search @geolocation @mvp
Fonctionnalité: Recherche géographique de contenus avec Nominatim

  En tant qu'utilisateur de RoadWave
  Je veux rechercher des contenus audio dans une zone géographique spécifique
  En utilisant un nom de lieu (ville, monument, région) et un rayon de recherche
  Afin de découvrir des contenus avant de me déplacer ou planifier un trajet

  Contexte:
    Étant donné un utilisateur authentifié
    Et l'API Nominatim est accessible

  # ============================================================================
  # GEOCODAGE AVEC NOMINATIM (lieu → coordonnées GPS)
  # ============================================================================

  Scénario: Recherche par nom de ville (cas simple)
    Étant donné l'utilisateur saisit "Paris" dans la recherche géographique
    Quand le système interroge Nominatim avec la requête "Paris, France"
    Alors Nominatim doit retourner les coordonnées :
      | lat      | 48.8566 |
      | lon      | 2.3522  |
      | type     | city    |
      | bbox     | (48.815, 48.902, 2.224, 2.470) |
    Et le système doit utiliser ces coordonnées comme centre de recherche

  Scénario: Recherche par monument ou POI (Point of Interest)
    Étant donné l'utilisateur saisit "Tour Eiffel"
    Quand le système interroge Nominatim
    Alors Nominatim doit retourner :
      | lat         | 48.8584  |
      | lon         | 2.2945   |
      | display_name| Tour Eiffel, Paris, France |
      | type        | tourism  |
    Et ces coordonnées doivent être utilisées pour la recherche de contenus

  Scénario: Recherche avec ambiguïté (plusieurs résultats)
    Étant donné l'utilisateur saisit "Montmartre"
    Quand le système interroge Nominatim
    Alors plusieurs résultats doivent être retournés :
      | display_name                              | lat     | lon    |
      | Montmartre, Paris 18e, France             | 48.8867 | 2.3431 |
      | Montmartre, Saskatchewan, Canada          | 49.3000 | -106.0 |
      | Montmartre-de-Bretagne, Ille-et-Vilaine   | 48.1867 | -1.1833|
    Et l'utilisateur doit choisir dans une liste déroulante
    Et le choix par défaut doit être le résultat français si détecté

  Scénario: Recherche avec contexte géographique (biais local)
    Étant donné l'utilisateur est actuellement à Lyon (GPS actif)
    Et l'utilisateur saisit "Bellecour"
    Quand le système interroge Nominatim avec viewbox="Lyon area"
    Alors Nominatim doit prioriser les résultats proches de Lyon
    Et "Place Bellecour, Lyon" doit apparaître en premier
    Avant "Bellecour, Jura" ou d'autres homonymes

  Scénario: Recherche par code postal
    Étant donné l'utilisateur saisit "75001"
    Quand le système interroge Nominatim
    Alors Nominatim doit retourner le centre du 1er arrondissement de Paris
    Et un rayon par défaut de 1km doit être appliqué

  Scénario: Recherche invalide ou introuvable
    Étant donné l'utilisateur saisit "Azertyuiopqsdfghjklm"
    Quand le système interroge Nominatim
    Alors Nominatim doit retourner 0 résultat
    Et un message d'erreur doit être affiché :
      """
      Aucun lieu trouvé pour "Azertyuiopqsdfghjklm".
      Essayez un nom de ville, monument ou adresse.
      """

  # ============================================================================
  # RAYON DE RECHERCHE CONFIGURABLE
  # ============================================================================

  Scénario: Recherche avec rayon par défaut (5 km)
    Étant donné l'utilisateur cherche "Louvre, Paris"
    Et Nominatim retourne les coordonnées (48.8606, 2.3376)
    Et aucun rayon n'est spécifié
    Quand le système effectue la recherche de contenus
    Alors un rayon par défaut de 5 km doit être appliqué
    Et une requête PostGIS doit être exécutée :
      """
      SELECT * FROM contents
      WHERE ST_DWithin(location::geography, ST_MakePoint(2.3376, 48.8606)::geography, 5000)
      """

  Scénario: Recherche avec rayon personnalisé (slider 1-50 km)
    Étant donné l'utilisateur cherche "Lyon"
    Et l'utilisateur ajuste le slider de rayon à 15 km
    Quand le système effectue la recherche de contenus
    Alors une requête PostGIS avec rayon 15000m doit être exécutée
    Et tous les contenus dans un rayon de 15 km autour de Lyon doivent être retournés

  Scénario: Rayon minimum (1 km) - recherche ultra locale
    Étant donné l'utilisateur cherche "Place de la Concorde"
    Et l'utilisateur définit le rayon à 1 km (minimum)
    Quand le système effectue la recherche
    Alors seuls les contenus très proches (<1 km) doivent être retournés
    Et le nombre de résultats peut être très faible (0-10)

  Scénario: Rayon maximum (50 km) - recherche large
    Étant donné l'utilisateur cherche "Versailles"
    Et l'utilisateur définit le rayon à 50 km (maximum)
    Quand le système effectue la recherche
    Alors tous les contenus dans un rayon de 50 km doivent être retournés
    Et cela peut inclure Paris, banlieue et zones périphériques
    Et un avertissement "Résultats nombreux, affinez votre recherche" peut s'afficher si >500 résultats

  Scénario: Mise à jour temps réel du rayon avec slider
    Étant donné l'utilisateur visualise les résultats pour "Bordeaux" avec rayon 10 km
    Quand l'utilisateur ajuste le slider de 10 km à 20 km
    Alors une nouvelle requête API doit être déclenchée automatiquement
    Et les résultats doivent être mis à jour en temps réel
    Et la carte (si affichée) doit ajuster le cercle de rayon

  # ============================================================================
  # FILTRES COMBINÉS AVEC RECHERCHE GÉO
  # ============================================================================

  Scénario: Recherche géo + filtre catégorie
    Étant donné l'utilisateur cherche "Marseille" avec rayon 10 km
    Et l'utilisateur filtre par catégorie "Tourisme"
    Quand le système effectue la recherche
    Alors seuls les contenus touristiques dans le rayon doivent être retournés
    Et la requête SQL doit combiner :
      """
      WHERE ST_DWithin(...) AND category_id IN (SELECT id FROM categories WHERE name = 'Tourisme')
      """

  Scénario: Recherche géo + filtre type de contenu
    Étant donné l'utilisateur cherche "Strasbourg" avec rayon 5 km
    Et l'utilisateur filtre par type "Audio-guides"
    Quand le système effectue la recherche
    Alors seuls les audio-guides dans le rayon doivent être retournés
    Et les podcasts et radios live doivent être exclus

  Scénario: Recherche géo + filtre durée
    Étant donné l'utilisateur cherche "Nice" avec rayon 15 km
    Et l'utilisateur filtre par durée "Court (<10 min)"
    Quand le système effectue la recherche
    Alors seuls les contenus de <10 minutes dans le rayon doivent être retournés

  Scénario: Recherche géo + mode Kids actif
    Étant donné l'utilisateur cherche "Disneyland Paris" avec rayon 5 km
    Et le mode Kids est activé (utilisateur 13-15 ans)
    Quand le système effectue la recherche
    Alors seuls les contenus "Tout public" doivent être retournés
    Et les contenus 16+ et 18+ doivent être exclus automatiquement

  Scénario: Recherche géo + filtre politique désactivé
    Étant donné l'utilisateur cherche "Assemblée Nationale" avec rayon 2 km
    Et l'utilisateur a désactivé les contenus politiques dans ses préférences
    Quand le système effectue la recherche
    Alors les contenus taggés "politique" doivent être exclus
    Même s'ils sont géographiquement pertinents

  # ============================================================================
  # AFFICHAGE DES RÉSULTATS
  # ============================================================================

  Scénario: Résultats triés par distance croissante
    Étant donné l'utilisateur cherche "Arc de Triomphe" avec rayon 3 km
    Quand les résultats sont retournés
    Alors ils doivent être triés par distance croissante :
      | contenu                    | distance |
      | Balade sur les Champs      | 0.1 km   |
      | Histoire de l'Arc          | 0.2 km   |
      | Quartier de l'Étoile       | 0.5 km   |
      | Secrets du 16e             | 2.8 km   |
    Et la distance doit être affichée pour chaque résultat

  Scénario: Affichage carte avec marqueurs de contenus
    Étant donné l'utilisateur cherche "Toulouse" avec rayon 10 km
    Quand les résultats sont affichés en mode "Carte"
    Alors une carte Leaflet doit être affichée
    Et un marqueur doit être placé pour chaque contenu :
      | contenu              | lat     | lon    | icone           |
      | Capitole de Toulouse | 43.6045 | 1.4442 | pin-tourisme    |
      | Bords de Garonne     | 43.5986 | 1.4330 | pin-nature      |
    Et un cercle représentant le rayon de 10 km doit être affiché
    Et le centre doit être le point géocodé de Toulouse

  Scénario: Clustering de marqueurs si résultats nombreux
    Étant donné l'utilisateur cherche "Paris" avec rayon 20 km
    Et la recherche retourne 350 contenus
    Quand la carte est affichée
    Alors les marqueurs proches doivent être groupés en clusters :
      | cluster_center      | nb_contenus |
      | Centre Paris        | 120         |
      | La Défense          | 45          |
      | Bois de Vincennes   | 18          |
    Et en cliquant sur un cluster, le zoom doit s'approcher
    Et révéler les marqueurs individuels

  Scénario: Affichage liste + carte simultanés (vue hybride)
    Étant donné l'utilisateur cherche "Nantes" avec rayon 8 km
    Quand l'utilisateur active la vue "Hybride"
    Alors la liste de résultats doit s'afficher à gauche (60% écran)
    Et la carte doit s'afficher à droite (40% écran)
    Et en cliquant sur un résultat dans la liste, le marqueur doit être highlighté sur la carte
    Et vice-versa

  # ============================================================================
  # PERFORMANCES & OPTIMISATIONS
  # ============================================================================

  Scénario: Cache résultats recherche géo fréquente (Paris, Lyon, Marseille)
    Étant donné l'utilisateur cherche "Paris" avec rayon 5 km
    Quand la requête est exécutée pour la 1ère fois
    Alors les résultats doivent être mis en cache Redis pendant 10 minutes
    Et les requêtes suivantes identiques doivent utiliser le cache
    Et un header "X-Cache: HIT" doit être retourné

  Scénario: Index PostGIS pour performances requêtes spatiales
    Étant donné la table "contents" contient 100 000 contenus
    Quand une recherche géo est exécutée avec ST_DWithin
    Alors un index GIST sur la colonne "location" doit être utilisé
    Et le temps de réponse doit être <100ms (p95)

  Scénario: Pagination résultats nombreux
    Étant donné l'utilisateur cherche "Île-de-France" avec rayon 50 km
    Et la recherche retourne 1200 contenus
    Quand les résultats sont affichés
    Alors seuls les 50 premiers résultats doivent être chargés initialement
    Et un scroll infini doit charger les résultats suivants par batch de 50
    Et le total "1200 contenus trouvés" doit être affiché en haut

  # ============================================================================
  # EDGE CASES & ERREURS
  # ============================================================================

  Scénario: Nominatim API indisponible (fallback)
    Étant donné l'utilisateur cherche "Lille"
    Quand l'API Nominatim retourne une erreur 503 (service unavailable)
    Alors un message d'erreur doit être affiché :
      """
      Le service de recherche géographique est temporairement indisponible.
      Veuillez réessayer dans quelques instants.
      """
    Et l'utilisateur doit pouvoir utiliser la recherche textuelle classique

  Scénario: Recherche géo sans connexion Internet
    Étant donné l'utilisateur est en mode offline
    Quand l'utilisateur tente une recherche géographique
    Alors un message doit indiquer :
      """
      La recherche géographique nécessite une connexion Internet.
      Vous pouvez parcourir les contenus téléchargés.
      """

  Scénario: Rate limiting Nominatim (1 req/s)
    Étant donné Nominatim impose un rate limit de 1 requête/seconde
    Et l'utilisateur tape rapidement "Par" → "Pari" → "Paris"
    Quand le système détecte plusieurs requêtes rapides
    Alors un debounce de 500ms doit être appliqué
    Et seule la dernière requête ("Paris") doit être envoyée à Nominatim

  Scénario: Recherche avec caractères spéciaux ou injection SQL
    Étant donné l'utilisateur saisit "Paris'; DROP TABLE contents; --"
    Quand le système traite la requête
    Alors les caractères spéciaux doivent être échappés
    Et aucune injection SQL ne doit être possible
    Et Nominatim doit retourner 0 résultat (lieu invalide)

  # ============================================================================
  # SAUVEGARDE DES RECHERCHES (max 5)
  # ============================================================================

  Scénario: Sauvegarde automatique d'une recherche géo
    Étant donné l'utilisateur effectue une recherche "Bordeaux" avec rayon 10 km
    Quand la recherche est validée
    Alors elle doit être sauvegardée dans l'historique :
      | search_query | Bordeaux                |
      | radius_km    | 10                      |
      | lat          | 44.8378                 |
      | lon          | -0.5792                 |
      | timestamp    | 2026-02-03 14:30:00     |
    Et l'utilisateur peut la rappeler via "Recherches récentes"

  Scénario: Limite de 5 recherches sauvegardées (FIFO)
    Étant donné l'utilisateur a déjà 5 recherches sauvegardées
    Quand l'utilisateur effectue une 6ème recherche "Montpellier"
    Alors la recherche la plus ancienne doit être supprimée
    Et "Montpellier" doit être ajoutée en 1ère position
    Et la limite de 5 recherches doit être respectée

  Scénario: Rejouer une recherche sauvegardée
    Étant donné l'utilisateur a une recherche sauvegardée "Lyon - 15 km"
    Quand l'utilisateur clique sur cette recherche dans l'historique
    Alors la recherche doit être ré-exécutée avec les mêmes paramètres
    Et les résultats actualisés doivent être affichés
    Et le rayon slider doit être positionné à 15 km

  # ============================================================================
  # MÉTRIQUES & ANALYTICS
  # ============================================================================

  Scénario: Logging des recherches géographiques pour analytics
    Étant donné l'utilisateur effectue une recherche "Grenoble" avec rayon 12 km
    Quand la recherche est exécutée
    Alors un événement analytics doit être loggé :
      | event_type          | geo_search_executed      |
      | search_query        | Grenoble                 |
      | radius_km           | 12                       |
      | results_count       | 87                       |
      | response_time_ms    | 145                      |
      | cache_hit           | false                    |
      | user_id             | user-123                 |
      | timestamp           | 2026-02-03 14:30:00      |
    Et ces données doivent alimenter le dashboard de monitoring

  Scénario: Top recherches géographiques (analytics)
    Étant donné le système analyse les recherches sur 30 jours
    Quand le dashboard analytics est consulté
    Alors les lieux les plus recherchés doivent être affichés :
      | lieu                | nb_recherches |
      | Paris               | 12450         |
      | Lyon                | 5632          |
      | Marseille           | 4521          |
      | Toulouse            | 3890          |
      | Nice                | 3124          |
    Et ces données doivent guider la création de contenus ciblés
