"""Tests for the Vorim sync and async clients."""

from __future__ import annotations

import httpx
import pytest
import respx

from vorim import AsyncVorim, Vorim, VorimError
from vorim.types import AuditEventInput, RateLimit

from .conftest import (
    AGENT_DATA,
    AGENT_ID,
    API_KEY,
    AUDIT_RESPONSE,
    BASE_URL,
    PERMISSION_CHECK_ALLOWED,
    PERMISSION_CHECK_DENIED,
    REGISTRATION_DATA,
    TRUST_RECORD_DATA,
)


# ── Sync Client ──────────────────────────────────────────────────────────


class TestVorimSync:
    def _client(self) -> Vorim:
        return Vorim(api_key=API_KEY, base_url=BASE_URL)

    @respx.mock
    def test_register(self):
        respx.post(f"{BASE_URL}/v1/agents").mock(
            return_value=httpx.Response(200, json=REGISTRATION_DATA)
        )
        with self._client() as v:
            result = v.register(
                name="test-agent",
                capabilities=["search"],
                scopes=["agent:read", "agent:execute"],
            )
        assert result.agent.agent_id == AGENT_ID
        assert result.private_key == "ed25519-private-key"
        assert result.key_fingerprint == "fp:abc123"

    @respx.mock
    def test_register_with_description(self):
        respx.post(f"{BASE_URL}/v1/agents").mock(
            return_value=httpx.Response(200, json=REGISTRATION_DATA)
        )
        with self._client() as v:
            result = v.register(
                name="test-agent",
                capabilities=["search"],
                scopes=["agent:read"],
                description="A test agent",
            )
        assert result.agent.name == "test-agent"
        body = respx.calls[0].request.content
        assert b'"description"' in body

    @respx.mock
    def test_verify(self):
        respx.get(f"{BASE_URL}/v1/trust/verify/{AGENT_ID}").mock(
            return_value=httpx.Response(200, json=TRUST_RECORD_DATA)
        )
        with self._client() as v:
            trust = v.verify(AGENT_ID)
        assert trust.agent_id == AGENT_ID
        assert trust.trust_score == 85.0
        assert trust.status == "active"
        assert trust.revocation_status is False

    @respx.mock
    def test_get_agent(self):
        respx.get(f"{BASE_URL}/v1/agents/{AGENT_ID}").mock(
            return_value=httpx.Response(200, json={"data": AGENT_DATA})
        )
        with self._client() as v:
            agent = v.get_agent(AGENT_ID)
        assert agent.agent_id == AGENT_ID
        assert agent.trust_score == 85.0

    @respx.mock
    def test_list_agents(self):
        respx.get(f"{BASE_URL}/v1/agents").mock(
            return_value=httpx.Response(
                200, json={"data": {"agents": [AGENT_DATA], "meta": {"total": 1}}}
            )
        )
        with self._client() as v:
            result = v.list_agents(page=1, per_page=10, status="active")
        assert len(result["agents"]) == 1

    @respx.mock
    def test_revoke(self):
        respx.delete(f"{BASE_URL}/v1/agents/{AGENT_ID}").mock(
            return_value=httpx.Response(200, json={"data": None})
        )
        with self._client() as v:
            v.revoke(AGENT_ID)

    @respx.mock
    def test_check_allowed(self):
        respx.post(f"{BASE_URL}/v1/agents/{AGENT_ID}/permissions/verify").mock(
            return_value=httpx.Response(200, json=PERMISSION_CHECK_ALLOWED)
        )
        with self._client() as v:
            result = v.check(AGENT_ID, "agent:execute")
        assert result.allowed is True
        assert result.scope == "agent:execute"

    @respx.mock
    def test_check_denied(self):
        respx.post(f"{BASE_URL}/v1/agents/{AGENT_ID}/permissions/verify").mock(
            return_value=httpx.Response(200, json=PERMISSION_CHECK_DENIED)
        )
        with self._client() as v:
            result = v.check(AGENT_ID, "agent:execute")
        assert result.allowed is False
        assert result.reason == "Scope not granted"

    @respx.mock
    def test_grant(self):
        respx.post(f"{BASE_URL}/v1/agents/{AGENT_ID}/permissions").mock(
            return_value=httpx.Response(200, json={"data": {"granted": True}})
        )
        with self._client() as v:
            result = v.grant(AGENT_ID, "agent:execute")
        assert result["granted"] is True

    @respx.mock
    def test_grant_with_rate_limit(self):
        respx.post(f"{BASE_URL}/v1/agents/{AGENT_ID}/permissions").mock(
            return_value=httpx.Response(200, json={"data": {"granted": True}})
        )
        with self._client() as v:
            result = v.grant(
                AGENT_ID,
                "agent:execute",
                valid_until="2026-12-31T23:59:59Z",
                rate_limit=RateLimit(max=100, window="1h"),
            )
        assert result["granted"] is True
        body = respx.calls[0].request.content
        assert b'"rate_limit"' in body

    @respx.mock
    def test_emit(self):
        respx.post(f"{BASE_URL}/v1/audit/events").mock(
            return_value=httpx.Response(200, json=AUDIT_RESPONSE)
        )
        with self._client() as v:
            result = v.emit(
                agent_id=AGENT_ID,
                event_type="tool_call",
                action="search",
                result="success",
            )
        assert result["ingested"] == 1

    @respx.mock
    def test_emit_batch(self):
        respx.post(f"{BASE_URL}/v1/audit/events").mock(
            return_value=httpx.Response(200, json={"data": {"ingested": 2}})
        )
        events = [
            AuditEventInput(
                agent_id=AGENT_ID,
                event_type="tool_call",
                action="search",
                result="success",
            ),
            AuditEventInput(
                agent_id=AGENT_ID,
                event_type="tool_call",
                action="write",
                result="success",
            ),
        ]
        with self._client() as v:
            result = v.emit_batch(events)
        assert result["ingested"] == 2

    @respx.mock
    def test_error_handling(self):
        respx.get(f"{BASE_URL}/v1/agents/nonexistent").mock(
            return_value=httpx.Response(
                404,
                json={
                    "error": {
                        "code": "AGENT_NOT_FOUND",
                        "message": "Agent not found",
                    }
                },
            )
        )
        with self._client() as v:
            with pytest.raises(VorimError) as exc_info:
                v.get_agent("nonexistent")
        assert exc_info.value.status == 404
        assert exc_info.value.code == "AGENT_NOT_FOUND"
        assert exc_info.value.message == "Agent not found"

    @respx.mock
    def test_error_no_json_body(self):
        respx.get(f"{BASE_URL}/v1/agents/bad").mock(
            return_value=httpx.Response(500, text="Internal Server Error")
        )
        with self._client() as v:
            with pytest.raises(VorimError) as exc_info:
                v.get_agent("bad")
        assert exc_info.value.status == 500
        assert exc_info.value.code == "UNKNOWN_ERROR"

    def test_context_manager(self):
        v = Vorim(api_key=API_KEY, base_url=BASE_URL)
        with v:
            pass  # should not raise

    def test_headers(self):
        v = Vorim(api_key=API_KEY, base_url=BASE_URL)
        headers = v._client.headers
        assert headers["authorization"] == f"Bearer {API_KEY}"
        assert headers["user-agent"] == "vorim-python/0.1.0"
        v.close()


# ── Async Client ─────────────────────────────────────────────────────────


class TestAsyncVorim:
    def _client(self) -> AsyncVorim:
        return AsyncVorim(api_key=API_KEY, base_url=BASE_URL)

    @respx.mock
    @pytest.mark.asyncio
    async def test_register(self):
        respx.post(f"{BASE_URL}/v1/agents").mock(
            return_value=httpx.Response(200, json=REGISTRATION_DATA)
        )
        async with self._client() as v:
            result = await v.register(
                name="test-agent",
                capabilities=["search"],
                scopes=["agent:read", "agent:execute"],
            )
        assert result.agent.agent_id == AGENT_ID

    @respx.mock
    @pytest.mark.asyncio
    async def test_verify(self):
        respx.get(f"{BASE_URL}/v1/trust/verify/{AGENT_ID}").mock(
            return_value=httpx.Response(200, json=TRUST_RECORD_DATA)
        )
        async with self._client() as v:
            trust = await v.verify(AGENT_ID)
        assert trust.trust_score == 85.0

    @respx.mock
    @pytest.mark.asyncio
    async def test_check(self):
        respx.post(f"{BASE_URL}/v1/agents/{AGENT_ID}/permissions/verify").mock(
            return_value=httpx.Response(200, json=PERMISSION_CHECK_ALLOWED)
        )
        async with self._client() as v:
            result = await v.check(AGENT_ID, "agent:execute")
        assert result.allowed is True

    @respx.mock
    @pytest.mark.asyncio
    async def test_emit(self):
        respx.post(f"{BASE_URL}/v1/audit/events").mock(
            return_value=httpx.Response(200, json=AUDIT_RESPONSE)
        )
        async with self._client() as v:
            result = await v.emit(
                agent_id=AGENT_ID,
                event_type="tool_call",
                action="search",
                result="success",
            )
        assert result["ingested"] == 1

    @respx.mock
    @pytest.mark.asyncio
    async def test_error_handling(self):
        respx.get(f"{BASE_URL}/v1/agents/bad").mock(
            return_value=httpx.Response(
                404,
                json={
                    "error": {
                        "code": "AGENT_NOT_FOUND",
                        "message": "Agent not found",
                    }
                },
            )
        )
        async with self._client() as v:
            with pytest.raises(VorimError):
                await v.get_agent("bad")

    @respx.mock
    @pytest.mark.asyncio
    async def test_revoke(self):
        respx.delete(f"{BASE_URL}/v1/agents/{AGENT_ID}").mock(
            return_value=httpx.Response(200, json={"data": None})
        )
        async with self._client() as v:
            await v.revoke(AGENT_ID)

    @respx.mock
    @pytest.mark.asyncio
    async def test_grant(self):
        respx.post(f"{BASE_URL}/v1/agents/{AGENT_ID}/permissions").mock(
            return_value=httpx.Response(200, json={"data": {"granted": True}})
        )
        async with self._client() as v:
            result = await v.grant(AGENT_ID, "agent:execute")
        assert result["granted"] is True
