import sys
import io
import time
from datetime import date, timedelta
from fastapi.testclient import TestClient
from main import (
    app,
    get_db,
    calculate_effective_players_for_date,
    sync_and_trigger_games_for_date,
    SLOT_MATIN,
    SLOT_APREM
)
from database import SessionLocal
from models import User, Availability, Game, VALID_SLOTS
from auth import hash_password

if hasattr(sys.stdout, 'reconfigure'):
    sys.stdout.reconfigure(encoding='utf-8')

client = TestClient(app)


def run_tests():
    print("=== DÉBUT DES TESTS AUTOMATISÉS ===")

    # 1. Test de la page d'accueil
    res = client.get("/")
    assert res.status_code == 200, f"Erreur GET / : {res.status_code}"
    assert "CALENDRIER DES PARTIES" in res.text
    print("✅ [1/8] GET / (Accueil & Calendrier) : OK")

    # 2. Test de la page des parties
    res = client.get("/parties")
    assert res.status_code == 200
    assert "PARTIES CRÉÉES" in res.text
    print("✅ [2/10] GET /parties : OK")

    # 3. Test de la page des règles / informations
    res = client.get("/regles")
    assert res.status_code == 200
    assert "COMMUNAUTÉ DISCORD" in res.text or "Informations" in res.text
    print("✅ [3/8] GET /regles : OK")

    # 4. Test d'authentification (Login Ghost_78)
    db_init = SessionLocal()
    ghost_user = db_init.query(User).filter(User.username == "Ghost_78").first()
    if not ghost_user:
        ghost_user = User(username="Ghost_78", hashed_password=hash_password("password123"))
        db_init.add(ghost_user)
        db_init.commit()
    db_init.close()

    res = client.post("/login", data={"username": "Ghost_78", "password": "password123"}, follow_redirects=False)
    assert res.status_code == 303, f"Erreur login : {res.status_code}"
    assert "airsoft_session" in res.cookies
    session_cookie = res.cookies["airsoft_session"]
    print("✅ [4/8] POST /login (Authentification & Session Cookie) : OK")

    # 5. Test API Day Details
    today_str = date.today().strftime("%Y-%m-%d")
    res = client.get(f"/api/day-details?date={today_str}", cookies={"airsoft_session": session_cookie})
    assert res.status_code == 200
    assert "Matin (09h-13h)" in res.text
    print("✅ [5/8] GET /api/day-details : OK")

    # 6. Test Toggle Availability (HTMX)
    test_d = date.today() + timedelta(days=20)
    test_date_str = test_d.strftime("%Y-%m-%d")
    slot = VALID_SLOTS[0]
    db_test = SessionLocal()
    db_test.query(Availability).filter(Availability.date == test_d).delete()
    db_test.commit()
    db_test.close()

    res = client.post(
        "/api/availability/toggle",
        data={"date": test_date_str, "slot": slot, "loop_index": 1},
        cookies={"airsoft_session": session_cookie}
    )
    assert res.status_code == 200
    assert "Me désister" in res.text or "Vous êtes inscrit" in res.text
    print("✅ [6/8] POST /api/availability/toggle (HTMX Ajout de disponibilité) : OK")

    # 7. Test Inscription nouvel utilisateur
    new_username = f"Op_{int(time.time())}"
    res = client.post("/register", data={
        "username": new_username,
        "password": "secretpassword",
        "confirm_password": "secretpassword"
    }, follow_redirects=False)
    assert res.status_code == 303
    print(f"✅ [7/8] POST /register (Nouvel opérateur '{new_username}') : OK")

    # 8. TEST CRITIQUE : 8 Joueurs sur Matin => Création automatique d'une partie Matin (8 joueurs)
    db = SessionLocal()
    try:
        special_date = date.today() + timedelta(days=60)
        special_date_str = special_date.strftime("%Y-%m-%d")

        # Nettoyage préalable pour cette date de test
        db.query(Availability).filter(Availability.date == special_date).delete()
        db.query(Game).filter(Game.date == special_date).delete()
        db.commit()

        # Créer / récupérer 8 utilisateurs distincts
        test_users = []
        for i in range(1, 9):
            uname = f"SpecialOp_{i}"
            u = db.query(User).filter(User.username == uname).first()
            if not u:
                u = User(username=uname, hashed_password=hash_password("password123"))
                db.add(u)
                db.commit()
                db.refresh(u)
            test_users.append(u)

        # 7 personnes mettent "Matin (09h-13h)"
        for u in test_users[:7]:
            db.add(Availability(user_id=u.id, date=special_date, slot=SLOT_MATIN))
        db.commit()

        # Vérifier : 7 joueurs sur Matin => pas encore de partie créée
        effective_before = calculate_effective_players_for_date(db, special_date)
        assert len(effective_before[SLOT_MATIN]["usernames"]) == 7

        # La 8ème personne s'inscrit sur Matin via l'endpoint API
        user_8 = test_users[7]
        from auth import create_session_token
        token_8 = create_session_token(user_8.id, user_8.username)

        res_toggle = client.post(
            "/api/availability/toggle",
            data={"date": special_date_str, "slot": SLOT_MATIN, "loop_index": 1},
            cookies={"airsoft_session": token_8}
        )
        assert res_toggle.status_code == 200

        # Vérifications finales
        effective_after = calculate_effective_players_for_date(db, special_date)
        matin_players = effective_after[SLOT_MATIN]["usernames"]
        assert len(matin_players) == 8, f"Attendu 8 joueurs sur Matin, obtenu: {len(matin_players)}"
        assert user_8.username in matin_players

        # Vérifier que la partie 'Game' a bien été créée dans la base de données
        game_matin = db.query(Game).filter(Game.date == special_date, Game.slot == SLOT_MATIN).first()
        assert game_matin is not None, "La partie Matin n'a pas été créée !"
        assert game_matin.player_count == 8, f"Le count de la partie doit être 8, obtenu {game_matin.player_count}"

        print("✅ [8/8] RÈGLE MÉTIER VALIDÉE : 8 joueurs Matin créent bien une partie Matin à 8 joueurs !")

        # 9. TEST : Rejet des inscriptions sur date passée
        past_date = date.today() - timedelta(days=1)
        past_date_str = past_date.strftime("%Y-%m-%d")
        res_past = client.post(
            "/api/availability/toggle",
            data={"date": past_date_str, "slot": SLOT_MATIN, "loop_index": 1},
            cookies={"airsoft_session": token_8}
        )
        assert res_past.status_code == 400, f"Attendu code 400 pour date passée, obtenu {res_past.status_code}"
        print("✅ [9/10] SÉCURITÉ VALIDÉE : Inscription sur date passée bloquée avec code 400 !")

        # 10. TEST : Seules les parties futures s'affichent sur /parties
        # On nettoie et insère une fausse partie passée dans la DB
        db.query(Game).filter(Game.date == past_date).delete()
        db.commit()
        past_game = Game(date=past_date, slot=SLOT_MATIN, player_count=10, notified_discord=True)
        db.add(past_game)
        db.commit()

        res_parties = client.get("/parties")
        assert res_parties.status_code == 200
        from discord import format_french_date
        past_french_date = format_french_date(past_date)
        assert past_french_date not in res_parties.text, "Une partie passée ne doit pas s'afficher sur la page /parties !"
        # 11. TEST : Cycle de Webhook - Confirmation, Annulation sur désistement, et Ré-alerte
        from unittest.mock import patch, AsyncMock
        import main as main_module

        with patch("main.send_discord_game_notification", new_callable=AsyncMock) as mock_confirm, \
             patch("main.send_discord_game_cancellation_notification", new_callable=AsyncMock) as mock_cancel:

            mock_confirm.return_value = True
            mock_cancel.return_value = True

            cycle_date = date.today() + timedelta(days=90)
            cycle_date_str = cycle_date.strftime("%Y-%m-%d")

            # Nettoyage préalable
            db.query(Availability).filter(Availability.date == cycle_date).delete()
            db.query(Game).filter(Game.date == cycle_date).delete()
            db.commit()

            # 7 joueurs inscrits
            for u in test_users[:7]:
                db.add(Availability(user_id=u.id, date=cycle_date, slot=SLOT_MATIN))
            db.commit()

            # A. Le 8ème joueur s'inscrit -> Seuil atteint (8 joueurs) => Envoi du Webhook de confirmation
            res_8 = client.post(
                "/api/availability/toggle",
                data={"date": cycle_date_str, "slot": SLOT_MATIN, "loop_index": 1},
                cookies={"airsoft_session": token_8}
            )
            assert res_8.status_code == 200
            assert mock_confirm.call_count == 1
            assert mock_cancel.call_count == 0

            game_cycle = db.query(Game).filter(Game.date == cycle_date, Game.slot == SLOT_MATIN).first()
            assert game_cycle.notified_discord is True
            assert game_cycle.player_count == 8

            # B. Le 8ème joueur se désiste -> Seuil non atteint (7 joueurs) => Envoi du Webhook d'annulation
            res_unreg = client.post(
                "/api/availability/toggle",
                data={"date": cycle_date_str, "slot": SLOT_MATIN, "loop_index": 1},
                cookies={"airsoft_session": token_8}
            )
            assert res_unreg.status_code == 200
            assert mock_cancel.call_count == 1
            db.refresh(game_cycle)
            assert game_cycle.notified_discord is False
            assert game_cycle.player_count == 7

            # C. Le 8ème joueur se réinscrit -> Seuil de nouveau atteint (8 joueurs) => Ré-envoi du Webhook de confirmation
            res_rereg = client.post(
                "/api/availability/toggle",
                data={"date": cycle_date_str, "slot": SLOT_MATIN, "loop_index": 1},
                cookies={"airsoft_session": token_8}
            )
            assert res_rereg.status_code == 200
            assert mock_confirm.call_count == 2
            db.refresh(game_cycle)
            assert game_cycle.notified_discord is True
            assert game_cycle.player_count == 8

            # D. Nouveau désistement -> Renvoi du Webhook d'annulation
            res_unreg2 = client.post(
                "/api/availability/toggle",
                data={"date": cycle_date_str, "slot": SLOT_MATIN, "loop_index": 1},
                cookies={"airsoft_session": token_8}
            )
            assert res_unreg2.status_code == 200
            assert mock_cancel.call_count == 2
            db.refresh(game_cycle)
            assert game_cycle.notified_discord is False
            assert game_cycle.player_count == 7

            print("✅ [11/11] CYCLE WEBHOOK VALIDÉ : Confirmation -> Annulation sur désistement -> Re-confirmation -> Re-annulation !")

    finally:
        db.close()

    print("\n🎉 TOUS LES TESTS SONT PASSÉS AVEC 100% DE SUCCÈS !")


if __name__ == "__main__":
    run_tests()

