# language: fr
Fonctionnalité: API - Notifications géolocalisées et quota anti-spam
  En tant qu'API backend
  Je veux gérer les notifications de contenus géolocalisés
  Afin de respecter les quotas et détecter le bon timing

  Contexte:
    Étant donné que l'API RoadWave est disponible
    Et que Redis est accessible
    Et qu'un utilisateur "user123" existe en mode voiture

  # Calcul ETA et déclenchement notification

  Scénario: API calcule ETA vers point géolocalisé
    Étant donné qu'un contenu géolocalisé existe à la position (48.8584, 2.2945) Tour Eiffel
    Et que l'utilisateur roule à 50 km/h vers ce point
    Et qu'il est actuellement à 98 mètres
    Quand je POST /api/v1/geo-notifications/check-eta
      """json
      {
        "user_id": "user123",
        "current_lat": 48.8577,
        "current_lon": 2.2950,
        "speed_kmh": 50,
        "target_content_id": "content_tower"
      }
      """
    Alors le statut de réponse est 200
    Et la réponse contient:
      """json
      {
        "eta_seconds": 7,
        "distance_meters": 98,
        "should_notify": true,
        "notification_trigger": "eta_threshold"
      }
      """

  Scénario: API déclenche notification 7 secondes avant arrivée
    Étant donné qu'un contenu géolocalisé existe au point GPS
    Et que l'ETA calculé est 7 secondes
    Quand je POST /api/v1/geo-notifications/trigger
      """json
      {
        "user_id": "user123",
        "content_id": "content_tower",
        "eta_seconds": 7
      }
      """
    Alors le statut de réponse est 201
    Et une notification est envoyée au mobile
    Et la notification contient:
      | champ       | valeur                  |
      | type        | geo_anchored            |
      | countdown   | 7                       |
      | icon        | 🏛️ (selon type contenu) |
      | sound       | bip court               |

  Scénario: API ne déclenche pas si ETA >7 secondes
    Étant donné qu'un contenu géolocalisé existe
    Et que l'ETA calculé est 10 secondes
    Quand je POST /api/v1/geo-notifications/check-eta
    Alors le statut de réponse est 200
    Et la réponse contient:
      """json
      {
        "eta_seconds": 10,
        "should_notify": false,
        "reason": "eta_above_threshold"
      }
      """
    Et aucune notification n'est envoyée

  Scénario: API déclenche notification immédiate si vitesse faible et proche
    Étant donné qu'un contenu géolocalisé existe
    Et que l'utilisateur roule à 3 km/h
    Et qu'il est à 40 mètres du point
    Quand je POST /api/v1/geo-notifications/check-eta
    Alors le statut de réponse est 200
    Et la réponse contient:
      """json
      {
        "eta_seconds": 0,
        "distance_meters": 40,
        "speed_kmh": 3,
        "should_notify": true,
        "notification_trigger": "proximity_and_low_speed"
      }
      """
    Et une notification est envoyée immédiatement

  # Quota anti-spam

  Scénario: API vérifie quota avant notification (6 contenus max/heure)
    Étant donné que l'utilisateur a déjà reçu 5 notifications géo dans la dernière heure
    Quand je POST /api/v1/geo-notifications/check-quota
      """json
      {
        "user_id": "user123"
      }
      """
    Alors le statut de réponse est 200
    Et la réponse contient:
      """json
      {
        "quota_available": true,
        "notifications_count": 5,
        "quota_limit": 6,
        "remaining": 1
      }
      """

  Scénario: API refuse notification si quota atteint (6/6)
    Étant donné que l'utilisateur a déjà reçu 6 notifications géo dans la dernière heure
    Quand je POST /api/v1/geo-notifications/trigger
      """json
      {
        "user_id": "user123",
        "content_id": "content_x"
      }
      """
    Alors le statut de réponse est 429
    Et la réponse contient:
      """json
      {
        "error": "QUOTA_EXCEEDED",
        "message": "Quota horaire de notifications géolocalisées atteint (6/6)",
        "notifications_count": 6,
        "quota_limit": 6,
        "reset_in_seconds": 1800
      }
      """
    Et aucune notification n'est envoyée

  Scénario: API incrémente quota après notification envoyée
    Étant donné que l'utilisateur a reçu 3 notifications géo
    Quand je POST /api/v1/geo-notifications/trigger (succès)
    Alors le quota passe à 4/6
    Et en Redis, la clé "user:user123:geo_quota" contient 4 entrées
    Et le sorted set contient les timestamps des 4 notifications

  Scénario: API utilise rolling window pour quota horaire
    Étant donné que l'utilisateur a reçu 6 notifications:
      | timestamp           |
      | 10:00:00            |
      | 10:15:00            |
      | 10:30:00            |
      | 10:45:00            |
      | 11:00:00            |
      | 11:05:00            |
    Et que l'heure actuelle est 11:10:00
    Quand je vérifie le quota
    Alors les notifications avant 10:10:00 sont expirées
    Et seules 4 notifications sont comptées (10:15, 10:30, 10:45, 11:00, 11:05 = 5)
    Et le quota disponible est 1/6

  Scénario: API exclut séquences d'audio-guide multi-séquences du quota
    Étant donné qu'un audio-guide "Visite Louvre" a 12 séquences
    Et que l'utilisateur a écouté les 12 séquences
    Quand je calcule le quota
    Alors l'audio-guide compte pour 1 seule notification
    Et les 12 séquences ne consomment pas 12 quota
    Et le quota utilisé est 1/6

  # Cooldown après ignorance

  Scénario: API active cooldown 10 min si notification ignorée
    Étant donné qu'une notification géo a été envoyée à 10:00:00
    Et que l'utilisateur n'a pas appuyé sur "Suivant" dans les 7 secondes
    Quand je POST /api/v1/geo-notifications/ignored
      """json
      {
        "user_id": "user123",
        "notification_id": "notif123"
      }
      """
    Alors le statut de réponse est 200
    Et un cooldown de 10 minutes est activé
    Et en Redis, la clé "user:user123:geo_cooldown" est créée
    Et le TTL est 600 secondes (10 minutes)

  Scénario: API refuse notification si cooldown actif
    Étant donné qu'un cooldown est actif depuis 5 minutes
    Et qu'il reste 5 minutes de cooldown
    Quand je POST /api/v1/geo-notifications/trigger
    Alors le statut de réponse est 429
    Et la réponse contient:
      """json
      {
        "error": "COOLDOWN_ACTIVE",
        "message": "Cooldown actif après notification ignorée",
        "cooldown_remaining_seconds": 300
      }
      """
    Et aucune notification n'est envoyée

  Scénario: API autorise notification après expiration cooldown
    Étant donné qu'un cooldown a été activé à 10:00:00
    Et que l'heure actuelle est 10:11:00 (11 minutes après)
    Quand je POST /api/v1/geo-notifications/check-quota
    Alors le cooldown a expiré
    Et les notifications sont à nouveau autorisées

  # Tracking GPS temps réel

  Scénario: API reçoit positions GPS toutes les secondes
    Étant donné que le mobile envoie des positions GPS
    Quand je POST /api/v1/gps/track
      """json
      {
        "user_id": "user123",
        "latitude": 48.8577,
        "longitude": 2.2950,
        "speed_kmh": 50,
        "timestamp": "2026-02-02T10:30:00Z"
      }
      """
    Alors le statut de réponse est 200
    Et la position est stockée dans l'historique GPS (Redis)
    Et l'historique conserve les 30 derniers points

  Scénario: API calcule vitesse moyenne sur 30 derniers points
    Étant donné que l'historique GPS contient 30 points
    Et que les vitesses enregistrées sont variables
    Quand je GET /api/v1/gps/average-speed
    Alors le statut de réponse est 200
    Et la réponse contient:
      """json
      {
        "average_speed_kmh": 48.5,
        "samples_count": 30,
        "period_seconds": 30
      }
      """

  Scénario: API détecte contenus géolocalisés dans rayon 500m
    Étant donné que l'utilisateur est à la position (48.8577, 2.2950)
    Et qu'un contenu géolocalisé existe à 300m
    Quand je GET /api/v1/geo-notifications/nearby
      """json
      {
        "user_id": "user123",
        "latitude": 48.8577,
        "longitude": 2.2950,
        "radius_meters": 500
      }
      """
    Alors le statut de réponse est 200
    Et la réponse contient:
      """json
      {
        "nearby_contents": [
          {
            "id": "content_tower",
            "distance_meters": 300,
            "eta_seconds": 21,
            "should_notify_soon": false
          }
        ]
      }
      """

  # Validation contenu géolocalisé

  Scénario: API valide que contenu géolocalisé accepté
    Étant donné qu'une notification géo a été envoyée
    Et que l'utilisateur a appuyé sur "Suivant" dans les 7 secondes
    Quand je POST /api/v1/geo-notifications/accepted
      """json
      {
        "user_id": "user123",
        "notification_id": "notif123",
        "content_id": "content_tower"
      }
      """
    Alors le statut de réponse est 200
    Et le contenu géolocalisé est inséré en priorité
    Et un décompte de 5 secondes est déclenché côté client
    Et le quota n'est PAS incrémenté (acceptation = pas de spam)

  Scénario: API enregistre contenu géolocalisé perdu
    Étant donné qu'une notification géo a été envoyée
    Et que l'utilisateur n'a pas appuyé sur "Suivant"
    Quand je POST /api/v1/geo-notifications/ignored
    Alors le contenu géolocalisé est perdu
    Et il n'est PAS inséré dans la file d'attente
    Et une métrique "geo_notification_ignored" est enregistrée
    Et le cooldown 10 min est activé

  # Métriques

  Scénario: API stocke métriques de notifications géolocalisées
    Étant donné que l'API envoie des notifications géo
    Quand je consulte les métriques
    Alors les données suivantes sont disponibles:
      | métrique                        | description                     |
      | geo_notifications_sent          | Nombre total envoyées           |
      | geo_notifications_accepted      | Nombre acceptées (Suivant)      |
      | geo_notifications_ignored       | Nombre ignorées                 |
      | geo_acceptance_rate             | % acceptation                   |
      | average_eta_seconds             | ETA moyen au déclenchement      |
      | quota_exceeded_count            | Nombre de refus quota           |

  # Redis structures

  Scénario: API utilise sorted set Redis pour quota
    Étant donné que l'utilisateur a reçu 3 notifications
    Quand je consulte Redis "user:user123:geo_quota"
    Alors la structure est un sorted set:
      """
      ZADD user:user123:geo_quota
        1707047400 "notif1"
        1707047700 "notif2"
        1707048000 "notif3"
      """
    Et les scores sont les timestamps Unix
    Et le TTL est 3600 secondes (1 heure)

  Scénario: API compte notifications avec ZCOUNT
    Étant donné que le sorted set contient plusieurs notifications
    Et que l'heure actuelle est 11:00:00 (timestamp 1707048000)
    Quand l'API vérifie le quota
    Alors la commande Redis est:
      """
      ZCOUNT user:user123:geo_quota 1707044400 1707048000
      """
    Et seules les notifications des 60 dernières minutes sont comptées

  # Gestion erreurs

  Scénario: API gère échec calcul ETA gracieusement
    Étant donné qu'une position GPS est invalide
    Quand je POST /api/v1/geo-notifications/check-eta
      """json
      {
        "user_id": "user123",
        "current_lat": 200,
        "current_lon": 300
      }
      """
    Alors le statut de réponse est 400
    Et la réponse contient:
      """json
      {
        "error": "INVALID_GPS_COORDINATES",
        "message": "Coordonnées GPS invalides"
      }
      """

  Scénario: API gère vitesse nulle sans crash
    Étant donné que l'utilisateur est à l'arrêt (vitesse = 0)
    Et qu'un contenu géolocalisé est à 100m
    Quand je POST /api/v1/geo-notifications/check-eta
    Alors l'ETA est incalculable (vitesse nulle)
    Et aucune notification n'est envoyée
    Et la réponse contient:
      """json
      {
        "should_notify": false,
        "reason": "speed_zero"
      }
      """

  Plan du Scénario: Déclenchement selon ETA
    Étant donné qu'un contenu géolocalisé existe
    Et que l'ETA calculé est <eta> secondes
    Quand je vérifie si notification doit être envoyée
    Alors la décision est <decision>

    Exemples:
      | eta | decision            |
      | 3   | notifier            |
      | 5   | notifier            |
      | 7   | notifier            |
      | 8   | ne pas notifier     |
      | 10  | ne pas notifier     |
      | 15  | ne pas notifier     |

  Plan du Scénario: Quota selon nombre notifications
    Étant donné que l'utilisateur a reçu <count> notifications dans l'heure
    Quand je vérifie le quota
    Alors le quota est <status>

    Exemples:
      | count | status      |
      | 0     | disponible  |
      | 3     | disponible  |
      | 5     | disponible  |
      | 6     | atteint     |
      | 7     | atteint     |

  # Edge cases : haute vitesse

  Scénario: API calcule ETA correctement à 130 km/h
    Étant donné qu'un contenu géolocalisé existe
    Et que l'utilisateur roule à 130 km/h (36.1 m/s)
    Et qu'il est à 252 mètres du point
    Quand je POST /api/v1/geo-notifications/check-eta
      """json
      {
        "user_id": "user123",
        "speed_kmh": 130,
        "distance_meters": 252
      }
      """
    Alors le statut de réponse est 200
    Et la réponse contient:
      """json
      {
        "eta_seconds": 7,
        "speed_ms": 36.1,
        "should_notify": true,
        "notification_trigger": "eta_threshold"
      }
      """
    Et une notification est envoyée 252m avant le point

  Scénario: API calcule distance notification selon vitesse
    Étant donné qu'un contenu géolocalisé existe
    Quand l'utilisateur roule à 180 km/h (50 m/s)
    Alors la notification est déclenchée à 350 mètres avant le point
    Et après décompte 5s, user a parcouru 250m
    Et le contenu démarre 100m avant le point
    Et le système fonctionne même à vitesse extrême

  # Edge cases : multiples points proches

  Scénario: API gère multiples points géolocalisés proches (800m chacun)
    Étant donné que 3 châteaux existent espacés de 800m chacun
      | contenu   | position_km |
      | Château A | 0           |
      | Château B | 0.8         |
      | Château C | 1.6         |
    Et que l'utilisateur roule à 50 km/h
    Quand une notification Château A est envoyée et acceptée
    Alors le quota passe à 1/6
    Et aucun cooldown n'est activé (notification acceptée)
    Quand 57 secondes s'écoulent (temps pour parcourir 800m)
    Et que l'utilisateur atteint Château B
    Alors une notification Château B est envoyée
    Car le quota n'est pas atteint (1/6)
    Et il n'y a pas de cooldown actif

  Scénario: API active cooldown réduit après validations multiples
    Étant donné que l'utilisateur a validé 2 notifications consécutives
    Et que les notifications ont toutes été acceptées (clic "Suivant")
    Quand une 3ème notification est ignorée
    Alors le cooldown activé est 5 minutes (réduit)
    Et non 10 minutes (standard)
    Car l'utilisateur a montré de l'engagement précédemment

  Scénario: API ignore notifications si cooldown actif après ignorance
    Étant donné qu'une notification a été ignorée à 10:00:00
    Et qu'un cooldown de 10 minutes est actif
    Et que 3 contenus géolocalisés existent à 10:05, 10:08, 10:11
    Quand l'utilisateur passe devant le contenu à 10:05
    Alors aucune notification n'est envoyée (cooldown actif, reste 5 min)
    Quand l'utilisateur passe devant le contenu à 10:08
    Alors aucune notification n'est envoyée (cooldown actif, reste 2 min)
    Quand l'utilisateur passe devant le contenu à 10:11
    Alors une notification est envoyée (cooldown expiré après 10 min)

  # Edge cases : mode stationnement

  Scénario: API détecte mode stationnement (vitesse < 1 km/h pendant 2 min)
    Étant donné que l'utilisateur roule à 50 km/h
    Quand la vitesse passe à 0.5 km/h (arrêt complet)
    Et que la vitesse reste < 1 km/h pendant 2 minutes consécutives
    Alors le mode "stationnement" est activé
    Et aucune notification géolocalisée n'est envoyée
    Et le système bascule automatiquement en mode piéton
    Et un événement "mode_stationnement_detected" est enregistré

  Scénario: API sort du mode stationnement quand vitesse > 5 km/h
    Étant donné que le mode stationnement est actif depuis 30 minutes
    Et que l'utilisateur était à l'arrêt près d'un château
    Quand la vitesse passe à 20 km/h pendant 10 secondes
    Alors le mode voiture est réactivé
    Et les notifications géolocalisées reprennent
    Et le quota horaire est vérifié avant nouvelle notification

  Scénario: API refuse notification si user arrêté longtemps près d'un point
    Étant donné qu'un contenu géolocalisé existe à 30 mètres
    Et que l'utilisateur est à l'arrêt (vitesse 0 km/h) depuis 3 minutes
    Quand le système détecte la proximité
    Alors le mode stationnement est actif
    Et aucune notification n'est envoyée
    Car l'utilisateur est probablement stationné (parking)
    Et pas en mode conduite
