"""
Tests pour POST /api/v1/auth/login
Couvre les AC 1-5 de la story 1.3 : Login Utilisateur (JWT)
"""

import time

import pytest
import jwt
from pathlib import Path
from fastapi.testclient import TestClient


LOGIN_URL = "/api/v1/auth/login"
REGISTER_URL = "/api/v1/auth/register"

VALID_USER = {
    "email": "login@example.com",
    "password": "Password123!",
    "name": "Login User",
}

VALID_CREDENTIALS = {
    "email": "login@example.com",
    "password": "Password123!",
}


@pytest.fixture()
def users_file(tmp_path: Path) -> Path:
    """Fichier de stockage JSON isolé pour les tests."""
    return tmp_path / "users.json"


@pytest.fixture()
def client(users_file: Path, monkeypatch):
    """TestClient avec stockage JSON isolé et rate limiting désactivé."""
    import services.auth_service as auth_svc

    monkeypatch.setattr(auth_svc, "USERS_FILE", users_file)
    monkeypatch.setattr(auth_svc, "USE_DATABASE", False)
    monkeypatch.setattr(auth_svc, "DATABASE_AVAILABLE", False)

    from middleware.rate_limiting import RateLimitManager

    async def _check_request_allow(self, request):
        return True, "ok", "test"

    async def _check_translation_allow(self, request, file_size_mb=0):
        return True, "ok"

    monkeypatch.setattr(RateLimitManager, "check_request", _check_request_allow)
    monkeypatch.setattr(RateLimitManager, "check_translation", _check_translation_allow)

    from main import app

    return TestClient(app, raise_server_exceptions=True)


@pytest.fixture()
def registered_client(client, users_file: Path):
    """Client avec un utilisateur déjà enregistré."""
    client.post(REGISTER_URL, json=VALID_USER)
    return client


# ---------------------------------------------------------------------------
# AC1 : Login réussi
# ---------------------------------------------------------------------------


class TestLoginSuccess:
    """AC1 : login valide → 200 + access_token (15min) + refresh_token (7j)"""

    def test_returns_200_on_success(self, registered_client):
        response = registered_client.post(LOGIN_URL, json=VALID_CREDENTIALS)
        assert response.status_code == 200

    def test_response_contains_data_and_meta(self, registered_client):
        response = registered_client.post(LOGIN_URL, json=VALID_CREDENTIALS)
        body = response.json()
        assert "data" in body
        assert "meta" in body

    def test_response_data_contains_access_token(self, registered_client):
        response = registered_client.post(LOGIN_URL, json=VALID_CREDENTIALS)
        data = response.json()["data"]
        assert "access_token" in data
        assert data["access_token"]

    def test_response_data_contains_refresh_token(self, registered_client):
        response = registered_client.post(LOGIN_URL, json=VALID_CREDENTIALS)
        data = response.json()["data"]
        assert "refresh_token" in data
        assert data["refresh_token"]

    def test_response_data_has_bearer_token_type(self, registered_client):
        response = registered_client.post(LOGIN_URL, json=VALID_CREDENTIALS)
        assert response.json()["data"]["token_type"] == "bearer"

    def test_access_token_expiry_is_15_minutes(self, registered_client):
        """AC1 : access_token expire dans ~15 minutes"""
        import services.auth_service as auth_svc

        if not auth_svc.JWT_AVAILABLE:
            pytest.skip("PyJWT non disponible dans cet environnement")

        response = registered_client.post(LOGIN_URL, json=VALID_CREDENTIALS)
        token = response.json()["data"]["access_token"]
        payload = jwt.decode(token, options={"verify_signature": False})
        now = time.time()
        exp = payload["exp"]
        # Doit expirer dans ~15 minutes (tolérance : 13–17 min = 780–1020s)
        assert 780 < (exp - now) < 1020, f"Expiry inattendu: {exp - now:.0f}s"

    def test_refresh_token_expiry_is_7_days(self, registered_client):
        """AC1 : refresh_token expire dans ~7 jours"""
        import services.auth_service as auth_svc

        if not auth_svc.JWT_AVAILABLE:
            pytest.skip("PyJWT non disponible dans cet environnement")

        response = registered_client.post(LOGIN_URL, json=VALID_CREDENTIALS)
        token = response.json()["data"]["refresh_token"]
        payload = jwt.decode(token, options={"verify_signature": False})
        now = time.time()
        exp = payload["exp"]
        # 7 jours = 604800s (tolérance : 6.5–7.5 jours = 561600–648000s)
        assert 561_600 < (exp - now) < 648_000, f"Expiry inattendu: {exp - now:.0f}s"


# ---------------------------------------------------------------------------
# AC2 : Signature JWT
# ---------------------------------------------------------------------------


class TestJWTSigning:
    """AC2 : tokens signés avec SECRET_KEY depuis la variable d'environnement"""

    def test_access_token_verifiable_with_secret_key(self, registered_client):
        """AC2 / 5.7 : access_token signé et vérifiable"""
        import services.auth_service as auth_svc

        if not auth_svc.JWT_AVAILABLE:
            pytest.skip("PyJWT non disponible dans cet environnement")

        response = registered_client.post(LOGIN_URL, json=VALID_CREDENTIALS)
        token = response.json()["data"]["access_token"]
        payload = jwt.decode(token, auth_svc.SECRET_KEY, algorithms=["HS256"])
        assert payload is not None

    def test_refresh_token_verifiable_with_secret_key(self, registered_client):
        """AC2 / 5.7 : refresh_token signé et vérifiable"""
        import services.auth_service as auth_svc

        if not auth_svc.JWT_AVAILABLE:
            pytest.skip("PyJWT non disponible dans cet environnement")

        response = registered_client.post(LOGIN_URL, json=VALID_CREDENTIALS)
        token = response.json()["data"]["refresh_token"]
        payload = jwt.decode(token, auth_svc.SECRET_KEY, algorithms=["HS256"])
        assert payload is not None

    def test_tokens_contain_correct_user_id_in_sub(
        self, registered_client, users_file: Path
    ):
        """AC2 / 5.6 : tokens contiennent l'id utilisateur dans le claim 'sub'"""
        import services.auth_service as auth_svc

        if not auth_svc.JWT_AVAILABLE:
            pytest.skip("PyJWT non disponible dans cet environnement")

        test_email = "sub_test@example.com"
        reg_response = registered_client.post(
            REGISTER_URL,
            json={"email": test_email, "password": "Pass123!", "name": "Sub Test"},
        )
        user_id = reg_response.json()["data"]["id"]

        login_response = registered_client.post(
            LOGIN_URL,
            json={"email": test_email, "password": "Pass123!"},
        )
        token = login_response.json()["data"]["access_token"]
        payload = jwt.decode(token, auth_svc.SECRET_KEY, algorithms=["HS256"])
        assert payload["sub"] == user_id

    def test_access_token_contains_tier_claim(self, registered_client):
        """Task 2.5 : access_token contient le claim 'tier'"""
        import services.auth_service as auth_svc

        if not auth_svc.JWT_AVAILABLE:
            pytest.skip("PyJWT non disponible dans cet environnement")

        response = registered_client.post(LOGIN_URL, json=VALID_CREDENTIALS)
        token = response.json()["data"]["access_token"]
        payload = jwt.decode(token, auth_svc.SECRET_KEY, algorithms=["HS256"])
        assert "tier" in payload
        assert payload["tier"] == "free"

    def test_tokens_use_hs256_algorithm(self, registered_client):
        """AC2 : tokens signés avec l'algorithme HS256"""
        import services.auth_service as auth_svc

        if not auth_svc.JWT_AVAILABLE:
            pytest.skip("PyJWT non disponible dans cet environnement")

        response = registered_client.post(LOGIN_URL, json=VALID_CREDENTIALS)
        access_token = response.json()["data"]["access_token"]
        refresh_token = response.json()["data"]["refresh_token"]

        access_header = jwt.get_unverified_header(access_token)
        refresh_header = jwt.get_unverified_header(refresh_token)
        assert access_header["alg"] == "HS256"
        assert refresh_header["alg"] == "HS256"


# ---------------------------------------------------------------------------
# AC3 : Mot de passe invalide
# ---------------------------------------------------------------------------


class TestInvalidPassword:
    """AC3 : mauvais mot de passe → 401 INVALID_CREDENTIALS"""

    def test_wrong_password_returns_401(self, registered_client):
        response = registered_client.post(
            LOGIN_URL,
            json={"email": VALID_CREDENTIALS["email"], "password": "WrongPassword!"},
        )
        assert response.status_code == 401

    def test_wrong_password_error_code(self, registered_client):
        response = registered_client.post(
            LOGIN_URL,
            json={"email": VALID_CREDENTIALS["email"], "password": "WrongPassword!"},
        )
        assert response.json()["error"] == "INVALID_CREDENTIALS"

    def test_wrong_password_has_message(self, registered_client):
        response = registered_client.post(
            LOGIN_URL,
            json={"email": VALID_CREDENTIALS["email"], "password": "WrongPassword!"},
        )
        assert "message" in response.json()
        assert response.json()["message"]


# ---------------------------------------------------------------------------
# AC4 : Utilisateur introuvable - maintenant retourne INVALID_CREDENTIALS
# pour éviter l'énumération d'emails
# ---------------------------------------------------------------------------


class TestUserNotFound:
    """AC4 : email inconnu → 401 INVALID_CREDENTIALS (anti-énumération)"""

    def test_unknown_email_returns_401(self, client):
        response = client.post(
            LOGIN_URL,
            json={"email": "nonexistent@example.com", "password": "Password123!"},
        )
        assert response.status_code == 401

    def test_unknown_email_returns_invalid_credentials_not_user_not_found(self, client):
        """Security: unknown email returns same error as wrong password"""
        response = client.post(
            LOGIN_URL,
            json={"email": "nonexistent@example.com", "password": "Password123!"},
        )
        assert response.json()["error"] == "INVALID_CREDENTIALS"

    def test_unknown_email_has_message(self, client):
        response = client.post(
            LOGIN_URL,
            json={"email": "nonexistent@example.com", "password": "Password123!"},
        )
        assert "message" in response.json()
        assert response.json()["message"]


# ---------------------------------------------------------------------------
# AC5 : Versionnage d'API + validation email
# ---------------------------------------------------------------------------


class TestApiVersioningAndValidation:
    """AC5 : endpoint à /api/v1/auth/login ; validation email"""

    def test_endpoint_accessible_at_v1_path(self, registered_client):
        response = registered_client.post(LOGIN_URL, json=VALID_CREDENTIALS)
        assert response.status_code == 200

    def test_invalid_email_format_returns_400(self, client):
        """Task 4.3 : email invalide → 400 INVALID_EMAIL"""
        response = client.post(
            LOGIN_URL,
            json={"email": "not-an-email", "password": "Password123!"},
        )
        assert response.status_code == 400

    def test_invalid_email_error_code(self, client):
        response = client.post(
            LOGIN_URL,
            json={"email": "not-an-email", "password": "Password123!"},
        )
        assert response.json()["error"] == "INVALID_EMAIL"

    def test_invalid_email_has_message(self, client):
        response = client.post(
            LOGIN_URL,
            json={"email": "not-an-email", "password": "Password123!"},
        )
        assert "message" in response.json()
        assert response.json()["message"]

    def test_invalid_json_returns_invalid_request(self, client):
        response = client.post(
            LOGIN_URL,
            data="{bad-json",
            headers={"Content-Type": "application/json"},
        )
        assert response.status_code == 400
        assert response.json()["error"] == "INVALID_REQUEST"

    def test_missing_password_returns_invalid_request(self, client):
        response = client.post(
            LOGIN_URL,
            json={"email": "test@example.com"},
        )
        assert response.status_code == 400
