"""Tests for the asynchronous AsyncBaponi client."""

from __future__ import annotations

import os
from unittest.mock import patch

import httpx
import pytest
import respx

from baponi import AsyncBaponi, BaponiError, SandboxResult
from baponi.exceptions import AuthenticationError, ServerError

FAKE_KEY = "sk-test-key-for-unit-tests"
EXECUTE_URL = "https://api.baponi.ai/v1/sandbox/execute"

MOCK_SUCCESS_RESPONSE = {
    "success": True,
    "stdout": "Hello!\n",
    "stderr": "",
    "exit_code": 0,
    "error": None,
}


class TestAsyncClientInit:
    def test_explicit_api_key(self):
        client = AsyncBaponi(api_key=FAKE_KEY)
        assert client._api_key == FAKE_KEY

    def test_env_var_api_key(self):
        with patch.dict(os.environ, {"BAPONI_API_KEY": FAKE_KEY}):
            client = AsyncBaponi()
            assert client._api_key == FAKE_KEY

    def test_missing_api_key_raises(self):
        with patch.dict(os.environ, {}, clear=True):
            os.environ.pop("BAPONI_API_KEY", None)
            with pytest.raises(BaponiError, match="API key is required"):
                AsyncBaponi()

    def test_external_client_not_closed(self):
        external = httpx.AsyncClient()
        client = AsyncBaponi(api_key=FAKE_KEY, http_client=external)
        assert not client._owns_client


class TestAsyncExecute:
    @respx.mock
    @pytest.mark.asyncio
    async def test_successful_execution(self):
        respx.post(EXECUTE_URL).mock(
            return_value=httpx.Response(200, json=MOCK_SUCCESS_RESPONSE)
        )
        async with AsyncBaponi(api_key=FAKE_KEY) as client:
            result = await client.execute("print('Hello!')")

        assert isinstance(result, SandboxResult)
        assert result.success is True
        assert result.stdout == "Hello!\n"

    @respx.mock
    @pytest.mark.asyncio
    async def test_sends_correct_headers(self):
        route = respx.post(EXECUTE_URL).mock(
            return_value=httpx.Response(200, json=MOCK_SUCCESS_RESPONSE)
        )
        async with AsyncBaponi(api_key=FAKE_KEY) as client:
            await client.execute("x")

        assert route.calls[0].request.headers["authorization"] == f"Bearer {FAKE_KEY}"
        assert route.calls[0].request.headers["user-agent"].startswith("baponi-python/")

    @respx.mock
    @pytest.mark.asyncio
    async def test_sends_env_vars_in_body(self):
        route = respx.post(EXECUTE_URL).mock(
            return_value=httpx.Response(200, json=MOCK_SUCCESS_RESPONSE)
        )
        async with AsyncBaponi(api_key=FAKE_KEY) as client:
            await client.execute("print(1)", env_vars={"MY_VAR": "test"})

        import json
        data = json.loads(route.calls[0].request.read())
        assert data["env_vars"] == {"MY_VAR": "test"}

    @respx.mock
    @pytest.mark.asyncio
    async def test_401_raises_authentication_error(self):
        respx.post(EXECUTE_URL).mock(
            return_value=httpx.Response(
                401, json={"error": "unauthorized", "message": "Invalid API key"}
            )
        )
        async with AsyncBaponi(api_key=FAKE_KEY, max_retries=0) as client:
            with pytest.raises(AuthenticationError):
                await client.execute("x")

    @respx.mock
    @pytest.mark.asyncio
    async def test_retries_on_503(self):
        route = respx.post(EXECUTE_URL).mock(
            side_effect=[
                httpx.Response(
                    503, json={"error": "service_unavailable", "message": "No capacity"}
                ),
                httpx.Response(200, json=MOCK_SUCCESS_RESPONSE),
            ]
        )
        async with AsyncBaponi(api_key=FAKE_KEY, max_retries=1) as client:
            result = await client.execute("x")

        assert result.success is True
        assert len(route.calls) == 2

    @respx.mock
    @pytest.mark.asyncio
    async def test_context_manager_closes_client(self):
        respx.post(EXECUTE_URL).mock(
            return_value=httpx.Response(200, json=MOCK_SUCCESS_RESPONSE)
        )
        client = AsyncBaponi(api_key=FAKE_KEY)
        async with client:
            await client.execute("x")
        assert client._client.is_closed
