"""Tests for TendemClient."""

from decimal import Decimal
from uuid import UUID

import httpx
import pytest
import respx

from tendem_mcp.client import TendemAPIError, TendemClient
from tendem_mcp.models import McpTaskStatus

BASE_URL = 'https://api.tendem.ai'
API_KEY = 'test-api-key'

TASK_ID = UUID('12345678-1234-5678-1234-567812345678')
TASK_ID_2 = UUID('22222222-2222-2222-2222-222222222222')
CANVAS_ID = UUID('abcdefab-abcd-abcd-abcd-abcdefabcdef')
VERSION_ID = UUID('11111111-2222-3333-4444-555555555555')
ARTIFACT_ID = 'artifact-123'


@pytest.fixture
def client() -> TendemClient:
    """Create a TendemClient instance for testing."""
    return TendemClient(api_key=API_KEY, base_url=BASE_URL)


def _task_response(
    task_id: UUID = TASK_ID,
    name: str = 'Test task',
    status: str = 'PROCESSING',
    created_at: str = '2024-01-15T10:30:00Z',
    approval_request_info: dict[str, str] | None = None,
) -> dict[str, object]:
    """Create a task response dict."""
    return {
        'task_id': str(task_id),
        'name': name,
        'status': status,
        'created_at': created_at,
        'approval_request_info': approval_request_info,
    }


@pytest.mark.asyncio
@respx.mock
async def test_list_tasks(client: TendemClient) -> None:
    """Test listing tasks."""
    response_data = {
        'tasks': [
            _task_response(TASK_ID, 'Task 1'),
            _task_response(TASK_ID_2, 'Task 2', 'COMPLETED'),
        ],
        'total': 2,
        'page_number': 0,
        'page_size': 10,
        'pages': 1,
    }
    route = respx.get(f'{BASE_URL}/tasks').mock(
        return_value=httpx.Response(200, json=response_data),
    )

    result = await client.list_tasks(page_number=0, page_size=10)

    assert route.called
    assert result.total == 2
    assert result.page_number == 0
    assert result.page_size == 10
    assert result.pages == 1
    assert len(result.tasks) == 2
    assert result.tasks[0].task_id == TASK_ID
    assert result.tasks[0].name == 'Task 1'
    assert result.tasks[1].task_id == TASK_ID_2
    assert result.tasks[1].status == McpTaskStatus.COMPLETED


@pytest.mark.asyncio
@respx.mock
async def test_list_tasks_pagination(client: TendemClient) -> None:
    """Test list_tasks sends correct pagination parameters."""
    response_data: dict[str, list[object] | int] = {
        'tasks': [],
        'total': 0,
        'page_number': 2,
        'page_size': 5,
        'pages': 0,
    }
    route = respx.get(f'{BASE_URL}/tasks').mock(
        return_value=httpx.Response(200, json=response_data),
    )

    _ = await client.list_tasks(page_number=2, page_size=5)

    request = route.calls.last.request
    assert 'pageNumber=2' in str(request.url)
    assert 'pageSize=5' in str(request.url)


@pytest.mark.asyncio
@respx.mock
async def test_create_task(client: TendemClient) -> None:
    """Test creating a task."""
    response_data = _task_response()
    _ = respx.post(f'{BASE_URL}/tasks').mock(
        return_value=httpx.Response(201, json=response_data),
    )

    result = await client.create_task('Test task description')

    assert result.task_id == TASK_ID
    assert result.name == 'Test task'
    assert result.status == McpTaskStatus.PROCESSING
    assert result.created_at.year == 2024


@pytest.mark.asyncio
@respx.mock
async def test_create_task_sends_correct_payload(client: TendemClient) -> None:
    """Test that create_task sends the correct JSON payload."""
    response_data = _task_response()
    route = respx.post(f'{BASE_URL}/tasks').mock(
        return_value=httpx.Response(201, json=response_data),
    )

    _ = await client.create_task('My task text')

    assert route.called
    request = route.calls.last.request
    assert request.content == b'{"text":"My task text"}'


@pytest.mark.asyncio
@respx.mock
async def test_create_task_includes_auth_header(client: TendemClient) -> None:
    """Test that requests include the Authorization header."""
    response_data = _task_response()
    route = respx.post(f'{BASE_URL}/tasks').mock(
        return_value=httpx.Response(201, json=response_data),
    )

    _ = await client.create_task('Test')

    request = route.calls.last.request
    assert request.headers['Authorization'] == f'ApiKey {API_KEY}'


@pytest.mark.asyncio
@respx.mock
async def test_get_task_processing(client: TendemClient) -> None:
    """Test getting task when processing."""
    response_data = _task_response(status='PROCESSING')
    _ = respx.get(f'{BASE_URL}/tasks/{TASK_ID}').mock(
        return_value=httpx.Response(200, json=response_data),
    )

    result = await client.get_task(TASK_ID)

    assert result.task_id == TASK_ID
    assert result.status == McpTaskStatus.PROCESSING
    assert result.approval_request_info is None


@pytest.mark.asyncio
@respx.mock
async def test_get_task_awaiting_approval(client: TendemClient) -> None:
    """Test getting task when awaiting approval."""
    response_data = _task_response(
        status='AWAITING_APPROVAL',
        approval_request_info={
            'price_usd': '10.50',
            'created_at': '2024-01-15T10:30:00Z',
        },
    )
    _ = respx.get(f'{BASE_URL}/tasks/{TASK_ID}').mock(
        return_value=httpx.Response(200, json=response_data),
    )

    result = await client.get_task(TASK_ID)

    assert result.task_id == TASK_ID
    assert result.status == McpTaskStatus.AWAITING_APPROVAL
    assert result.approval_request_info is not None
    assert result.approval_request_info.price_usd == Decimal('10.50')


@pytest.mark.asyncio
@respx.mock
async def test_approve_task(client: TendemClient) -> None:
    """Test approving a task."""
    route = respx.post(f'{BASE_URL}/tasks/{TASK_ID}/approve').mock(
        return_value=httpx.Response(204),
    )

    await client.approve_task(TASK_ID)

    assert route.called


@pytest.mark.asyncio
@respx.mock
async def test_cancel_task(client: TendemClient) -> None:
    """Test cancelling a task."""
    route = respx.post(f'{BASE_URL}/tasks/{TASK_ID}/cancel').mock(
        return_value=httpx.Response(204),
    )

    await client.cancel_task(TASK_ID)

    assert route.called


@pytest.mark.asyncio
@respx.mock
async def test_get_task_results(client: TendemClient) -> None:
    """Test getting task results."""
    response_data = {
        'canvases': [
            {
                'canvas_id': str(CANVAS_ID),
                'version_id': str(VERSION_ID),
                'created_at': '2024-01-15T10:30:00Z',
                'content': '# Result content',
            },
        ],
        'total': 1,
        'page_number': 0,
        'page_size': 10,
        'pages': 1,
    }
    _ = respx.get(f'{BASE_URL}/tasks/{TASK_ID}/results').mock(
        return_value=httpx.Response(200, json=response_data),
    )

    result = await client.get_task_results(TASK_ID)

    assert result.total == 1
    assert result.page_number == 0
    assert result.page_size == 10
    assert result.pages == 1
    assert len(result.canvases) == 1
    assert result.canvases[0].canvas_id == CANVAS_ID
    assert result.canvases[0].content == '# Result content'


@pytest.mark.asyncio
@respx.mock
async def test_get_task_results_with_pagination(client: TendemClient) -> None:
    """Test getting task results with pagination parameters."""
    response_data: dict[str, list[dict[str, str]] | int] = {
        'canvases': [],
        'total': 25,
        'page_number': 2,
        'page_size': 5,
        'pages': 5,
    }
    route = respx.get(f'{BASE_URL}/tasks/{TASK_ID}/results').mock(
        return_value=httpx.Response(200, json=response_data),
    )

    result = await client.get_task_results(TASK_ID, page_number=2, page_size=5)

    assert result.page_number == 2
    assert result.page_size == 5
    request = route.calls.last.request
    assert 'pageNumber=2' in str(request.url)
    assert 'pageSize=5' in str(request.url)


@pytest.mark.asyncio
@respx.mock
async def test_get_artifact(client: TendemClient) -> None:
    """Test getting an artifact."""
    artifact_content = b'binary artifact content'
    _ = respx.get(f'{BASE_URL}/tasks/{TASK_ID}/artifacts/{ARTIFACT_ID}').mock(
        return_value=httpx.Response(200, content=artifact_content),
    )

    result = await client.get_artifact(TASK_ID, ARTIFACT_ID)

    assert result == artifact_content


@pytest.mark.asyncio
@respx.mock
async def test_http_error_raises_exception(client: TendemClient) -> None:
    """Test that HTTP errors raise exceptions with details."""
    _ = respx.post(f'{BASE_URL}/tasks').mock(
        return_value=httpx.Response(
            401,
            json={
                'type': '/auth/unauthorized',
                'title': 'Unauthorized',
                'detail': 'Invalid API key',
            },
        ),
    )

    with pytest.raises(TendemAPIError) as exc_info:
        _ = await client.create_task('Test')

    assert exc_info.value.status_code == 401
    assert exc_info.value.detail == 'Invalid API key'
    assert exc_info.value.error_type == '/auth/unauthorized'


@pytest.mark.asyncio
@respx.mock
async def test_server_error_raises_exception(client: TendemClient) -> None:
    """Test that server errors raise exceptions with details."""
    _ = respx.get(f'{BASE_URL}/tasks/{TASK_ID}').mock(
        return_value=httpx.Response(
            500,
            json={
                'type': '/server/error',
                'title': 'Internal Server Error',
                'detail': 'Database unavailable',
            },
        ),
    )

    with pytest.raises(TendemAPIError) as exc_info:
        _ = await client.get_task(TASK_ID)

    assert exc_info.value.status_code == 500
    assert exc_info.value.detail == 'Database unavailable'


@pytest.mark.asyncio
@respx.mock
async def test_eula_not_accepted_error(client: TendemClient) -> None:
    """Test that EULA not accepted error is returned with details."""
    _ = respx.post(f'{BASE_URL}/tasks').mock(
        return_value=httpx.Response(
            409,
            json={
                'type': '/user/eula_not_accepted',
                'title': 'Conflict',
                'status': 409,
                'detail': 'EULA version 1 is required',
            },
        ),
    )

    with pytest.raises(TendemAPIError) as exc_info:
        _ = await client.create_task('Test')

    assert exc_info.value.status_code == 409
    assert exc_info.value.error_type == '/user/eula_not_accepted'
    assert exc_info.value.detail == 'EULA version 1 is required'
    assert 'EULA version 1 is required' in str(exc_info.value)


@pytest.mark.asyncio
@respx.mock
async def test_context_manager() -> None:
    """Test using client as async context manager."""
    response_data = _task_response()
    _ = respx.post(f'{BASE_URL}/tasks').mock(
        return_value=httpx.Response(201, json=response_data),
    )

    async with TendemClient(api_key=API_KEY, base_url=BASE_URL) as ctx_client:
        result = await ctx_client.create_task('Test')
        assert result.task_id == TASK_ID


@pytest.mark.asyncio
@respx.mock
async def test_naive_datetime_gets_utc_timezone(client: TendemClient) -> None:
    """Test that naive datetimes from API are treated as UTC."""
    response_data = _task_response(created_at='2024-01-15T10:30:00')
    _ = respx.post(f'{BASE_URL}/tasks').mock(
        return_value=httpx.Response(201, json=response_data),
    )

    result = await client.create_task('Test')

    assert result.created_at.tzinfo is not None
    assert result.created_at.year == 2024
    # Verify serialization includes timezone (RFC 3339 compliant)
    serialized = result.model_dump_json()
    assert '+00:00' in serialized or 'Z' in serialized


@pytest.mark.asyncio
async def test_base_url_trailing_slash_stripped() -> None:
    """Test that trailing slash in base_url is stripped."""
    test_client = TendemClient(api_key=API_KEY, base_url='https://api.example.com/')
    assert test_client.base_url == 'https://api.example.com'
    await test_client.close()
