"""
Tests pour POST /api/v1/api-keys
Couvre les AC 1-7 de la story 3.1 : Modele API Key & Generation
"""

import hashlib
import json
import pytest
from pathlib import Path
from fastapi.testclient import TestClient


API_KEYS_URL = "/api/v1/api-keys"


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


@pytest.fixture()
def client(users_file: Path, monkeypatch):
    """TestClient avec stockage JSON isole et rate limiting desactive."""
    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)

    import main as main_module
    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 pro_user_token(client):
    """Cree un utilisateur Pro et retourne son token JWT."""
    import services.auth_service as auth_svc

    response = client.post(
        "/api/v1/auth/register",
        json={
            "email": "pro@example.com",
            "password": "ProPass123!",
            "name": "Pro User",
        },
    )
    assert response.status_code == 201

    users = auth_svc.load_users()
    for user_id, user_data in users.items():
        if user_data.get("email") == "pro@example.com":
            user_data["tier"] = "pro"
            user_data["plan"] = "pro"
    auth_svc.save_users(users)

    login_response = client.post(
        "/api/v1/auth/login",
        json={"email": "pro@example.com", "password": "ProPass123!"},
    )
    assert login_response.status_code == 200
    return login_response.json()["data"]["access_token"]


@pytest.fixture()
def free_user_token(client):
    """Cree un utilisateur Free et retourne son token JWT."""
    response = client.post(
        "/api/v1/auth/register",
        json={
            "email": "free@example.com",
            "password": "FreePass123!",
            "name": "Free User",
        },
    )
    assert response.status_code == 201

    login_response = client.post(
        "/api/v1/auth/login",
        json={"email": "free@example.com", "password": "FreePass123!"},
    )
    assert login_response.status_code == 200
    return login_response.json()["data"]["access_token"]


def auth_headers(token: str) -> dict:
    """Cree les headers d'authentification."""
    return {"Authorization": f"Bearer {token}"}


class TestApiKeyGenerationSuccess:
    """AC1, AC2, AC3, AC4, AC5, AC7 : generation reussie pour utilisateur Pro"""

    def test_returns_201_on_success(self, client, pro_user_token):
        response = client.post(API_KEYS_URL, headers=auth_headers(pro_user_token))
        assert response.status_code == 201

    def test_response_contains_data_and_meta(self, client, pro_user_token):
        response = client.post(API_KEYS_URL, headers=auth_headers(pro_user_token))
        body = response.json()
        assert "data" in body
        assert "meta" in body

    def test_response_data_contains_key(self, client, pro_user_token):
        response = client.post(API_KEYS_URL, headers=auth_headers(pro_user_token))
        assert "key" in response.json()["data"]
        assert response.json()["data"]["key"]

    def test_key_has_sk_live_prefix(self, client, pro_user_token):
        """AC4 : la cle suit le format sk_live_..."""
        response = client.post(API_KEYS_URL, headers=auth_headers(pro_user_token))
        key = response.json()["data"]["key"]
        assert key.startswith("sk_live_"), (
            f"La cle doit commencer par 'sk_live_', recu: {key[:20]}..."
        )

    def test_key_has_sufficient_randomness(self, client, pro_user_token):
        """AC2 : secrets.token_urlsafe(32) produit ~43 chars apres le prefixe"""
        response = client.post(API_KEYS_URL, headers=auth_headers(pro_user_token))
        key = response.json()["data"]["key"]
        random_part = key[len("sk_live_") :]
        assert len(random_part) >= 40, (
            f"La partie aleatoire doit faire au moins 40 chars, recu: {len(random_part)}"
        )

    def test_response_contains_id(self, client, pro_user_token):
        response = client.post(API_KEYS_URL, headers=auth_headers(pro_user_token))
        assert "id" in response.json()["data"]
        assert response.json()["data"]["id"]

    def test_response_contains_name(self, client, pro_user_token):
        response = client.post(
            API_KEYS_URL,
            json={"name": "My Test Key"},
            headers=auth_headers(pro_user_token),
        )
        assert response.json()["data"]["name"] == "My Test Key"

    def test_response_contains_created_at(self, client, pro_user_token):
        response = client.post(API_KEYS_URL, headers=auth_headers(pro_user_token))
        assert "created_at" in response.json()["data"]

    def test_key_prefix_stored_correctly(self, client, pro_user_token):
        """Le prefixe stocke doit correspondre aux 8 premiers caracteres de la cle."""
        response = client.post(API_KEYS_URL, headers=auth_headers(pro_user_token))
        key = response.json()["data"]["key"]
        key_prefix = response.json()["data"]["key_prefix"]
        assert key_prefix == key[:8], (
            f"key_prefix doit etre les 8 premiers chars de la cle"
        )
        assert key_prefix == "sk_live_"


class TestApiKeyStorageHashed:
    """AC3 : la cle est stockee hashee (SHA256), jamais en clair"""

    def test_key_not_stored_in_plaintext(
        self, client, pro_user_token, users_file: Path
    ):
        response = client.post(API_KEYS_URL, headers=auth_headers(pro_user_token))
        raw_key = response.json()["data"]["key"]
        assert raw_key.startswith("sk_live_")

    def test_key_hash_format_is_sha256(self, client, pro_user_token):
        """La cle a le bon format pour un hash SHA256 valide (64 chars hex)."""
        response = client.post(API_KEYS_URL, headers=auth_headers(pro_user_token))
        raw_key = response.json()["data"]["key"]

        expected_hash = hashlib.sha256(raw_key.encode()).hexdigest()
        assert len(expected_hash) == 64, "SHA256 hash doit faire 64 caracteres"
        assert all(c in "0123456789abcdef" for c in expected_hash), (
            "Hash doit etre hexadecimal"
        )

    def test_key_hash_verifiable(self, client, pro_user_token):
        """Verifie que le hash SHA256 de la cle peut etre calcule correctement."""
        response = client.post(API_KEYS_URL, headers=auth_headers(pro_user_token))
        raw_key = response.json()["data"]["key"]

        computed_hash = hashlib.sha256(raw_key.encode()).hexdigest()

        import services.auth_service as auth_svc

        if auth_svc.USE_DATABASE:
            from database.connection import get_sync_session
            from database.models import ApiKey

            with get_sync_session() as session:
                stored_key = (
                    session.query(ApiKey)
                    .filter(ApiKey.key_prefix == raw_key[:8])
                    .first()
                )
                if stored_key:
                    assert stored_key.key_hash == computed_hash, (
                        "Le hash stocke doit correspondre au hash calcule"
                    )


class TestProTierRequirement:
    """AC6 : les utilisateurs Free recoivent 403 avec PRO_FEATURE_REQUIRED"""

    def test_free_user_returns_403(self, client, free_user_token):
        response = client.post(API_KEYS_URL, headers=auth_headers(free_user_token))
        assert response.status_code == 403

    def test_free_user_error_code(self, client, free_user_token):
        response = client.post(API_KEYS_URL, headers=auth_headers(free_user_token))
        assert response.json()["error"] == "PRO_FEATURE_REQUIRED"

    def test_free_user_error_has_message(self, client, free_user_token):
        response = client.post(API_KEYS_URL, headers=auth_headers(free_user_token))
        assert "message" in response.json()
        assert response.json()["message"]


class TestAuthenticationRequired:
    """AC5 : authentification requise (401 sans token)"""

    def test_no_token_returns_401(self, client):
        response = client.post(API_KEYS_URL)
        assert response.status_code == 401

    def test_no_token_error_code(self, client):
        response = client.post(API_KEYS_URL)
        assert response.json()["error"] == "UNAUTHORIZED"

    def test_invalid_token_returns_401(self, client):
        response = client.post(
            API_KEYS_URL,
            headers={"Authorization": "Bearer invalid_token"},
        )
        assert response.status_code == 401

    def test_expired_token_returns_401(self, client):
        response = client.post(
            API_KEYS_URL,
            headers={
                "Authorization": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.expired"
            },
        )
        assert response.status_code == 401


class TestMultipleKeysForSameUser:
    """Tests supplementaires : un utilisateur peut avoir plusieurs cles"""

    def test_user_can_create_multiple_keys(self, client, pro_user_token):
        response1 = client.post(API_KEYS_URL, headers=auth_headers(pro_user_token))
        response2 = client.post(API_KEYS_URL, headers=auth_headers(pro_user_token))

        assert response1.status_code == 201
        assert response2.status_code == 201

        key1 = response1.json()["data"]["key"]
        key2 = response2.json()["data"]["key"]
        assert key1 != key2, "Chaque cle doit etre unique"

    def test_list_keys_returns_all_keys(self, client, pro_user_token):
        client.post(
            API_KEYS_URL,
            json={"name": "Key 1"},
            headers=auth_headers(pro_user_token),
        )
        client.post(
            API_KEYS_URL,
            json={"name": "Key 2"},
            headers=auth_headers(pro_user_token),
        )

        response = client.get(API_KEYS_URL, headers=auth_headers(pro_user_token))
        assert response.status_code == 200
        assert response.json()["meta"]["total"] >= 2


class TestKeyUniqueness:
    """Tests supplementaires : unicite des cles generees"""

    def test_keys_are_unique_across_users(self, client, pro_user_token):
        import services.auth_service as auth_svc

        response1 = client.post(API_KEYS_URL, headers=auth_headers(pro_user_token))

        client.post(
            "/api/v1/auth/register",
            json={
                "email": "pro2@example.com",
                "password": "Pro2Pass123!",
                "name": "Pro User 2",
            },
        )
        users = auth_svc.load_users()
        for user_id, user_data in users.items():
            if user_data.get("email") == "pro2@example.com":
                user_data["tier"] = "pro"
                user_data["plan"] = "pro"
        auth_svc.save_users(users)

        login_response = client.post(
            "/api/v1/auth/login",
            json={"email": "pro2@example.com", "password": "Pro2Pass123!"},
        )
        token2 = login_response.json()["data"]["access_token"]

        response2 = client.post(API_KEYS_URL, headers=auth_headers(token2))

        key1 = response1.json()["data"]["key"]
        key2 = response2.json()["data"]["key"]
        assert key1 != key2, "Les cles de differents utilisateurs doivent etre uniques"
