"""Tests for BanditoClient in bandito/api.py."""

import os
from unittest.mock import patch

import pytest

from bandito.api import BanditoClient


class _FakeResponse:
    """Minimal httpx.Response stand-in."""

    def __init__(self, status_code, json_data=None, text=""):
        self.status_code = status_code
        self._json = json_data
        self.text = text

    def json(self):
        return self._json


# -- Init --


def test_init_requires_api_key():
    with patch.dict(os.environ, {}, clear=True), \
         patch("bandito.api._resolve_bandito_api_key", return_value=None):
        with pytest.raises(RuntimeError, match="BANDITO_API_KEY is required"):
            BanditoClient()


def test_init_from_env():
    with patch.dict(os.environ, {"BANDITO_API_KEY": "bnd_live_test"}, clear=True):
        client = BanditoClient()
        assert client._api_key == "bnd_live_test"


def test_init_explicit_key():
    client = BanditoClient(api_key="bnd_live_explicit")
    assert client._api_key == "bnd_live_explicit"


def test_init_custom_base_url():
    client = BanditoClient(api_key="k", base_url="http://localhost:8000")
    assert client._base_url == "http://localhost:8000"


# -- Rubrics --


def test_get_rubric_success():
    client = BanditoClient(api_key="k")
    fake = _FakeResponse(200, {"id": 1, "project": "bot", "content": "## Good"})

    with patch("httpx.request", return_value=fake):
        result = client.get_rubric("bot")

    assert result["project"] == "bot"
    assert result["content"] == "## Good"


def test_get_rubric_not_found():
    client = BanditoClient(api_key="k")
    fake = _FakeResponse(404, {"detail": "Not found"})

    with patch("httpx.request", return_value=fake):
        result = client.get_rubric("missing")

    assert result is None


def test_save_rubric():
    client = BanditoClient(api_key="k")
    fake = _FakeResponse(200, {"id": 1, "project": "bot", "content": "new"})

    with patch("httpx.request", return_value=fake) as mock:
        result = client.save_rubric("bot", "new")

    assert result["content"] == "new"
    call_kwargs = mock.call_args
    assert call_kwargs.kwargs["json"] == {"content": "new"}


# -- Evaluations --


def test_write_evaluation():
    client = BanditoClient(api_key="k")
    fake = _FakeResponse(201, {"id": 1, "trace_id": "tr-1", "human_score": 1})

    with patch("httpx.request", return_value=fake):
        result = client.write_evaluation(
            {"project": "bot", "trace_id": "tr-1", "human_score": 1}
        )

    assert result["trace_id"] == "tr-1"


def test_list_evaluations():
    client = BanditoClient(api_key="k")
    fake = _FakeResponse(200, {
        "evaluations": {
            "tr-1": {"id": 1, "trace_id": "tr-1", "human_score": 1},
            "tr-2": {"id": 2, "trace_id": "tr-2", "human_score": 0},
        }
    })

    with patch("httpx.request", return_value=fake):
        result = client.list_evaluations("bot")

    assert "tr-1" in result
    assert "tr-2" in result


def test_get_evaluation_not_found():
    client = BanditoClient(api_key="k")
    fake = _FakeResponse(404, {"detail": "Not found"})

    with patch("httpx.request", return_value=fake):
        result = client.get_evaluation("tr-missing")

    assert result is None


# -- Calibrations --


def test_get_calibration():
    client = BanditoClient(api_key="k")
    fake = _FakeResponse(200, {
        "calibrated": True,
        "stale": False,
        "calibration": {"id": 1, "tpr": 0.95, "tnr": 0.92},
    })

    with patch("httpx.request", return_value=fake):
        result = client.get_calibration("bot")

    assert result["calibrated"] is True


def test_get_calibration_none():
    client = BanditoClient(api_key="k")
    fake = _FakeResponse(200, {"calibrated": False, "stale": True, "calibration": None})

    with patch("httpx.request", return_value=fake):
        result = client.get_calibration("bot")

    assert result is None


# -- Error handling --


def test_401_error():
    client = BanditoClient(api_key="k")
    fake = _FakeResponse(401, {"detail": "Invalid key"})

    with patch("httpx.request", return_value=fake):
        with pytest.raises(RuntimeError, match="Invalid BANDITO_API_KEY"):
            client.get_rubric("bot")


def test_402_error():
    client = BanditoClient(api_key="k")
    fake = _FakeResponse(402, {"detail": "Plan limit reached"})

    with patch("httpx.request", return_value=fake):
        with pytest.raises(RuntimeError, match="Plan limit reached"):
            client.save_rubric("bot", "x")


def test_timeout_error():
    import httpx

    client = BanditoClient(api_key="k")

    with patch("httpx.request", side_effect=httpx.TimeoutException("timed out")):
        with pytest.raises(RuntimeError, match="timed out"):
            client.get_rubric("bot")


def test_connection_error():
    import httpx

    client = BanditoClient(api_key="k")

    with patch("httpx.request", side_effect=httpx.ConnectError("refused")):
        with pytest.raises(RuntimeError, match="Cannot connect"):
            client.get_rubric("bot")


# -- Header verification --


def test_api_key_header_sent():
    client = BanditoClient(api_key="bnd_live_secret")
    fake = _FakeResponse(200, {"id": 1, "project": "bot", "content": "x"})

    with patch("httpx.request", return_value=fake) as mock:
        client.get_rubric("bot")

    headers = mock.call_args.kwargs["headers"]
    assert headers["X-API-Key"] == "bnd_live_secret"


def test_url_construction():
    client = BanditoClient(api_key="k", base_url="http://localhost:8000")
    fake = _FakeResponse(200, {"id": 1, "project": "bot", "content": "x"})

    with patch("httpx.request", return_value=fake) as mock:
        client.get_rubric("bot")

    url = mock.call_args.args[1]
    assert url == "http://localhost:8000/api/v1/rubrics/bot"
