"""Unit tests for server-side helpers and tool behaviour."""
from __future__ import annotations

from unittest.mock import AsyncMock, MagicMock

import pytest

from dropmail_mcp.client import DropmailClient, DropmailError
from dropmail_mcp.server import _text_preview, _shape_mail_preview, create_server


# ---------------------------------------------------------------------------
# _minify_query
# ---------------------------------------------------------------------------

from dropmail_mcp.client import _minify_query


def test_minify_query_collapses_whitespace() -> None:
    assert _minify_query("query {\n  domains {\n    id\n  }\n}") == "query{domains{id}}"


def test_minify_query_removes_spaces_around_structural_chars() -> None:
    assert _minify_query("mutation { a( id: $id ) }") == "mutation{a(id:$id)}"


def test_minify_query_strips_leading_trailing() -> None:
    assert _minify_query("  query { x }  ") == "query{x}"


def test_minify_query_idempotent() -> None:
    q = "query{domains{id,name}}"
    assert _minify_query(q) == q


# ---------------------------------------------------------------------------
# _text_preview
# ---------------------------------------------------------------------------

def test_text_preview_collapses_newlines() -> None:
    assert _text_preview("hello\nworld") == "hello world"


def test_text_preview_collapses_mixed_whitespace() -> None:
    assert _text_preview("a\t\n  b\r\nc") == "a b c"


def test_text_preview_strips_leading_trailing() -> None:
    assert _text_preview("  hello  ") == "hello"


def test_text_preview_truncates_at_1024() -> None:
    long_text = "x " * 600  # 1200 chars
    result = _text_preview(long_text)
    assert len(result) <= 1024


def test_text_preview_none_returns_empty() -> None:
    assert _text_preview(None) == ""


def test_text_preview_empty_returns_empty() -> None:
    assert _text_preview("") == ""


def test_text_preview_exact_1024_not_truncated() -> None:
    text = "a" * 1024
    assert _text_preview(text) == text


# ---------------------------------------------------------------------------
# _shape_mail_preview
# ---------------------------------------------------------------------------

def test_shape_mail_preview_renames_text_to_textpreview() -> None:
    mail = {
        "id": "1",
        "headerSubject": "Hello",
        "text": "line1\nline2",
    }
    result = _shape_mail_preview(mail)
    assert "text" not in result
    assert result["textPreview"] == "line1 line2"
    assert result["id"] == "1"
    assert result["headerSubject"] == "Hello"


def test_shape_mail_preview_handles_null_text() -> None:
    result = _shape_mail_preview({"id": "2", "text": None})
    assert result["textPreview"] == ""
    assert "text" not in result


def test_shape_mail_preview_passes_other_fields_through() -> None:
    mail = {
        "id": "3",
        "receivedAt": "2026-01-01T00:00:00Z",
        "fromAddr": "sender@example.com",
        "headerFrom": "Sender <sender@example.com>",
        "headerSubject": "Test",
        "toAddr": "me@dropmail.me",
        "rawSize": 1234,
        "text": "body",
    }
    result = _shape_mail_preview(mail)
    assert result["receivedAt"] == "2026-01-01T00:00:00Z"
    assert result["fromAddr"] == "sender@example.com"
    assert result["rawSize"] == 1234


# ---------------------------------------------------------------------------
# wait_for_email — SESSION_NOT_FOUND
# ---------------------------------------------------------------------------

@pytest.mark.asyncio
async def test_wait_for_email_raises_on_session_not_found() -> None:
    client = MagicMock(spec=DropmailClient)
    errors = [{"message": "session_not_found", "extensions": {"code": "SESSION_NOT_FOUND"}, "path": ["sessionMailReceived"]}]
    client.execute = AsyncMock(return_value=(None, errors))
    client.unwrap = DropmailClient.unwrap.__get__(client, DropmailClient)

    mcp = create_server(client)
    tool = next(t for t in mcp._tool_manager.list_tools() if t.name == "wait_for_email")
    with pytest.raises(DropmailError) as exc_info:
        await tool.fn(session_id="sess1")
    assert exc_info.value.code == "SESSION_NOT_FOUND"
    assert "create_session" in str(exc_info.value)


@pytest.mark.asyncio
async def test_wait_for_email_returns_timeout_on_server_timeout_code() -> None:
    client = MagicMock(spec=DropmailClient)
    errors = [{"message": "Http processing error: Request execution timed out", "extensions": {"code": "timeout", "timeout_ms": 300000}}]
    client.execute = AsyncMock(return_value=(None, errors))

    mcp = create_server(client)
    tool = next(t for t in mcp._tool_manager.list_tools() if t.name == "wait_for_email")
    result = await tool.fn(session_id="sess1")
    assert result == {"arrived": False, "reason": "timeout"}


@pytest.mark.asyncio
async def test_wait_for_email_returns_timeout_on_cancellation() -> None:
    import asyncio
    client = MagicMock(spec=DropmailClient)
    client.execute = AsyncMock(side_effect=asyncio.CancelledError())

    mcp = create_server(client)
    tool = next(t for t in mcp._tool_manager.list_tools() if t.name == "wait_for_email")
    result = await tool.fn(session_id="sess1")
    assert result == {"arrived": False, "reason": "timeout"}
