"""
Tests de charge Locust — SaaS Turf Prédictions IA
Sprint 8 — QA, Beta Fermee, Go/No-Go
Ticket: HRT-34

Scénarios :
- Test normal : 100 users simultanés, 10 min
- Test spike : 500 users en 2 min

Cibles :
- p95 latence < 500ms
- error rate < 0.1%

Usage :
  # Test normal (100 users, 10 min)
  locust -f tests/load/locustfile.py --host http://localhost:8792 \
    --users 100 --spawn-rate 10 --run-time 10m \
    --headless --csv tests/reports/load_normal

  # Test spike (500 users, 2 min)
  locust -f tests/load/locustfile.py --host http://localhost:8792 \
    --users 500 --spawn-rate 250 --run-time 2m \
    --headless --csv tests/reports/load_spike

  # Interface web
  locust -f tests/load/locustfile.py --host http://localhost:8792
"""

import random
import json
from locust import HttpUser, TaskSet, task, between, events
from locust.env import Environment
import logging

logger = logging.getLogger(__name__)

# === Credentials de test ===
TEST_USERS = [
    {"email": f"loadtest+{i}@h3r7.tech", "password": "TestLoad_2026!"}
    for i in range(50)
]


# === Helper de login ===
class AuthMixin:
    """Mixin pour authentification JWT."""

    token: str = None

    def login(self):
        """Login et stocke le token JWT."""
        user = random.choice(TEST_USERS)
        with self.client.post(
            "/api/auth/login",
            json={"email": user["email"], "password": user["password"]},
            catch_response=True,
            name="/api/auth/login",
        ) as resp:
            if resp.status_code == 200:
                data = resp.json()
                self.token = data.get("access_token") or data.get("token")
                resp.success()
            else:
                self.token = None
                resp.failure(f"Login failed: {resp.status_code}")

    def auth_headers(self):
        if self.token:
            return {"Authorization": f"Bearer {self.token}"}
        return {}


# === Comportement utilisateur Free ===
class FreePlanTaskSet(TaskSet):
    """Simule un utilisateur Free qui consulte les prédictions top 3."""

    def on_start(self):
        self.user.login()

    @task(5)
    def voir_dashboard(self):
        with self.client.get(
            "/dashboard",
            catch_response=True,
            name="/dashboard",
        ) as resp:
            if resp.status_code in (200, 304):
                resp.success()
            else:
                resp.failure(f"Dashboard: {resp.status_code}")

    @task(8)
    def voir_predictions_aujourd_hui(self):
        with self.client.get(
            "/api",
            headers=self.user.auth_headers(),
            catch_response=True,
            name="/api (today predictions)",
        ) as resp:
            if resp.status_code == 200:
                data = resp.json()
                if isinstance(data, (list, dict)):
                    resp.success()
                else:
                    resp.failure("Réponse inattendue")
            elif resp.status_code in (401, 403):
                resp.success()  # Normal si token expiré
            else:
                resp.failure(f"Predictions: {resp.status_code}")

    @task(3)
    def voir_courses(self):
        with self.client.get(
            "/api/races",
            headers=self.user.auth_headers(),
            catch_response=True,
            name="/api/races",
        ) as resp:
            if resp.status_code in (200, 401, 403):
                resp.success()
            else:
                resp.failure(f"Races: {resp.status_code}")

    @task(2)
    def voir_scoring(self):
        with self.client.get(
            "/api/scoring",
            headers=self.user.auth_headers(),
            catch_response=True,
            name="/api/scoring",
        ) as resp:
            if resp.status_code in (200, 401, 403):
                resp.success()
            else:
                resp.failure(f"Scoring: {resp.status_code}")

    @task(1)
    def voir_portail(self):
        with self.client.get(
            "/",
            catch_response=True,
            name="/ (portail)",
        ) as resp:
            if resp.status_code in (200, 304):
                resp.success()
            else:
                resp.failure(f"Portail: {resp.status_code}")


# === Comportement utilisateur Premium ===
class PremiumPlanTaskSet(TaskSet):
    """Simule un utilisateur Premium avec accès complet."""

    def on_start(self):
        self.user.login()

    @task(4)
    def voir_dashboard_complet(self):
        with self.client.get(
            "/dashboard",
            headers=self.user.auth_headers(),
            catch_response=True,
            name="/dashboard (premium)",
        ) as resp:
            if resp.status_code in (200, 304):
                resp.success()
            else:
                resp.failure(f"Dashboard premium: {resp.status_code}")

    @task(6)
    def voir_toutes_courses(self):
        with self.client.get(
            "/api/races?all=true",
            headers=self.user.auth_headers(),
            catch_response=True,
            name="/api/races?all=true",
        ) as resp:
            if resp.status_code in (200, 403):
                resp.success()
            else:
                resp.failure(f"All races: {resp.status_code}")

    @task(3)
    def export_csv(self):
        with self.client.get(
            "/api/export/csv",
            headers=self.user.auth_headers(),
            catch_response=True,
            name="/api/export/csv",
        ) as resp:
            if resp.status_code in (200, 403):
                resp.success()
            else:
                resp.failure(f"CSV export: {resp.status_code}")

    @task(2)
    def voir_historique(self):
        with self.client.get(
            "/api/odds_history",
            headers=self.user.auth_headers(),
            catch_response=True,
            name="/api/odds_history",
        ) as resp:
            if resp.status_code in (200, 403):
                resp.success()
            else:
                resp.failure(f"Odds history: {resp.status_code}")

    @task(1)
    def appel_api_externe(self):
        """Simule un accès API via clé (plan pro)."""
        with self.client.get(
            "/api/races",
            headers={"X-API-Key": "test-api-key-qa"},
            catch_response=True,
            name="/api/races (api-key)",
        ) as resp:
            if resp.status_code in (200, 401, 403):
                resp.success()
            else:
                resp.failure(f"API key access: {resp.status_code}")


# === Utilisateurs Locust ===


class FreeUser(AuthMixin, HttpUser):
    """Utilisateur plan free (70% du trafic)."""

    tasks = [FreePlanTaskSet]
    wait_time = between(1, 4)
    weight = 70


class PremiumUser(AuthMixin, HttpUser):
    """Utilisateur plan premium (30% du trafic)."""

    tasks = [PremiumPlanTaskSet]
    wait_time = between(0.5, 2)
    weight = 30


# === Events et rapports ===


@events.test_stop.add_listener
def on_test_stop(environment: Environment, **kwargs):
    """Analyse les résultats et écrit un rapport."""
    stats = environment.stats
    total = stats.total

    report_lines = [
        "=" * 60,
        "RAPPORT TEST DE CHARGE — SaaS Turf IA",
        "=" * 60,
        f"Total requests      : {total.num_requests}",
        f"Failures            : {total.num_failures}",
        f"Error rate          : {total.fail_ratio * 100:.2f}%",
        f"Avg response time   : {total.avg_response_time:.1f}ms",
        f"95th percentile     : {total.get_response_time_percentile(0.95):.1f}ms",
        f"99th percentile     : {total.get_response_time_percentile(0.99):.1f}ms",
        f"Max response time   : {total.max_response_time:.1f}ms",
        f"RPS (avg)           : {total.total_rps:.2f}",
        "",
        "CRITERES DE SUCCES :",
    ]

    p95 = total.get_response_time_percentile(0.95)
    error_rate = total.fail_ratio * 100

    p95_ok = p95 < 500
    error_ok = error_rate < 0.1

    report_lines.append(
        f"  p95 < 500ms        : {'✅ PASS' if p95_ok else '❌ FAIL'} ({p95:.1f}ms)"
    )
    report_lines.append(
        f"  error rate < 0.1%  : {'✅ PASS' if error_ok else '❌ FAIL'} ({error_rate:.2f}%)"
    )
    report_lines.append("")
    report_lines.append(
        f"VERDICT GLOBAL      : {'✅ GO' if (p95_ok and error_ok) else '❌ NO-GO'}"
    )
    report_lines.append("=" * 60)

    report = "\n".join(report_lines)
    print(report)

    # Écrire dans un fichier
    import os
    from pathlib import Path
    from datetime import datetime

    Path("tests/reports").mkdir(parents=True, exist_ok=True)
    report_file = (
        f"tests/reports/load_report_{datetime.now().strftime('%Y%m%d_%H%M%S')}.txt"
    )
    with open(report_file, "w") as f:
        f.write(report)
    print(f"\nRapport sauvegardé : {report_file}")

    # Exit code non-0 si les critères ne sont pas respectés
    if not (p95_ok and error_ok):
        logger.error("CRITÈRES DE PERFORMANCE NON RESPECTÉS — NO-GO")
        environment.process_exit_code = 1
