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

Vérifications rapides sur l'état de l'application :
- Routes de base accessibles
- API répond en JSON valide
- Base de données accessible
- Pas d'erreurs 5xx sur les routes principales

Ces tests peuvent tourner SANS infra complète (pas besoin de HRT-31/33).
Exécuter sur l'app actuelle en staging ou localhost.
"""

import pytest
import requests
import os
import json

BASE_URL = os.environ.get("APP_URL", "http://localhost:8792")

# Routes qui doivent retourner 200 (publiques)
PUBLIC_ROUTES_200 = [
    "/",
    "/dashboard",
]

# Routes API qui doivent retourner 200 ou 401 (jamais 500)
API_ROUTES_NO_500 = [
    "/api",
    "/api/races",
    "/api/scoring",
    "/api/weather",
    "/api/odds_history",
]


class TestSmoke:
    """Tests de smoke : l'app répond correctement aux requêtes de base."""

    @pytest.mark.smoke
    @pytest.mark.parametrize("route", PUBLIC_ROUTES_200)
    def test_route_publique_accessible(self, route):
        """Les routes publiques doivent retourner 200."""
        try:
            resp = requests.get(f"{BASE_URL}{route}", timeout=10)
            assert resp.status_code in (200, 304), (
                f"Route publique inaccessible: {route} → {resp.status_code}"
            )
            assert len(resp.content) > 0, f"Réponse vide sur {route}"
        except requests.exceptions.ConnectionError:
            pytest.skip(
                f"App non accessible sur {BASE_URL} — vérifier que le serveur est démarré"
            )

    @pytest.mark.smoke
    @pytest.mark.parametrize("route", API_ROUTES_NO_500)
    def test_api_pas_derreur_serveur(self, route):
        """Les routes API ne doivent jamais retourner 5xx."""
        try:
            resp = requests.get(f"{BASE_URL}{route}", timeout=10)
            assert resp.status_code < 500, (
                f"Erreur serveur sur {route}: {resp.status_code}\n{resp.text[:200]}"
            )
        except requests.exceptions.ConnectionError:
            pytest.skip(f"App non accessible sur {BASE_URL}")

    @pytest.mark.smoke
    def test_api_today_retourne_json(self):
        """L'endpoint principal /api doit retourner du JSON valide."""
        try:
            resp = requests.get(f"{BASE_URL}/api", timeout=10)
            if resp.status_code == 200:
                data = resp.json()
                assert data is not None, "Réponse JSON nulle"
                assert isinstance(data, (list, dict)), (
                    f"Type de réponse inattendu: {type(data)}"
                )
        except requests.exceptions.ConnectionError:
            pytest.skip(f"App non accessible sur {BASE_URL}")
        except json.JSONDecodeError as e:
            pytest.fail(f"/api ne retourne pas du JSON valide: {e}")

    @pytest.mark.smoke
    def test_contenu_html_portail_valide(self):
        """Le portail doit contenir un titre et du contenu significatif."""
        try:
            resp = requests.get(f"{BASE_URL}/", timeout=10)
            if resp.status_code == 200:
                content = resp.text
                assert "<html" in content.lower() or "<!doctype" in content.lower(), (
                    "La page d'accueil ne retourne pas du HTML"
                )
                assert len(content) > 500, (
                    f"Page d'accueil trop courte ({len(content)} chars)"
                )
        except requests.exceptions.ConnectionError:
            pytest.skip(f"App non accessible sur {BASE_URL}")

    @pytest.mark.smoke
    def test_headers_securite_presents(self):
        """Les headers de sécurité de base doivent être présents."""
        try:
            resp = requests.get(f"{BASE_URL}/", timeout=10)
            if resp.status_code != 200:
                return

            # En production (derrière Nginx), ces headers doivent être présents
            # En dev direct Flask, ils peuvent être absents — on note seulement
            security_headers = {
                "X-Content-Type-Options": "nosniff",
                "X-Frame-Options": None,  # SAMEORIGIN ou DENY
                "X-XSS-Protection": None,
            }

            missing = []
            for header, expected_value in security_headers.items():
                if header not in resp.headers:
                    missing.append(header)

            if missing:
                # Warning seulement — bloquant uniquement en prod derrière Nginx
                pytest.warns(UserWarning, match=r".*") if False else None
                print(f"⚠️  Headers sécurité manquants (requis en prod): {missing}")

        except requests.exceptions.ConnectionError:
            pytest.skip(f"App non accessible sur {BASE_URL}")

    @pytest.mark.smoke
    def test_api_races_format_reponse(self):
        """L'endpoint /api/races doit retourner une liste structurée."""
        try:
            resp = requests.get(f"{BASE_URL}/api/races", timeout=10)
            if resp.status_code == 200:
                data = resp.json()
                assert isinstance(data, (list, dict)), (
                    f"Format inattendu pour /api/races: {type(data)}"
                )
                if isinstance(data, list) and len(data) > 0:
                    first = data[0]
                    # Vérifier la présence de champs clés
                    expected_fields = ["date", "course", "hippodrome"]
                    present = [
                        f
                        for f in expected_fields
                        if f in first
                        or any(k in first for k in [f, f.upper(), f.replace("_", "")])
                    ]
                    assert len(present) > 0, (
                        f"Champs attendus absents de /api/races. Champs présents: {list(first.keys())}"
                    )
        except requests.exceptions.ConnectionError:
            pytest.skip(f"App non accessible sur {BASE_URL}")
        except json.JSONDecodeError:
            pytest.fail("/api/races ne retourne pas du JSON valide")


class TestSmokeDatabase:
    """Tests smoke sur la base de données."""

    @pytest.mark.smoke
    def test_base_donnees_accessible(self):
        """La base de données SQLite doit être accessible et contenir des données."""
        import sqlite3

        db_path = "/home/h3r7/turf_saas/turf_saas.db"

        if not __import__("os").path.exists(db_path):
            pytest.skip(f"Base de données non trouvée: {db_path}")

        conn = sqlite3.connect(db_path)
        c = conn.cursor()

        # Vérifier que les tables essentielles existent
        c.execute("SELECT name FROM sqlite_master WHERE type='table'")
        tables = {row[0] for row in c.fetchall()}
        conn.close()

        expected_tables = ["predictions", "results"]
        for table in expected_tables:
            assert table in tables, (
                f"Table manquante dans la BDD: {table}. Tables présentes: {tables}"
            )

    @pytest.mark.smoke
    def test_donnees_predictions_disponibles(self):
        """Des prédictions doivent être présentes dans la BDD."""
        import sqlite3

        db_path = "/home/h3r7/turf_saas/turf_saas.db"

        if not __import__("os").path.exists(db_path):
            pytest.skip(f"Base de données non trouvée: {db_path}")

        conn = sqlite3.connect(db_path)
        c = conn.cursor()
        c.execute("SELECT COUNT(*) FROM predictions")
        count = c.fetchone()[0]
        conn.close()

        # Au moins quelques données pour que le SaaS soit utile
        assert count >= 0, "Table predictions accessible"
        if count == 0:
            print("⚠️  Aucune prédiction en base — le scraper doit être lancé")
