"""Unit tests for BidiAgent hook events."""

from unittest.mock import Mock

import pytest

from strands.experimental.hooks import (
    BidiAfterInvocationEvent,
    BidiAfterToolCallEvent,
    BidiAgentInitializedEvent,
    BidiBeforeInvocationEvent,
    BidiBeforeToolCallEvent,
    BidiInterruptionEvent,
    BidiMessageAddedEvent,
)
from strands.types.tools import ToolResult, ToolUse


@pytest.fixture
def agent():
    return Mock()


@pytest.fixture
def tool():
    tool = Mock()
    tool.tool_name = "test_tool"
    return tool


@pytest.fixture
def tool_use():
    return ToolUse(name="test_tool", toolUseId="123", input={"param": "value"})


@pytest.fixture
def tool_invocation_state():
    return {"param": "value"}


@pytest.fixture
def tool_result():
    return ToolResult(content=[{"text": "result"}], status="success", toolUseId="123")


@pytest.fixture
def message():
    return {"role": "user", "content": [{"text": "Hello"}]}


@pytest.fixture
def initialized_event(agent):
    return BidiAgentInitializedEvent(agent=agent)


@pytest.fixture
def before_invocation_event(agent):
    return BidiBeforeInvocationEvent(agent=agent)


@pytest.fixture
def after_invocation_event(agent):
    return BidiAfterInvocationEvent(agent=agent)


@pytest.fixture
def message_added_event(agent, message):
    return BidiMessageAddedEvent(agent=agent, message=message)


@pytest.fixture
def before_tool_event(agent, tool, tool_use, tool_invocation_state):
    return BidiBeforeToolCallEvent(
        agent=agent,
        selected_tool=tool,
        tool_use=tool_use,
        invocation_state=tool_invocation_state,
    )


@pytest.fixture
def after_tool_event(agent, tool, tool_use, tool_invocation_state, tool_result):
    return BidiAfterToolCallEvent(
        agent=agent,
        selected_tool=tool,
        tool_use=tool_use,
        invocation_state=tool_invocation_state,
        result=tool_result,
    )


@pytest.fixture
def interruption_event(agent):
    return BidiInterruptionEvent(agent=agent, reason="user_speech")


def test_event_should_reverse_callbacks(
    initialized_event,
    before_invocation_event,
    after_invocation_event,
    message_added_event,
    before_tool_event,
    after_tool_event,
    interruption_event,
):
    """Verify which events use reverse callback ordering."""
    # note that we ignore E712 (explicit booleans) for consistency/readability purposes

    assert initialized_event.should_reverse_callbacks == False  # noqa: E712
    assert message_added_event.should_reverse_callbacks == False  # noqa: E712
    assert interruption_event.should_reverse_callbacks == False  # noqa: E712

    assert before_invocation_event.should_reverse_callbacks == False  # noqa: E712
    assert after_invocation_event.should_reverse_callbacks == True  # noqa: E712

    assert before_tool_event.should_reverse_callbacks == False  # noqa: E712
    assert after_tool_event.should_reverse_callbacks == True  # noqa: E712


def test_interruption_event_with_response_id(agent):
    """Verify BidiInterruptionEvent can include response ID."""
    event = BidiInterruptionEvent(agent=agent, reason="error", interrupted_response_id="resp_123")

    assert event.reason == "error"
    assert event.interrupted_response_id == "resp_123"


def test_message_added_event_cannot_write_properties(message_added_event):
    """Verify BidiMessageAddedEvent properties are read-only."""
    with pytest.raises(AttributeError, match="Property agent is not writable"):
        message_added_event.agent = Mock()
    with pytest.raises(AttributeError, match="Property message is not writable"):
        message_added_event.message = {}


def test_before_tool_call_event_can_write_properties(before_tool_event):
    """Verify BidiBeforeToolCallEvent allows writing specific properties."""
    new_tool_use = ToolUse(name="new_tool", toolUseId="456", input={})
    before_tool_event.selected_tool = None  # Should not raise
    before_tool_event.tool_use = new_tool_use  # Should not raise
    before_tool_event.cancel_tool = "Cancelled by user"  # Should not raise


def test_before_tool_call_event_cannot_write_properties(before_tool_event):
    """Verify BidiBeforeToolCallEvent protects certain properties."""
    with pytest.raises(AttributeError, match="Property agent is not writable"):
        before_tool_event.agent = Mock()
    with pytest.raises(AttributeError, match="Property invocation_state is not writable"):
        before_tool_event.invocation_state = {}


def test_after_tool_call_event_can_write_properties(after_tool_event):
    """Verify BidiAfterToolCallEvent allows writing result property."""
    new_result = ToolResult(content=[{"text": "new result"}], status="success", toolUseId="456")
    after_tool_event.result = new_result  # Should not raise


def test_after_tool_call_event_cannot_write_properties(after_tool_event):
    """Verify BidiAfterToolCallEvent protects certain properties."""
    with pytest.raises(AttributeError, match="Property agent is not writable"):
        after_tool_event.agent = Mock()
    with pytest.raises(AttributeError, match="Property selected_tool is not writable"):
        after_tool_event.selected_tool = None
    with pytest.raises(AttributeError, match="Property tool_use is not writable"):
        after_tool_event.tool_use = ToolUse(name="new", toolUseId="456", input={})
    with pytest.raises(AttributeError, match="Property invocation_state is not writable"):
        after_tool_event.invocation_state = {}
    with pytest.raises(AttributeError, match="Property exception is not writable"):
        after_tool_event.exception = Exception("test")
