"""Unit tests for DropmailClient: execute(), unwrap(), DropmailError."""
from __future__ import annotations

import pytest
import respx
import httpx

from dropmail_mcp.client import DropmailClient, DropmailError

BASE = "http://test.invalid/api/graphql"
TOKEN = "testtoken"
URL = f"{BASE}/{TOKEN}"


@pytest.fixture
def client() -> DropmailClient:
    return DropmailClient(token=TOKEN, base_url=BASE)


# ---------------------------------------------------------------------------
# execute()
# ---------------------------------------------------------------------------

@respx.mock
async def test_execute_returns_data_and_no_errors(client: DropmailClient) -> None:
    respx.post(URL).mock(return_value=httpx.Response(200, json={"data": {"foo": "bar"}}))
    data, errors = await client.execute("query { foo }")
    assert data == {"foo": "bar"}
    assert errors is None


@respx.mock
async def test_execute_returns_both_data_and_errors(client: DropmailClient) -> None:
    payload = {
        "data": {"session": {"id": "1"}},
        "errors": [{"message": "some field failed", "path": ["session", "mails"]}],
    }
    respx.post(URL).mock(return_value=httpx.Response(200, json=payload))
    data, errors = await client.execute("query { session { id mails { id } } }")
    assert data == {"session": {"id": "1"}}
    assert errors is not None
    assert errors[0]["message"] == "some field failed"


@respx.mock
async def test_execute_raises_on_http_error(client: DropmailClient) -> None:
    respx.post(URL).mock(return_value=httpx.Response(500))
    with pytest.raises(httpx.HTTPStatusError):
        await client.execute("query { foo }")


# ---------------------------------------------------------------------------
# unwrap()
# ---------------------------------------------------------------------------

def test_unwrap_returns_value_when_present(client: DropmailClient) -> None:
    result = client.unwrap({"session": {"id": "1"}}, None, "session")
    assert result == {"id": "1"}


def test_unwrap_ignores_nested_errors_when_field_present(client: DropmailClient) -> None:
    errors = [{"message": "nested fail", "path": ["session", "mails"]}]
    result = client.unwrap({"session": {"id": "1"}}, errors, "session")
    assert result == {"id": "1"}


def test_unwrap_raises_when_field_null_with_matching_error(client: DropmailClient) -> None:
    errors = [{"message": "Session not found", "path": ["session"], "extensions": {}}]
    with pytest.raises(DropmailError, match="Session not found"):
        client.unwrap({"session": None}, errors, "session")


def test_unwrap_raises_when_field_null_no_path(client: DropmailClient) -> None:
    """Error with empty path applies to any null top-level field."""
    errors = [{"message": "General error", "path": []}]
    with pytest.raises(DropmailError, match="General error"):
        client.unwrap({"session": None}, errors, "session")


def test_unwrap_raises_generic_when_null_and_no_errors(client: DropmailClient) -> None:
    with pytest.raises(DropmailError, match="'session' returned null"):
        client.unwrap({"session": None}, None, "session")


def test_unwrap_raises_on_absent_data(client: DropmailClient) -> None:
    errors = [{"message": "Boom", "path": []}]
    with pytest.raises(DropmailError, match="Boom"):
        client.unwrap(None, errors, "session")


def test_unwrap_skips_non_matching_path(client: DropmailClient) -> None:
    """Error on a different top-level field should not affect this unwrap."""
    errors = [{"message": "Other field failed", "path": ["domains"]}]
    with pytest.raises(DropmailError, match="'session' returned null"):
        client.unwrap({"session": None}, errors, "session")


# ---------------------------------------------------------------------------
# RATE_LIMIT_EXCEEDED
# ---------------------------------------------------------------------------

def test_unwrap_rate_limit_message_has_no_token_hint(client: DropmailClient) -> None:
    errors = [{
        "message": "Rate limited",
        "path": ["introduceSession"],
        "extensions": {"code": "RATE_LIMIT_EXCEEDED", "captcha": {}},
    }]
    with pytest.raises(DropmailError) as exc_info:
        client.unwrap({"introduceSession": None}, errors, "introduceSession")
    msg = str(exc_info.value)
    assert "try again later" in msg.lower()
    assert "token" not in msg.lower()
    assert exc_info.value.code == "RATE_LIMIT_EXCEEDED"


# ---------------------------------------------------------------------------
# SESSION_NOT_FOUND
# ---------------------------------------------------------------------------

def test_unwrap_session_not_found_message(client: DropmailClient) -> None:
    errors = [{
        "message": "session_not_found",
        "path": ["session"],
        "extensions": {"code": "SESSION_NOT_FOUND"},
    }]
    with pytest.raises(DropmailError) as exc_info:
        client.unwrap({"session": None}, errors, "session")
    msg = str(exc_info.value)
    assert "create_session" in msg
    assert "restore_address" in msg
    assert exc_info.value.code == "SESSION_NOT_FOUND"


# ---------------------------------------------------------------------------
# User-Agent
# ---------------------------------------------------------------------------

@respx.mock
async def test_user_agent_header_is_set(client: DropmailClient) -> None:
    route = respx.post(URL).mock(return_value=httpx.Response(200, json={"data": {}}))
    await client.execute("query { domains { id } }")
    ua = route.calls[0].request.headers.get("user-agent", "")
    assert ua.startswith("dropmail-mcp/")
