"""Tests for Anthropic adapter conversion functions."""

from __future__ import annotations

import pytest

from dotpromptz.adapters.anthropic import (
    AnthropicAdapter,
    to_anthropic_messages,
    to_anthropic_request,
    to_anthropic_tools,
)
from dotpromptz.typing import (
    MediaContent,
    MediaPart,
    Message,
    PromptOutputConfig,
    RenderedPrompt,
    Role,
    TextPart,
    ToolDefinition,
    ToolRequestContent,
    ToolRequestPart,
    ToolResponseContent,
    ToolResponsePart,
)


class TestSystemExtraction:
    """Test that system messages are extracted to top-level param."""

    def test_system_extracted(self) -> None:
        system_text, msgs = to_anthropic_messages([
            Message(role=Role.SYSTEM, content=[TextPart(text='You are helpful.')]),
            Message(role=Role.USER, content=[TextPart(text='hi')]),
        ])
        assert system_text == 'You are helpful.'
        assert len(msgs) == 1
        assert msgs[0]['role'] == 'user'

    def test_no_system(self) -> None:
        system_text, msgs = to_anthropic_messages([
            Message(role=Role.USER, content=[TextPart(text='hi')]),
        ])
        assert system_text is None
        assert len(msgs) == 1

    def test_multiple_system(self) -> None:
        system_text, _ = to_anthropic_messages([
            Message(role=Role.SYSTEM, content=[TextPart(text='Rule 1.')]),
            Message(role=Role.SYSTEM, content=[TextPart(text='Rule 2.')]),
            Message(role=Role.USER, content=[TextPart(text='hi')]),
        ])
        assert system_text == 'Rule 1.\nRule 2.'


class TestRoleMapping:
    """Test role conversion for Anthropic."""

    def test_model_to_assistant(self) -> None:
        _, msgs = to_anthropic_messages([
            Message(role=Role.MODEL, content=[TextPart(text='hello')]),
        ])
        assert msgs[0]['role'] == 'assistant'

    def test_tool_to_user(self) -> None:
        """Anthropic tool results go as user role."""
        _, msgs = to_anthropic_messages([
            Message(
                role=Role.TOOL,
                content=[ToolResponsePart(tool_response=ToolResponseContent(name='fn', output={'r': 1}, ref='id1'))],
            ),
        ])
        assert msgs[0]['role'] == 'user'


class TestContentBlocks:
    """Test Part → Anthropic content block conversion."""

    def test_single_text_simplified(self) -> None:
        _, msgs = to_anthropic_messages([
            Message(role=Role.USER, content=[TextPart(text='hello')]),
        ])
        # Single text block simplifies to string.
        assert msgs[0]['content'] == 'hello'

    def test_multi_text_keeps_blocks(self) -> None:
        _, msgs = to_anthropic_messages([
            Message(role=Role.USER, content=[TextPart(text='a'), TextPart(text='b')]),
        ])
        content = msgs[0]['content']
        assert isinstance(content, list)
        assert len(content) == 2

    def test_media_base64(self) -> None:
        _, msgs = to_anthropic_messages([
            Message(
                role=Role.USER,
                content=[MediaPart(media=MediaContent(url='data:image/png;base64,abc123', content_type='image/png'))],
            ),
        ])
        content = msgs[0]['content']
        # Single media block is kept as list (not simplified to string).
        assert isinstance(content, list)
        block = content[0]
        assert block['type'] == 'image'
        assert block['source']['type'] == 'base64'
        assert block['source']['data'] == 'abc123'

    def test_tool_request_becomes_tool_use(self) -> None:
        _, msgs = to_anthropic_messages([
            Message(
                role=Role.MODEL,
                content=[ToolRequestPart(tool_request=ToolRequestContent(name='search', input={'q': 'x'}, ref='id1'))],
            ),
        ])
        block = msgs[0]['content']
        assert isinstance(block, list)
        assert block[0]['type'] == 'tool_use'
        assert block[0]['name'] == 'search'


class TestToolConversion:
    """Test ToolDefinition → Anthropic tool format."""

    def test_basic(self) -> None:
        tools = to_anthropic_tools([
            ToolDefinition(name='search', description='desc', input_schema={'type': 'object'}),
        ])
        assert tools is not None
        assert tools[0]['name'] == 'search'
        assert tools[0]['input_schema'] == {'type': 'object'}

    def test_none(self) -> None:
        assert to_anthropic_tools(None) is None


class TestToAnthropicRequest:
    """Test full RenderedPrompt → Anthropic request conversion."""

    def test_basic(self) -> None:
        rendered = RenderedPrompt(
            config={'model': 'claude-sonnet-4-20250514'},
            messages=[
                Message(role=Role.SYSTEM, content=[TextPart(text='Be helpful')]),
                Message(role=Role.USER, content=[TextPart(text='hi')]),
            ],
        )
        req = to_anthropic_request(rendered)
        assert req['model'] == 'claude-sonnet-4-20250514'
        assert req['system'] == 'Be helpful'
        assert len(req['messages']) == 1

    def test_no_model_raises(self) -> None:
        rendered = RenderedPrompt(
            messages=[Message(role=Role.USER, content=[TextPart(text='hi')])],
        )
        with pytest.raises(ValueError, match='No model specified'):
            to_anthropic_request(rendered)

    def test_max_tokens_default(self) -> None:
        rendered = RenderedPrompt(
            config={'model': 'claude-sonnet-4-20250514'},
            messages=[Message(role=Role.USER, content=[TextPart(text='hi')])],
        )
        req = to_anthropic_request(rendered)
        assert req['max_tokens'] == 4096

    def test_json_output_no_schema(self) -> None:
        """JSON format without schema should not set output_config."""
        rendered = RenderedPrompt(
            config={'model': 'claude-sonnet-4-20250514'},
            messages=[Message(role=Role.USER, content=[TextPart(text='hi')])],
            output=PromptOutputConfig(format='json'),
        )
        req = to_anthropic_request(rendered)
        assert 'output_config' not in req

    def test_json_output_with_schema(self) -> None:
        """JSON format with schema should set output_config with json_schema."""
        schema = {
            'type': 'object',
            'properties': {
                'name': {'type': 'string'},
                'age': {'type': 'integer'},
            },
            'required': ['name', 'age'],
            'additionalProperties': False,
        }
        rendered = RenderedPrompt(
            config={'model': 'claude-sonnet-4-20250514'},
            messages=[Message(role=Role.USER, content=[TextPart(text='hi')])],
            output=PromptOutputConfig(format='json', schema=schema),
        )
        req = to_anthropic_request(rendered)
        assert 'output_config' in req
        output_config = req['output_config']
        assert output_config['format']['type'] == 'json_schema'
        assert output_config['format']['schema'] == schema

    def test_text_output_no_output_config(self) -> None:
        """Text format should not set output_config."""
        rendered = RenderedPrompt(
            config={'model': 'claude-sonnet-4-20250514'},
            messages=[Message(role=Role.USER, content=[TextPart(text='hi')])],
            output=PromptOutputConfig(format='text'),
        )
        req = to_anthropic_request(rendered)
        assert 'output_config' not in req


class TestAnthropicAdapterConvert:
    """Test the adapter's convert method (no API call)."""

    def test_convert_returns_dict(self) -> None:
        adapter = AnthropicAdapter(api_key='test-key')
        rendered = RenderedPrompt(
            config={'model': 'claude-sonnet-4-20250514'},
            messages=[Message(role=Role.USER, content=[TextPart(text='hello')])],
        )
        result = adapter.convert(rendered)
        assert isinstance(result, dict)
        assert result['model'] == 'claude-sonnet-4-20250514'


class TestAnthropicAdapterBaseUrl:
    """Test base_url support for third-party compatible endpoints."""

    def test_base_url_param(self) -> None:
        adapter = AnthropicAdapter(api_key='test-key', base_url='https://my-proxy.example.com')
        assert adapter._base_url == 'https://my-proxy.example.com'

    def test_base_url_from_env(self, monkeypatch: pytest.MonkeyPatch) -> None:
        monkeypatch.setenv('ANTHROPIC_BASE_URL', 'https://env-proxy.example.com')
        adapter = AnthropicAdapter(api_key='test-key')
        assert adapter._base_url == 'https://env-proxy.example.com'

    def test_base_url_param_overrides_env(self, monkeypatch: pytest.MonkeyPatch) -> None:
        monkeypatch.setenv('ANTHROPIC_BASE_URL', 'https://env-proxy.example.com')
        adapter = AnthropicAdapter(api_key='test-key', base_url='https://param-proxy.example.com')
        assert adapter._base_url == 'https://param-proxy.example.com'

    def test_base_url_none_by_default(self, monkeypatch) -> None:
        monkeypatch.delenv('ANTHROPIC_BASE_URL', raising=False)
        adapter = AnthropicAdapter(api_key='test-key')
        assert adapter._base_url is None
