"""Tests for the Gibs Python SDK."""

from __future__ import annotations

import httpx
import pytest
import respx

from gibs import (
    AsyncGibsClient,
    GibsAPIError,
    GibsAuthError,
    GibsClient,
    GibsError,
    GibsRateLimitError,
)


# --- Fixtures ---

API_KEY = "gbs_live_test123"
BASE_URL = "https://api.gibs.dev"

CLASSIFY_RESPONSE = {
    "risk_level": "high",
    "confidence": 0.92,
    "reasoning": "This system uses biometric identification in public spaces.",
    "obligations": [
        {
            "id": "ob_001",
            "description": "Conduct conformity assessment",
            "source_article": "Article 43",
            "deadline": None,
        }
    ],
    "sources": [
        {
            "article_id": "art_6",
            "title": "Article 6 - Classification rules",
            "text_excerpt": "High-risk AI systems referred to in Annex III...",
            "relevance_score": 0.95,
        }
    ],
    "request_id": "req_abc123",
    "corpus_version": "ai_act_v1.1.0",
    "processing_time_ms": 3200,
}

CHECK_RESPONSE = {
    "answer": "GDPR grants data subjects several rights under Articles 15-22.",
    "confidence": "high",
    "sources": [
        {
            "article_id": "gdpr_art15",
            "title": "Article 15 - Right of access",
            "text_excerpt": "The data subject shall have the right to obtain...",
            "relevance_score": 0.98,
        }
    ],
    "should_abstain": False,
    "abstention_reason": None,
    "request_id": "req_def456",
    "corpus_version": "gdpr_v1.0.0",
    "processing_time_ms": 2800,
}

HEALTH_RESPONSE = {
    "status": "healthy",
    "environment": "production",
    "corpus_version": "2026-02-10",
    "components": {"qdrant": "ok", "postgres": "ok", "redis": "ok"},
}

KEYS_RESPONSE = [
    {
        "id": 1,
        "name": "Production",
        "key_prefix": "gbs_live_abc",
        "is_test": False,
        "created_at": "2026-01-15T10:00:00Z",
        "last_used_at": "2026-02-13T08:30:00Z",
        "request_count": 1542,
    }
]

CREATE_KEY_RESPONSE = {
    "id": 2,
    "api_key": "gbs_live_newkey123456",
    "key_prefix": "gbs_live_new",
    "name": "CI Pipeline",
    "created_at": "2026-02-13T12:00:00Z",
}


# --- Sync client tests ---


class TestGibsClient:
    def test_init_requires_api_key(self) -> None:
        with pytest.raises(GibsError, match="No API key provided"):
            GibsClient()

    def test_init_with_api_key(self) -> None:
        client = GibsClient(api_key=API_KEY)
        assert client.api_key == API_KEY
        assert client.base_url == BASE_URL
        client.close()

    def test_init_custom_base_url(self) -> None:
        client = GibsClient(api_key=API_KEY, base_url="https://custom.api.dev/")
        assert client.base_url == "https://custom.api.dev"
        client.close()

    def test_init_from_env(self, monkeypatch: pytest.MonkeyPatch) -> None:
        monkeypatch.setenv("GIBS_API_KEY", "gbs_live_fromenv")
        client = GibsClient()
        assert client.api_key == "gbs_live_fromenv"
        client.close()

    def test_context_manager(self) -> None:
        with GibsClient(api_key=API_KEY) as client:
            assert client.api_key == API_KEY

    @respx.mock
    def test_classify(self) -> None:
        respx.post(f"{BASE_URL}/v1/classify").mock(
            return_value=httpx.Response(200, json=CLASSIFY_RESPONSE)
        )

        with GibsClient(api_key=API_KEY) as client:
            result = client.classify("Facial recognition for airport security")

        assert result.risk_level == "high"
        assert result.confidence == 0.92
        assert len(result.obligations) == 1
        assert result.obligations[0].id == "ob_001"
        assert len(result.sources) == 1
        assert result.request_id == "req_abc123"

    @respx.mock
    def test_classify_with_params(self) -> None:
        route = respx.post(f"{BASE_URL}/v1/classify").mock(
            return_value=httpx.Response(200, json=CLASSIFY_RESPONSE)
        )

        with GibsClient(api_key=API_KEY) as client:
            client.classify(
                "CV screening tool",
                data_types=["personal", "employment"],
                decision_scope="hiring",
                sector="recruitment",
            )

        request_body = route.calls[0].request.content
        assert b"data_types" in request_body
        assert b"decision_scope" in request_body

    @respx.mock
    def test_check(self) -> None:
        respx.post(f"{BASE_URL}/v1/check").mock(
            return_value=httpx.Response(200, json=CHECK_RESPONSE)
        )

        with GibsClient(api_key=API_KEY) as client:
            result = client.check("What are GDPR data subject rights?")

        assert result.confidence == "high"
        assert result.should_abstain is False
        assert len(result.sources) == 1
        assert "Article 15" in result.sources[0].title

    @respx.mock
    def test_health(self) -> None:
        respx.get(f"{BASE_URL}/v1/health").mock(
            return_value=httpx.Response(200, json=HEALTH_RESPONSE)
        )

        with GibsClient(api_key=API_KEY) as client:
            result = client.health()

        assert result.status == "healthy"
        assert result.components["qdrant"] == "ok"

    @respx.mock
    def test_list_keys(self) -> None:
        respx.get(f"{BASE_URL}/v1/account/keys").mock(
            return_value=httpx.Response(200, json=KEYS_RESPONSE)
        )

        with GibsClient(api_key=API_KEY) as client:
            keys = client.list_keys()

        assert len(keys) == 1
        assert keys[0].name == "Production"
        assert keys[0].request_count == 1542

    @respx.mock
    def test_create_key(self) -> None:
        respx.post(f"{BASE_URL}/v1/account/keys").mock(
            return_value=httpx.Response(200, json=CREATE_KEY_RESPONSE)
        )

        with GibsClient(api_key=API_KEY) as client:
            key = client.create_key("CI Pipeline")

        assert key.api_key == "gbs_live_newkey123456"
        assert key.name == "CI Pipeline"

    @respx.mock
    def test_delete_key(self) -> None:
        respx.delete(f"{BASE_URL}/v1/account/keys/1").mock(
            return_value=httpx.Response(200, json={"status": "revoked"})
        )

        with GibsClient(api_key=API_KEY) as client:
            result = client.delete_key(1)

        assert result.status == "revoked"

    # --- Error handling ---

    @respx.mock
    def test_auth_error(self) -> None:
        respx.post(f"{BASE_URL}/v1/classify").mock(
            return_value=httpx.Response(401, json={"detail": "Invalid API key"})
        )

        with GibsClient(api_key=API_KEY) as client:
            with pytest.raises(GibsAuthError) as exc_info:
                client.classify("test")

        assert exc_info.value.status_code == 401
        assert "Invalid API key" in str(exc_info.value)

    @respx.mock
    def test_rate_limit_error(self) -> None:
        respx.post(f"{BASE_URL}/v1/classify").mock(
            return_value=httpx.Response(
                429,
                json={"detail": "Rate limit exceeded"},
                headers={"Retry-After": "5"},
            )
        )

        with GibsClient(api_key=API_KEY, max_retries=0) as client:
            with pytest.raises(GibsRateLimitError) as exc_info:
                client.classify("test")

        assert exc_info.value.retry_after == 5.0

    @respx.mock
    def test_api_error(self) -> None:
        respx.post(f"{BASE_URL}/v1/classify").mock(
            return_value=httpx.Response(500, json={"detail": "Internal server error"})
        )

        with GibsClient(api_key=API_KEY, max_retries=0) as client:
            with pytest.raises(GibsAPIError) as exc_info:
                client.classify("test")

        assert exc_info.value.status_code == 500

    @respx.mock
    def test_retry_on_rate_limit(self) -> None:
        route = respx.post(f"{BASE_URL}/v1/classify")
        route.side_effect = [
            httpx.Response(429, json={"detail": "Rate limit"}, headers={"Retry-After": "0.01"}),
            httpx.Response(200, json=CLASSIFY_RESPONSE),
        ]

        with GibsClient(api_key=API_KEY, max_retries=1) as client:
            result = client.classify("test")

        assert result.risk_level == "high"
        assert route.call_count == 2


# --- Async client tests ---


class TestAsyncGibsClient:
    def test_init_requires_api_key(self) -> None:
        with pytest.raises(GibsError, match="No API key provided"):
            AsyncGibsClient()

    @respx.mock
    @pytest.mark.asyncio
    async def test_classify(self) -> None:
        respx.post(f"{BASE_URL}/v1/classify").mock(
            return_value=httpx.Response(200, json=CLASSIFY_RESPONSE)
        )

        async with AsyncGibsClient(api_key=API_KEY) as client:
            result = await client.classify("Facial recognition for airport security")

        assert result.risk_level == "high"
        assert len(result.obligations) == 1

    @respx.mock
    @pytest.mark.asyncio
    async def test_check(self) -> None:
        respx.post(f"{BASE_URL}/v1/check").mock(
            return_value=httpx.Response(200, json=CHECK_RESPONSE)
        )

        async with AsyncGibsClient(api_key=API_KEY) as client:
            result = await client.check("What are GDPR data subject rights?")

        assert result.confidence == "high"
        assert result.should_abstain is False

    @respx.mock
    @pytest.mark.asyncio
    async def test_health(self) -> None:
        respx.get(f"{BASE_URL}/v1/health").mock(
            return_value=httpx.Response(200, json=HEALTH_RESPONSE)
        )

        async with AsyncGibsClient(api_key=API_KEY) as client:
            result = await client.health()

        assert result.status == "healthy"

    @respx.mock
    @pytest.mark.asyncio
    async def test_auth_error(self) -> None:
        respx.post(f"{BASE_URL}/v1/classify").mock(
            return_value=httpx.Response(401, json={"detail": "Invalid API key"})
        )

        async with AsyncGibsClient(api_key=API_KEY) as client:
            with pytest.raises(GibsAuthError):
                await client.classify("test")

    @respx.mock
    @pytest.mark.asyncio
    async def test_retry_on_rate_limit(self) -> None:
        route = respx.post(f"{BASE_URL}/v1/classify")
        route.side_effect = [
            httpx.Response(429, json={"detail": "Rate limit"}, headers={"Retry-After": "0.01"}),
            httpx.Response(200, json=CLASSIFY_RESPONSE),
        ]

        async with AsyncGibsClient(api_key=API_KEY, max_retries=1) as client:
            result = await client.classify("test")

        assert result.risk_level == "high"
        assert route.call_count == 2
