# tests/test_client.py
"""Tests for the sync Ihyee client."""
from __future__ import annotations

import json
import os

import pytest
import respx
from httpx import Response

from ihyee import Ihyee
from ihyee.exceptions import AuthError, BadRequestError, RateLimitError, ServerError
from ihyee.models import FetchResponse, SearchResponse

from conftest import (
    MOCK_ERROR_400,
    MOCK_ERROR_401,
    MOCK_ERROR_429,
    MOCK_FETCH_RESPONSE,
    MOCK_SEARCH_RESPONSE,
)

BASE = "https://mock.ihyee.test"


class TestClientInit:
    def test_explicit_api_key(self):
        client = Ihyee(api_key="test_key", base_url=BASE)
        assert client is not None
        client.close()

    def test_api_key_from_env(self, monkeypatch):
        monkeypatch.setenv("IHYEE_API_KEY", "env_key")
        client = Ihyee(base_url=BASE)
        assert client is not None
        client.close()

    def test_missing_api_key_raises(self, monkeypatch):
        monkeypatch.delenv("IHYEE_API_KEY", raising=False)
        with pytest.raises(AuthError, match="No API key provided"):
            Ihyee(base_url=BASE)

    def test_context_manager(self):
        with Ihyee(api_key="key", base_url=BASE) as client:
            assert client is not None


class TestSearch:
    @respx.mock
    def test_search_returns_typed_response(self):
        respx.post(f"{BASE}/v1/search").mock(
            return_value=Response(200, json=MOCK_SEARCH_RESPONSE)
        )
        with Ihyee(api_key="key", base_url=BASE) as client:
            result = client.search("test query")
        assert isinstance(result, SearchResponse)
        assert result.results_count == 1
        assert result.content[0].text.summary == "A short summary."

    @respx.mock
    def test_search_sends_correct_payload(self):
        route = respx.post(f"{BASE}/v1/search").mock(
            return_value=Response(200, json=MOCK_SEARCH_RESPONSE)
        )
        with Ihyee(api_key="key", base_url=BASE) as client:
            client.search("ai research", max_results=3, content_mode="summary", before="2026-01-01")
        payload = route.calls[0].request.content
        body = json.loads(payload)
        assert body["query"] == "ai research"
        assert body["max_results"] == 3
        assert body["content_mode"] == "summary"
        assert body["before"] == "2026-01-01"

    @respx.mock
    def test_search_omits_none_params(self):
        route = respx.post(f"{BASE}/v1/search").mock(
            return_value=Response(200, json=MOCK_SEARCH_RESPONSE)
        )
        with Ihyee(api_key="key", base_url=BASE) as client:
            client.search("test")
        body = json.loads(route.calls[0].request.content)
        assert "before" not in body
        assert "must_have" not in body


class TestFetch:
    @respx.mock
    def test_fetch_returns_typed_response(self):
        respx.post(f"{BASE}/v1/fetch").mock(
            return_value=Response(200, json=MOCK_FETCH_RESPONSE)
        )
        with Ihyee(api_key="key", base_url=BASE) as client:
            result = client.fetch(["https://example.com"])
        assert isinstance(result, FetchResponse)
        assert result.meta.pages_fetched == 1


class TestRender:
    @respx.mock
    def test_render_returns_typed_response(self):
        respx.post(f"{BASE}/v1/render").mock(
            return_value=Response(200, json=MOCK_FETCH_RESPONSE)
        )
        with Ihyee(api_key="key", base_url=BASE) as client:
            result = client.render("https://spa.example.com")
        assert isinstance(result, FetchResponse)


class TestErrors:
    @respx.mock
    def test_401_raises_auth_error(self):
        respx.post(f"{BASE}/v1/search").mock(
            return_value=Response(401, json=MOCK_ERROR_401)
        )
        with Ihyee(api_key="bad", base_url=BASE) as client:
            with pytest.raises(AuthError) as exc:
                client.search("test")
        assert exc.value.code == "invalid_key"
        assert exc.value.status_code == 401

    @respx.mock
    def test_429_raises_rate_limit_error(self):
        respx.post(f"{BASE}/v1/fetch").mock(
            return_value=Response(429, json=MOCK_ERROR_429)
        )
        with Ihyee(api_key="key", base_url=BASE) as client:
            with pytest.raises(RateLimitError) as exc:
                client.fetch(["https://example.com"])
        assert exc.value.retry_after == 30

    @respx.mock
    def test_400_raises_bad_request(self):
        respx.post(f"{BASE}/v1/search").mock(
            return_value=Response(400, json=MOCK_ERROR_400)
        )
        with Ihyee(api_key="key", base_url=BASE) as client:
            with pytest.raises(BadRequestError):
                client.search("")

    @respx.mock
    def test_502_raises_server_error(self):
        respx.post(f"{BASE}/v1/fetch").mock(
            return_value=Response(502, json={"error": {"code": "upstream_failed", "message": "Upstream error"}})
        )
        with Ihyee(api_key="key", base_url=BASE) as client:
            with pytest.raises(ServerError):
                client.fetch(["https://example.com"])

    @respx.mock
    def test_non_json_error_body(self):
        respx.post(f"{BASE}/v1/search").mock(
            return_value=Response(500, text="Internal Server Error")
        )
        with Ihyee(api_key="key", base_url=BASE) as client:
            with pytest.raises(ServerError) as exc:
                client.search("test")
        assert exc.value.code == "unknown"
