"""Tests for the Anthropic integration."""

from pathlib import Path
import re
from unittest.mock import AsyncMock, patch

from anthropic.types import Message, TextBlock, Usage
from freezegun import freeze_time
import pytest
from syrupy.assertion import SnapshotAssertion
import voluptuous as vol

from homeassistant.components import ai_task, media_source
from homeassistant.components.anthropic.const import (
    CONF_CHAT_MODEL,
    CONF_THINKING_BUDGET,
)
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import entity_registry as er, selector

from . import create_content_block, create_thinking_block, create_tool_use_block

from tests.common import MockConfigEntry


async def test_generate_data(
    hass: HomeAssistant,
    mock_config_entry: MockConfigEntry,
    mock_init_component,
    mock_create_stream: AsyncMock,
    entity_registry: er.EntityRegistry,
) -> None:
    """Test AI Task data generation."""
    entity_id = "ai_task.claude_ai_task"

    # Ensure entity is linked to the subentry
    entity_entry = entity_registry.async_get(entity_id)
    ai_task_entry = next(
        iter(
            entry
            for entry in mock_config_entry.subentries.values()
            if entry.subentry_type == "ai_task_data"
        )
    )
    assert entity_entry is not None
    assert entity_entry.config_entry_id == mock_config_entry.entry_id
    assert entity_entry.config_subentry_id == ai_task_entry.subentry_id

    mock_create_stream.return_value = [create_content_block(0, ["The test data"])]

    result = await ai_task.async_generate_data(
        hass,
        task_name="Test Task",
        entity_id=entity_id,
        instructions="Generate test data",
    )

    assert result.data == "The test data"


async def test_translation_key(
    hass: HomeAssistant,
    mock_config_entry: MockConfigEntry,
    mock_init_component,
    entity_registry: er.EntityRegistry,
) -> None:
    """Test entity translation key."""
    entry = entity_registry.async_get("ai_task.claude_ai_task")
    assert entry is not None
    assert entry.translation_key == "ai_task_data"


async def test_empty_data(
    hass: HomeAssistant,
    mock_config_entry: MockConfigEntry,
    mock_init_component,
    mock_create_stream: AsyncMock,
) -> None:
    """Test AI Task data generation but the data returned is empty."""
    mock_create_stream.return_value = [create_content_block(0, [""])]

    with pytest.raises(
        HomeAssistantError, match="Last content in chat log is not an AssistantContent"
    ):
        await ai_task.async_generate_data(
            hass,
            task_name="Test Task",
            entity_id="ai_task.claude_ai_task",
            instructions="Generate test data",
        )


async def test_stream_wrong_type(
    hass: HomeAssistant,
    mock_config_entry: MockConfigEntry,
    mock_init_component,
    mock_create_stream: AsyncMock,
) -> None:
    """Test error if the response is not a stream."""
    mock_create_stream.return_value = Message(
        type="message",
        id="message_id",
        model="claude-opus-4-6",
        role="assistant",
        content=[TextBlock(type="text", text="This is not a stream")],
        usage=Usage(input_tokens=42, output_tokens=42),
    )

    with pytest.raises(HomeAssistantError, match="Expected a stream of messages"):
        await ai_task.async_generate_data(
            hass,
            task_name="Test Task",
            entity_id="ai_task.claude_ai_task",
            instructions="Generate test data",
        )


@freeze_time("2026-01-01 12:00:00")
async def test_generate_structured_data_legacy(
    hass: HomeAssistant,
    mock_config_entry: MockConfigEntry,
    mock_init_component,
    mock_create_stream: AsyncMock,
    snapshot: SnapshotAssertion,
) -> None:
    """Test AI Task structured data generation with legacy method."""
    for subentry in mock_config_entry.subentries.values():
        hass.config_entries.async_update_subentry(
            mock_config_entry,
            subentry,
            data={
                CONF_CHAT_MODEL: "claude-sonnet-4-0",
                CONF_THINKING_BUDGET: 0,
            },
        )

    mock_create_stream.return_value = [
        create_tool_use_block(
            0,
            "toolu_0123456789AbCdEfGhIjKlM",
            "test_task",
            ['{"charac', 'ters": ["Mario', '", "Luigi"]}'],
        ),
    ]

    result = await ai_task.async_generate_data(
        hass,
        task_name="Test Task",
        entity_id="ai_task.claude_ai_task",
        instructions="Generate test data",
        structure=vol.Schema(
            {
                vol.Required("characters"): selector.selector(
                    {
                        "text": {
                            "multiple": True,
                        }
                    }
                )
            },
        ),
    )

    assert result.data == {"characters": ["Mario", "Luigi"]}
    assert mock_create_stream.call_args.kwargs.copy() == snapshot


@freeze_time("2026-01-01 12:00:00")
async def test_generate_structured_data_legacy_tools(
    hass: HomeAssistant,
    mock_config_entry: MockConfigEntry,
    mock_init_component,
    mock_create_stream: AsyncMock,
    snapshot: SnapshotAssertion,
) -> None:
    """Test AI Task structured data generation with legacy method and tools enabled."""
    mock_create_stream.return_value = [
        create_tool_use_block(
            0,
            "toolu_0123456789AbCdEfGhIjKlM",
            "test_task",
            ['{"charac', 'ters": ["Mario', '", "Luigi"]}'],
        ),
    ]

    for subentry in mock_config_entry.subentries.values():
        hass.config_entries.async_update_subentry(
            mock_config_entry,
            subentry,
            data={
                "chat_model": "claude-sonnet-4-0",
                "web_search": True,
                "thinking_budget": 0,
            },
        )

    result = await ai_task.async_generate_data(
        hass,
        task_name="Test Task",
        entity_id="ai_task.claude_ai_task",
        instructions="Generate test data",
        structure=vol.Schema(
            {
                vol.Required("characters"): selector.selector(
                    {
                        "text": {
                            "multiple": True,
                        }
                    }
                )
            },
        ),
    )

    assert result.data == {"characters": ["Mario", "Luigi"]}
    assert mock_create_stream.call_args.kwargs.copy() == snapshot


@freeze_time("2026-01-01 12:00:00")
async def test_generate_structured_data_legacy_extended_thinking(
    hass: HomeAssistant,
    mock_config_entry: MockConfigEntry,
    mock_init_component,
    mock_create_stream: AsyncMock,
    snapshot: SnapshotAssertion,
) -> None:
    """Test AI Task structured data generation.

    Uses legacy method with extended_thinking.
    """
    mock_create_stream.return_value = [
        (
            *create_thinking_block(
                0,
                ["Let's use the tool to respond"],
            ),
            *create_tool_use_block(
                1,
                "toolu_0123456789AbCdEfGhIjKlM",
                "test_task",
                ['{"charac', 'ters": ["Mario', '", "Luigi"]}'],
            ),
        ),
    ]

    for subentry in mock_config_entry.subentries.values():
        hass.config_entries.async_update_subentry(
            mock_config_entry,
            subentry,
            data={
                "chat_model": "claude-sonnet-4-0",
                "thinking_budget": 1500,
            },
        )

    result = await ai_task.async_generate_data(
        hass,
        task_name="Test Task",
        entity_id="ai_task.claude_ai_task",
        instructions="Generate test data",
        structure=vol.Schema(
            {
                vol.Required("characters"): selector.selector(
                    {
                        "text": {
                            "multiple": True,
                        }
                    }
                )
            },
        ),
    )

    assert result.data == {"characters": ["Mario", "Luigi"]}
    assert mock_create_stream.call_args.kwargs.copy() == snapshot


@freeze_time("2026-01-01 12:00:00")
async def test_generate_structured_data_legacy_extra_text_block(
    hass: HomeAssistant,
    mock_config_entry: MockConfigEntry,
    mock_init_component,
    mock_create_stream: AsyncMock,
    snapshot: SnapshotAssertion,
) -> None:
    """Test AI Task structured data generation.

    Uses legacy method with extra text block.
    """
    mock_create_stream.return_value = [
        (
            *create_thinking_block(
                0,
                ["Let's use the tool to respond"],
            ),
            *create_content_block(1, ["Sure!"]),
            *create_tool_use_block(
                2,
                "toolu_0123456789AbCdEfGhIjKlM",
                "test_task",
                ['{"charac', 'ters": ["Mario', '", "Luigi"]}'],
            ),
        ),
    ]

    for subentry in mock_config_entry.subentries.values():
        hass.config_entries.async_update_subentry(
            mock_config_entry,
            subentry,
            data={
                "chat_model": "claude-sonnet-4-0",
                "thinking_budget": 1500,
            },
        )

    result = await ai_task.async_generate_data(
        hass,
        task_name="Test Task",
        entity_id="ai_task.claude_ai_task",
        instructions="Generate test data",
        structure=vol.Schema(
            {
                vol.Required("characters"): selector.selector(
                    {
                        "text": {
                            "multiple": True,
                        }
                    }
                )
            },
        ),
    )

    assert result.data == {"characters": ["Mario", "Luigi"]}
    assert mock_create_stream.call_args.kwargs.copy() == snapshot


async def test_generate_invalid_structured_data_legacy(
    hass: HomeAssistant,
    mock_config_entry: MockConfigEntry,
    mock_init_component,
    mock_create_stream: AsyncMock,
) -> None:
    """Test AI Task with invalid JSON response with legacy method."""
    for subentry in mock_config_entry.subentries.values():
        hass.config_entries.async_update_subentry(
            mock_config_entry,
            subentry,
            data={
                CONF_CHAT_MODEL: "claude-sonnet-4-0",
            },
        )

    mock_create_stream.return_value = [
        create_tool_use_block(
            0,
            "toolu_0123456789AbCdEfGhIjKlM",
            "test_task",
            "INVALID JSON RESPONSE",
        )
    ]

    with pytest.raises(
        HomeAssistantError, match="Error with Claude structured response"
    ):
        await ai_task.async_generate_data(
            hass,
            task_name="Test Task",
            entity_id="ai_task.claude_ai_task",
            instructions="Generate test data",
            structure=vol.Schema(
                {
                    vol.Required("characters"): selector.selector(
                        {
                            "text": {
                                "multiple": True,
                            }
                        }
                    )
                },
            ),
        )


@freeze_time("2026-01-01 12:00:00")
async def test_generate_structured_data(
    hass: HomeAssistant,
    mock_config_entry: MockConfigEntry,
    mock_init_component,
    mock_create_stream: AsyncMock,
    snapshot: SnapshotAssertion,
) -> None:
    """Test AI Task structured data generation."""
    mock_create_stream.return_value = [
        create_content_block(0, ['{"charac', 'ters": ["Mario', '", "Luigi"]}'])
    ]

    result = await ai_task.async_generate_data(
        hass,
        task_name="Test Task",
        entity_id="ai_task.claude_ai_task",
        instructions="Generate test data",
        structure=vol.Schema(
            {
                vol.Required("characters"): selector.selector(
                    {
                        "text": {
                            "multiple": True,
                        }
                    }
                )
            },
        ),
    )

    assert result.data == {"characters": ["Mario", "Luigi"]}
    assert mock_create_stream.call_args.kwargs.copy() == snapshot


async def test_generate_data_with_attachments(
    hass: HomeAssistant,
    mock_config_entry: MockConfigEntry,
    mock_init_component,
    mock_create_stream: AsyncMock,
    entity_registry: er.EntityRegistry,
) -> None:
    """Test AI Task data generation with attachments."""
    entity_id = "ai_task.claude_ai_task"

    mock_create_stream.return_value = [create_content_block(0, ["Hi there!"])]

    # Test with attachments
    with (
        patch(
            "homeassistant.components.media_source.async_resolve_media",
            side_effect=[
                media_source.PlayMedia(
                    url="http://example.com/doorbell_snapshot.jpg",
                    mime_type="image/jpg",
                    path=Path("doorbell_snapshot.jpg"),
                ),
                media_source.PlayMedia(
                    url="http://example.com/context.pdf",
                    mime_type="application/pdf",
                    path=Path("context.pdf"),
                ),
            ],
        ),
        patch("pathlib.Path.exists", return_value=True),
        patch("pathlib.Path.read_bytes", return_value=b"fake_image_data"),
    ):
        result = await ai_task.async_generate_data(
            hass,
            task_name="Test Task",
            entity_id=entity_id,
            instructions="Test prompt",
            attachments=[
                {"media_content_id": "media-source://media/doorbell_snapshot.jpg"},
                {"media_content_id": "media-source://media/context.pdf"},
            ],
        )

    assert result.data == "Hi there!"

    # Verify that the create stream was called with the correct parameters
    # The last call should have the user message with attachments
    call_args = mock_create_stream.call_args
    assert call_args is not None

    # Check that the input includes the attachments
    input_messages = call_args[1]["messages"]
    assert len(input_messages) > 0

    # Find the user message with attachments
    user_message_with_attachments = input_messages[-2]

    assert user_message_with_attachments is not None
    assert isinstance(user_message_with_attachments["content"], list)
    assert len(user_message_with_attachments["content"]) == 3  # Text + attachments

    text_block, image_block, document_block = user_message_with_attachments["content"]

    # Text block
    assert text_block["type"] == "text"
    assert text_block["text"] == "Test prompt"

    # Image attachment
    assert image_block["type"] == "image"
    assert image_block["source"] == {
        "data": "ZmFrZV9pbWFnZV9kYXRh",
        "media_type": "image/jpeg",
        "type": "base64",
    }

    # Document attachment (ignore extra metadata like cache_control)
    assert document_block["type"] == "document"
    assert document_block["source"]["data"] == "ZmFrZV9pbWFnZV9kYXRh"
    assert document_block["source"]["media_type"] == "application/pdf"
    assert document_block["source"]["type"] == "base64"


async def test_generate_data_invalid_attachments(
    hass: HomeAssistant,
    mock_config_entry: MockConfigEntry,
    mock_init_component,
    mock_create_stream: AsyncMock,
    entity_registry: er.EntityRegistry,
) -> None:
    """Test AI Task data generation with attachments of unsupported type."""
    entity_id = "ai_task.claude_ai_task"

    mock_create_stream.return_value = [create_content_block(0, ["Hi there!"])]

    # Test path that doesn't exist
    with (
        patch(
            "homeassistant.components.media_source.async_resolve_media",
            side_effect=[
                media_source.PlayMedia(
                    url="http://example.com/doorbell_snapshot.jpg",
                    mime_type="image/jpeg",
                    path=Path("doorbell_snapshot.jpg"),
                )
            ],
        ),
        patch("pathlib.Path.exists", return_value=False),
        pytest.raises(
            HomeAssistantError, match="`doorbell_snapshot.jpg` does not exist"
        ),
    ):
        await ai_task.async_generate_data(
            hass,
            task_name="Test Task",
            entity_id=entity_id,
            instructions="Test prompt",
            attachments=[
                {"media_content_id": "media-source://media/doorbell_snapshot.jpg"},
            ],
        )

    # Test unsupported file type
    with (
        patch(
            "homeassistant.components.media_source.async_resolve_media",
            side_effect=[
                media_source.PlayMedia(
                    url="http://example.com/doorbell_snapshot.txt",
                    mime_type=None,
                    path=Path("doorbell_snapshot.txt"),
                )
            ],
        ),
        patch("pathlib.Path.exists", return_value=True),
        patch(
            "homeassistant.components.anthropic.entity.guess_file_type",
            return_value=("text/plain", None),
        ),
        pytest.raises(
            HomeAssistantError,
            match=re.escape(
                "The Claude Haiku 4.5 model does not support"
                " text/plain file types"
                " (for `doorbell_snapshot.txt`)"
            ),
        ),
    ):
        await ai_task.async_generate_data(
            hass,
            task_name="Test Task",
            entity_id=entity_id,
            instructions="Test prompt",
            attachments=[
                {"media_content_id": "media-source://media/doorbell_snapshot.txt"},
            ],
        )
