"""
Tests pour DELETE /api/v1/admin/api-keys/{key_id}
Couvre les AC 1-8 de la story 3.3 : Admin - Revocation API Key (Any User)
"""

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


API_KEYS_URL = "/api/v1/api-keys"
ADMIN_API_KEYS_URL = "/api/v1/admin/api-keys"
ADMIN_LOGIN_URL = "/api/v1/admin/login"
REGISTER_URL = "/api/v1/auth/register"
LOGIN_URL = "/api/v1/auth/login"


@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 admin_password():
    return "admin-secret"


@pytest.fixture()
def client(users_file: Path, monkeypatch):
    """TestClient avec stockage JSON isole et rate limiting desactive."""
    import services.auth_service as auth_svc
    from middleware.rate_limiting import RateLimitManager

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

    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 client_with_admin(client, admin_password, monkeypatch):
    """Same as client but with admin credentials patched in admin_routes."""
    import routes.admin_routes as admin_routes_mod

    monkeypatch.setattr(admin_routes_mod, "ADMIN_USERNAME", "admin")
    monkeypatch.setattr(admin_routes_mod, "ADMIN_PASSWORD", admin_password)
    monkeypatch.setattr(admin_routes_mod, "ADMIN_PASSWORD_HASH", None)
    return client


@pytest.fixture()
def admin_token(client_with_admin, admin_password):
    """Get admin Bearer token."""
    r = client_with_admin.post(ADMIN_LOGIN_URL, json={"password": admin_password})
    assert r.status_code == 200, r.text
    return r.json()["access_token"]


@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(
        REGISTER_URL,
        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(
        LOGIN_URL,
        json={"email": "pro@example.com", "password": "ProPass123!"},
    )
    assert login_response.status_code == 200
    return login_response.json()["data"]["access_token"]


@pytest.fixture()
def another_pro_user_token(client):
    """Cree un deuxieme utilisateur Pro et retourne son token JWT."""
    import services.auth_service as auth_svc

    response = client.post(
        REGISTER_URL,
        json={
            "email": "pro2@example.com",
            "password": "Pro2Pass123!",
            "name": "Pro User 2",
        },
    )
    assert response.status_code == 201

    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(
        LOGIN_URL,
        json={"email": "pro2@example.com", "password": "Pro2Pass123!"},
    )
    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}"}


@pytest.fixture()
def created_api_key_by_pro_user(client, pro_user_token):
    """Cree une cle API pour l'utilisateur Pro et retourne son ID et sa cle."""
    response = client.post(
        API_KEYS_URL,
        json={"name": "Test Key to Revoke by Admin"},
        headers=auth_headers(pro_user_token),
    )
    assert response.status_code == 201
    data = response.json()["data"]
    return data["id"], data["key"]


@pytest.fixture()
def created_api_key_by_another_pro_user(client, another_pro_user_token):
    """Cree une cle API pour un autre utilisateur Pro et retourne son ID."""
    response = client.post(
        API_KEYS_URL,
        json={"name": "Another User Key"},
        headers=auth_headers(another_pro_user_token),
    )
    assert response.status_code == 201
    data = response.json()["data"]
    return data["id"], data["key"]


class TestAdminRevokeApiKeySuccess:
    """AC1, AC2, AC3, AC4, AC5, AC8 : revocation reussie par admin"""

    def test_returns_200_on_success(
        self, client_with_admin, admin_token, created_api_key_by_pro_user
    ):
        """AC1: DELETE retourne 200."""
        key_id, _ = created_api_key_by_pro_user
        response = client_with_admin.delete(
            f"{ADMIN_API_KEYS_URL}/{key_id}",
            headers=auth_headers(admin_token),
        )
        assert response.status_code == 200

    def test_response_format_has_data_and_meta(
        self, client_with_admin, admin_token, created_api_key_by_pro_user
    ):
        """AC5: Reponse au format {data: {...}, meta: {}}."""
        key_id, _ = created_api_key_by_pro_user
        response = client_with_admin.delete(
            f"{ADMIN_API_KEYS_URL}/{key_id}",
            headers=auth_headers(admin_token),
        )
        body = response.json()
        assert "data" in body
        assert "meta" in body
        assert body["data"]["revoked"] is True
        assert "revoked_at" in body["data"]
        assert "id" in body["data"]

    def test_response_includes_owner_user_id(
        self, client_with_admin, admin_token, created_api_key_by_pro_user
    ):
        """AC5: La reponse inclut owner_user_id pour tracabilite."""
        key_id, _ = created_api_key_by_pro_user
        response = client_with_admin.delete(
            f"{ADMIN_API_KEYS_URL}/{key_id}",
            headers=auth_headers(admin_token),
        )
        body = response.json()
        assert "owner_user_id" in body["data"]

    def test_soft_delete_sets_is_active_false(
        self, client_with_admin, admin_token, created_api_key_by_pro_user
    ):
        """AC4: La cle est marquee is_active=False (soft delete)."""
        key_id, _ = created_api_key_by_pro_user
        client_with_admin.delete(
            f"{ADMIN_API_KEYS_URL}/{key_id}",
            headers=auth_headers(admin_token),
        )
        from database.connection import get_sync_session
        from database.models import ApiKey

        with get_sync_session() as session:
            api_key = session.query(ApiKey).filter(ApiKey.id == key_id).first()
            assert api_key is not None
            assert api_key.is_active is False

    def test_admin_can_revoke_any_key_not_own(
        self, client_with_admin, admin_token, created_api_key_by_another_pro_user
    ):
        """AC3: L'admin peut revoquer la cle de N'IMPORTE quel utilisateur."""
        key_id, _ = created_api_key_by_another_pro_user
        response = client_with_admin.delete(
            f"{ADMIN_API_KEYS_URL}/{key_id}",
            headers=auth_headers(admin_token),
        )
        assert response.status_code == 200
        body = response.json()
        assert body["data"]["revoked"] is True

    def test_revoked_key_cannot_authenticate(
        self, client_with_admin, admin_token, created_api_key_by_pro_user
    ):
        """AC4: La cle revoquee ne peut plus authentifier."""
        key_id, raw_key = created_api_key_by_pro_user
        client_with_admin.delete(
            f"{ADMIN_API_KEYS_URL}/{key_id}",
            headers=auth_headers(admin_token),
        )
        from database.connection import get_sync_session
        from database.models import ApiKey
        import hashlib

        key_hash = hashlib.sha256(raw_key.encode()).hexdigest()
        with get_sync_session() as session:
            api_key = (
                session.query(ApiKey)
                .filter(
                    ApiKey.key_hash == key_hash,
                    ApiKey.is_active == True,
                )
                .first()
            )
            assert api_key is None


class TestAdminRevokeApiKeyWithReason:
    """AC6, AC8 : revocation avec raison optionnelle"""

    def test_revoke_with_reason_returns_reason_in_response(
        self, client_with_admin, admin_token, created_api_key_by_pro_user
    ):
        """AC8: La raison est retournee dans la reponse."""
        key_id, _ = created_api_key_by_pro_user
        response = client_with_admin.request(
            "DELETE",
            f"{ADMIN_API_KEYS_URL}/{key_id}",
            content=json.dumps({"reason": "Violation des conditions d'utilisation"}),
            headers={**auth_headers(admin_token), "Content-Type": "application/json"},
        )
        assert response.status_code == 200
        body = response.json()
        assert body["data"]["reason"] == "Violation des conditions d'utilisation"

    def test_revoke_without_reason_returns_null_reason(
        self, client_with_admin, admin_token, created_api_key_by_pro_user
    ):
        """AC8: Sans raison, le champ reason est null."""
        key_id, _ = created_api_key_by_pro_user
        response = client_with_admin.delete(
            f"{ADMIN_API_KEYS_URL}/{key_id}",
            headers=auth_headers(admin_token),
        )
        assert response.status_code == 200
        body = response.json()
        assert body["data"]["reason"] is None


class TestAdminRevokeApiKeyAuditLogging:
    """AC6 : audit logging"""

    def test_audit_logging_called(
        self, client_with_admin, admin_token, created_api_key_by_pro_user, caplog
    ):
        """AC6: L'action est journalisee avec admin_id, key_id, owner_user_id, reason."""
        key_id, _ = created_api_key_by_pro_user
        with caplog.at_level(logging.INFO):
            response = client_with_admin.request(
                "DELETE",
                f"{ADMIN_API_KEYS_URL}/{key_id}",
                content=json.dumps({"reason": "Test audit"}),
                headers={
                    **auth_headers(admin_token),
                    "Content-Type": "application/json",
                },
            )
        assert response.status_code == 200
        found_log = False
        for record in caplog.records:
            if "admin_api_key_revoked" in record.message:
                found_log = True
                break
        assert found_log, "admin_api_key_revoked log not found"

    def test_audit_logging_includes_admin_id(
        self, client_with_admin, admin_token, created_api_key_by_pro_user, caplog
    ):
        """AC6: L'audit log doit inclure admin_id."""
        key_id, _ = created_api_key_by_pro_user
        with caplog.at_level(logging.INFO):
            response = client_with_admin.request(
                "DELETE",
                f"{ADMIN_API_KEYS_URL}/{key_id}",
                content=json.dumps({"reason": "Test admin_id"}),
                headers={
                    **auth_headers(admin_token),
                    "Content-Type": "application/json",
                },
            )
        assert response.status_code == 200
        found_admin_id = False
        for record in caplog.records:
            if "admin_api_key_revoked" in record.message:
                if hasattr(record, "admin_id") or "admin_id" in str(record.__dict__):
                    found_admin_id = True
                    break
        assert found_admin_id, "admin_id not found in audit log"


class TestAdminRevokeApiKeyErrors:
    """AC2, AC7 : cas d'erreur"""

    def test_returns_401_without_admin_auth(
        self, client_with_admin, created_api_key_by_pro_user
    ):
        """AC2: Sans authentification admin, retourne 401."""
        key_id, _ = created_api_key_by_pro_user
        response = client_with_admin.delete(
            f"{ADMIN_API_KEYS_URL}/{key_id}",
        )
        assert response.status_code == 401

    def test_returns_401_with_invalid_admin_token(
        self, client_with_admin, created_api_key_by_pro_user
    ):
        """AC2: Avec token admin invalide, retourne 401."""
        key_id, _ = created_api_key_by_pro_user
        response = client_with_admin.delete(
            f"{ADMIN_API_KEYS_URL}/{key_id}",
            headers={"Authorization": "Bearer invalid_token"},
        )
        assert response.status_code == 401

    def test_returns_401_with_pro_user_token_not_admin(
        self, client_with_admin, pro_user_token, created_api_key_by_pro_user
    ):
        """AC2: Un token utilisateur Pro (pas admin) ne peut pas acceder."""
        key_id, _ = created_api_key_by_pro_user
        response = client_with_admin.delete(
            f"{ADMIN_API_KEYS_URL}/{key_id}",
            headers=auth_headers(pro_user_token),
        )
        assert response.status_code == 401

    def test_returns_404_for_nonexistent_key(self, client_with_admin, admin_token):
        """AC7: Cle inexistante retourne 404."""
        response = client_with_admin.delete(
            f"{ADMIN_API_KEYS_URL}/nonexistent-key-id",
            headers=auth_headers(admin_token),
        )
        assert response.status_code == 404
        body = response.json()
        assert body["error"] == "API_KEY_NOT_FOUND"
        assert body["message"]  # Message non vide

    def test_returns_404_for_already_revoked_key(
        self, client_with_admin, admin_token, created_api_key_by_pro_user
    ):
        """Une cle deja revoquee retourne 404 (deja is_active=False)."""
        key_id, _ = created_api_key_by_pro_user
        client_with_admin.delete(
            f"{ADMIN_API_KEYS_URL}/{key_id}",
            headers=auth_headers(admin_token),
        )
        response = client_with_admin.delete(
            f"{ADMIN_API_KEYS_URL}/{key_id}",
            headers=auth_headers(admin_token),
        )
        assert response.status_code == 404
