from unittest.mock import patch

import pytest
from pydantic import BaseModel, Field

from src.broadie.agents import Agent
from src.broadie.factory import _build_channels, create_agent, create_sub_agent
from src.broadie.schemas import ChannelSchema, ChannelType, ModelProvider, ModelSchema, SubAgentSchema

# # Mock create_react_agent globally to prevent API calls
# @pytest.fixture(autouse=True)
# def mock_create_react_agent():
#     """Auto-mock create_react_agent to ensure unit tests only."""
#     with patch("src.broadie.agents.create_react_agent") as mock_create:
#         mock_runtime = AsyncMock()
#         mock_message = Mock()
#         mock_message.content = "Mocked response"
#         mock_runtime.ainvoke.return_value = {"messages": [mock_message]}
#         mock_create.return_value = mock_runtime
#         yield mock_create


class CustomOutput(BaseModel):
    """Custom output schema for testing."""

    result: str = Field(description="The result")
    status: str = Field(description="The status")


class TestBuildChannels:
    """Unit tests for _build_channels function."""

    def test_build_channels_empty_list(self):
        """Test _build_channels with empty list."""
        result = _build_channels([])
        assert result == []

    def test_build_channels_none(self):
        """Test _build_channels with None."""
        result = _build_channels(None)
        assert result == []

    def test_build_channels_with_channel_schema(self):
        """Test _build_channels with ChannelSchema objects."""
        channels = [
            ChannelSchema(type=ChannelType.slack, target="#general"),
            ChannelSchema(type=ChannelType.email, target="user@example.com"),
        ]
        result = _build_channels(channels)

        assert len(result) == 2
        assert all(isinstance(ch, ChannelSchema) for ch in result)
        assert result[0].type == ChannelType.slack
        assert result[1].type == ChannelType.email

    def test_build_channels_with_dict(self):
        """Test _build_channels with dictionary objects."""
        channels = [
            {"type": "slack", "target": "#general", "instructions": "Format nicely"},
            {"type": "email", "target": "admin@example.com"},
        ]
        result = _build_channels(channels)

        assert len(result) == 2
        assert all(isinstance(ch, ChannelSchema) for ch in result)
        assert result[0].type == ChannelType.slack
        assert result[0].target == "#general"
        assert result[0].instructions == "Format nicely"
        assert result[1].type == ChannelType.email
        assert result[1].target == "admin@example.com"

    def test_build_channels_mixed_types(self):
        """Test _build_channels with mixed ChannelSchema and dict objects."""
        channels = [
            ChannelSchema(type=ChannelType.slack, target="#general"),
            {"type": "email", "target": "user@example.com"},
        ]
        result = _build_channels(channels)

        assert len(result) == 2
        assert all(isinstance(ch, ChannelSchema) for ch in result)
        assert result[0].type == ChannelType.slack
        assert result[1].type == ChannelType.email

    def test_build_channels_invalid_type(self):
        """Test _build_channels with invalid channel type."""
        channels = ["invalid_channel"]

        with pytest.raises(ValueError, match="Invalid channel type"):
            _build_channels(channels)

    def test_build_channels_invalid_dict(self):
        """Test _build_channels with invalid dictionary."""
        channels = [{"invalid": "channel"}]

        with pytest.raises(Exception):  # Pydantic validation error
            _build_channels(channels)


class TestCreateSubAgent:
    """Unit tests for create_sub_agent function."""

    def test_create_sub_agent_minimal(self):
        """Test create_sub_agent with minimal parameters."""
        result = create_sub_agent(name="test_agent", prompt="Be helpful")

        assert isinstance(result, SubAgentSchema)
        assert result.name == "test_agent"
        assert result.description == "Be helpful"  # defaults to prompt
        assert result.prompt == "Be helpful"
        assert result.tools == []
        assert result.model is None
        assert result.strict_mode is True

    def test_create_sub_agent_full_parameters(self):
        """Test create_sub_agent with all parameters."""
        model = ModelSchema(provider=ModelProvider.google, name="gemini-2.0-flash")
        tools = ["search", "calculator"]

        result = create_sub_agent(
            name="assistant",
            prompt="You are helpful",
            tools=tools,
            description="Helper agent",
            model=model,
            output_schema=CustomOutput,
            strict_mode=False,
        )

        assert result.name == "assistant"
        assert result.description == "Helper agent"
        assert result.prompt == "You are helpful"
        assert result.tools == tools
        assert result.model == model
        assert result.strict_mode is False

    def test_create_sub_agent_with_callable_tools(self):
        """Test create_sub_agent with callable tools."""

        def dummy_tool():
            """A dummy tool for testing."""
            return "result"

        tools = [dummy_tool]
        result = create_sub_agent(name="test", prompt="help", tools=tools)

        assert len(result.tools) == 1
        assert callable(result.tools[0])

    def test_create_sub_agent_default_description(self):
        """Test create_sub_agent uses prompt as default description."""
        result = create_sub_agent(name="my_agent", prompt="help")
        assert result.description == "help"


class TestCreateAgent:
    """Unit tests for create_agent function."""

    @pytest.fixture(autouse=True)
    def mock_create_react_agent(self):
        """Auto-mock create_react_agent to prevent API calls."""
        with patch("src.broadie.channels.create_react_agent") as mock_create:
            mock_create.return_value = None
            yield mock_create

    def test_create_agent_minimal(self):
        """Test create_agent with minimal parameters."""
        agent = create_agent(name="test_agent", instruction="Be helpful")

        assert isinstance(agent, Agent)
        assert agent.name == "test_agent"
        assert agent.instruction == "Be helpful"
        assert agent.label == "Be helpful"  # defaults to instruction
        assert isinstance(agent.model, ModelSchema)
        assert agent.model.provider == ModelProvider.google
        assert agent.model.name == "gemini-2.0-flash"
        assert agent.tools == []
        assert agent.channels == []
        assert agent.subagent_router == {}
        assert agent.strict_mode is True

    @pytest.mark.skip(reason="OpenAI provider not installed in test environment")
    def test_create_agent_with_string_model(self):
        """Test create_agent with string model name."""
        agent = create_agent(
            name="test",
            instruction="help",
            model="gpt-4",
            provider=ModelProvider.openai,
        )

        assert agent.model.name == "gpt-4"
        assert agent.model.provider == ModelProvider.openai

    @pytest.mark.skip(reason="Anthropic provider not installed in test environment")
    def test_create_agent_with_model_schema(self):
        """Test create_agent with ModelSchema object."""
        model = ModelSchema(provider=ModelProvider.anthropic, name="claude-3")
        agent = create_agent(name="test", instruction="help", model=model)

        assert agent.model == model

    @pytest.mark.skip(reason="OpenAI provider not installed in test environment")
    def test_create_agent_with_string_provider(self):
        """Test create_agent with string provider."""
        agent = create_agent(
            name="test",
            instruction="help",
            model="gpt-4",
            provider="openai",
        )

        assert agent.model.provider == ModelProvider.openai

    def test_create_agent_full_parameters(self, mock_create_react_agent):
        """Test create_agent with all parameters."""
        model = ModelSchema(provider=ModelProvider.google, name="gemini-2.0-flash")
        channels = [{"type": "slack", "target": "#general"}]
        subagents = [{"name": "helper", "prompt": "Help with tasks", "description": "Helper agent"}]

        agent = create_agent(
            name="main_agent",
            instruction="Process requests",
            model=model,
            description="Main processing agent",
            channels=channels,
            subagents=subagents,
            output_schema=CustomOutput,
            strict_mode=False,
        )

        assert agent.name == "main_agent"
        assert agent.instruction == "Process requests"
        assert agent.label == "Main processing agent"
        assert agent.model == model
        assert agent.strict_mode is False
        assert len(agent.channels) == 1
        assert len(agent.subagent_router) == 1

    def test_create_agent_with_channel_schemas(self, mock_create_react_agent):
        """Test create_agent with ChannelSchema objects."""
        channels = [
            ChannelSchema(type=ChannelType.slack, target="#general"),
            ChannelSchema(type=ChannelType.email, target="admin@example.com"),
        ]

        agent = create_agent(name="test", instruction="help", channels=channels)

        assert len(agent.channels) == 2
        assert all(isinstance(ch, ChannelSchema) for ch in agent.channels)

    def test_create_agent_with_channel_dicts(self, mock_create_react_agent):
        """Test create_agent with channel dictionaries."""
        channels = [
            {"type": "slack", "target": "#general"},
            {"type": "api", "target": "https://api.example.com/webhook"},
        ]

        agent = create_agent(name="test", instruction="help", channels=channels)

        assert len(agent.channels) == 2
        assert agent.channels[0].type == ChannelType.slack
        assert agent.channels[1].type == ChannelType.api

    def test_create_agent_with_subagent_schemas(self):
        """Test create_agent with SubAgentSchema objects."""
        subagents = [
            SubAgentSchema(name="helper1", prompt="Be helpful", description="First helper"),
            SubAgentSchema(name="helper2", prompt="Be helpful", description="Second helper"),
        ]

        agent = create_agent(name="test", instruction="help", subagents=subagents)

        assert len(agent.subagent_router) == 2
        assert agent.subagent_router["helper1"].name == "helper1"
        assert agent.subagent_router["helper2"].name == "helper2"

    def test_create_agent_with_subagent_dicts(self):
        """Test create_agent with subagent dictionaries."""
        subagents = [
            {"name": "search_helper", "prompt": "Search for information", "description": "Help with searching"},
            {"name": "calc_helper", "prompt": "Calculate results", "description": "Help with calculations"},
        ]

        agent = create_agent(name="test", instruction="help", subagents=subagents)

        assert len(agent.subagent_router) == 2
        assert agent.subagent_router["search_helper"].name == "search_helper"
        assert agent.subagent_router["calc_helper"].name == "calc_helper"

    def test_create_agent_invalid_channel_type(self):
        """Test create_agent with invalid channel type."""
        channels = ["invalid_channel"]

        with pytest.raises(ValueError, match="Invalid channel type"):
            create_agent(name="test", instruction="help", channels=channels)

    def test_create_agent_invalid_subagent_type(self):
        """Test create_agent with invalid subagent type."""
        subagents = ["invalid_subagent"]

        with pytest.raises(ValueError, match="Invalid subagent type"):
            create_agent(name="test", instruction="help", subagents=subagents)

    def test_create_agent_with_callable_tools(self):
        """Test create_agent with callable tools."""

        def dummy_tool():
            """A dummy tool for testing."""
            return "result"

        def another_tool():
            """Another dummy tool for testing."""
            return "another result"

        tools = [dummy_tool, another_tool]
        agent = create_agent(name="test", instruction="help", tools=tools)

        assert len(agent.tools) == 2
        assert callable(agent.tools[0])
        assert callable(agent.tools[1])

    def test_create_agent_default_description(self):
        """Test create_agent uses instruction as default description."""
        agent = create_agent(name="test", instruction="Be a helpful assistant")
        assert agent.label == "Be a helpful assistant"

    def test_create_agent_returns_agent_instance(self):
        """Test create_agent returns proper Agent instance."""
        agent = create_agent(name="test", instruction="help")

        assert isinstance(agent, Agent)
        assert hasattr(agent, "run")
        assert hasattr(agent, "get_identity")

    def test_create_agent_with_mixed_channel_types(self, mock_create_react_agent):
        """Test create_agent with mixed channel types."""
        channels = [
            ChannelSchema(type=ChannelType.slack, target="#general"),
            {"type": "email", "target": "admin@example.com"},
        ]

        agent = create_agent(name="test", instruction="help", channels=channels)

        assert len(agent.channels) == 2
        assert agent.channels[0].type == ChannelType.slack
        assert agent.channels[1].type == ChannelType.email

    def test_create_agent_with_mixed_subagent_types(self):
        """Test create_agent with mixed subagent types."""
        subagents = [
            SubAgentSchema(name="helper1", prompt="Be helpful", description="First helper"),
            {"name": "helper2", "prompt": "Be helpful", "description": "Second helper"},
        ]

        agent = create_agent(name="test", instruction="help", subagents=subagents)

        assert len(agent.subagent_router) == 2
        assert agent.subagent_router["helper1"].name == "helper1"
        assert agent.subagent_router["helper2"].name == "helper2"
