"""Tests for Matrix presence and status message utilities."""

from __future__ import annotations

from types import SimpleNamespace
from unittest.mock import AsyncMock, MagicMock, patch

import nio
import pytest

from mindroom.config.agent import AgentConfig, TeamConfig
from mindroom.config.calls import CallsConfig, RealtimeCallProfile
from mindroom.config.main import Config
from mindroom.config.models import ModelConfig
from mindroom.constants import ROUTER_AGENT_NAME
from mindroom.matrix.presence import (
    build_agent_status_message,
    is_user_online,
    set_presence_status,
    should_use_streaming,
)


class TestSetPresenceStatus:
    """Test the set_presence_status function."""

    @pytest.mark.asyncio
    async def test_set_presence_success(self) -> None:
        """Test successfully setting presence status."""
        mock_client = AsyncMock()
        mock_response = nio.PresenceSetResponse()
        mock_client.set_presence.return_value = mock_response

        await set_presence_status(mock_client, "Test status", "online")

        mock_client.set_presence.assert_called_once_with("online", "Test status")

    @pytest.mark.asyncio
    async def test_set_presence_failure(self) -> None:
        """Test handling presence set failure."""
        mock_client = AsyncMock()
        mock_response = MagicMock()  # Not a PresenceSetResponse
        mock_client.set_presence.return_value = mock_response

        await set_presence_status(mock_client, "Test status")

        mock_client.set_presence.assert_called_once_with("online", "Test status")


class TestBuildAgentStatusMessage:
    """Test the build_agent_status_message function."""

    def test_router_agent_status(self) -> None:
        """Test building status message for router agent."""
        config = Config(
            router={"model": "gpt-4"},
            models={"gpt-4": ModelConfig(provider="openai", id="gpt-4-turbo")},
        )

        status = build_agent_status_message(ROUTER_AGENT_NAME, config)

        assert "🤖 Model: openai/gpt-4-turbo" in status
        assert "📍 Routes messages to appropriate agents or teams" in status

    def test_regular_agent_status_with_tools(self) -> None:
        """Test building status message for regular agent with tools."""
        config = Config(
            agents={
                "researcher": AgentConfig(
                    display_name="Research Agent",
                    role="Research specialist focused on finding information",
                    tools=["web_search", "arxiv", "wikipedia", "news"],
                    model="claude",
                ),
            },
            models={"claude": ModelConfig(provider="anthropic", id="claude-opus-5")},
            defaults={"tools": []},
        )

        status = build_agent_status_message("researcher", config)

        assert "🤖 Model: anthropic/claude-opus-5" in status
        assert "💼 Research specialist focused on finding information" in status
        assert "🔧 4 tools available" in status

    def test_regular_agent_status_no_tools(self) -> None:
        """Test building status message for regular agent without tools."""
        config = Config(
            agents={
                "assistant": AgentConfig(
                    display_name="General Assistant",
                    role="General purpose assistant",
                    model="default",
                ),
            },
            models={"default": ModelConfig(provider="ollama", id="llama3")},
            defaults={"tools": []},
        )

        status = build_agent_status_message("assistant", config)

        assert "🤖 Model: ollama/llama3" in status
        assert "💼 General purpose assistant" in status
        assert "🔧" not in status  # No tools section

    def test_only_runtime_ready_calls_agents_advertise_voice_calls(self) -> None:
        """Presence gives clients an exact runtime-ready voice-call capability signal."""
        config = Config(
            agents={
                "voice": AgentConfig(display_name="Voice"),
                "text": AgentConfig(display_name="Text"),
            },
            calls=CallsConfig(
                enabled=True,
                profiles={
                    "voice": RealtimeCallProfile(
                        backend="realtime",
                        model="gpt-realtime",
                        credentials_service="openai",
                        voice="marin",
                    ),
                },
                agents={"voice": "voice"},
            ),
        )

        assert "📞 Voice calls" in build_agent_status_message(
            "voice",
            config,
            voice_calls_available=True,
        ).split(" | ")
        assert "📞 Voice calls" not in build_agent_status_message("voice", config).split(" | ")
        assert "📞 Voice calls" not in build_agent_status_message("text", config).split(" | ")

    def test_team_agent_status(self) -> None:
        """Test building status message for team agent."""
        config = Config(
            agents={
                "researcher": AgentConfig(display_name="Researcher"),
                "analyst": AgentConfig(display_name="Analyst"),
                "writer": AgentConfig(display_name="Writer"),
                "reviewer": AgentConfig(display_name="Reviewer"),
                "editor": AgentConfig(display_name="Editor"),
                "fact_checker": AgentConfig(display_name="Fact Checker"),
            },
            teams={
                "research_team": TeamConfig(
                    display_name="Research Team",
                    role="Collaborative research team",
                    agents=["researcher", "analyst", "writer", "reviewer", "editor", "fact_checker"],
                    model="gpt-4",
                ),
            },
            models={"gpt-4": ModelConfig(provider="openai", id="gpt-4")},
        )

        status = build_agent_status_message("research_team", config)

        assert "🤖 Model: openai/gpt-4" in status
        assert "👥 Collaborative research team" in status
        assert "🤝 Team: researcher, analyst, writer, reviewer, editor" in status  # First 5 only
        assert "fact_checker" not in status  # 6th agent not shown

    def test_long_status_message(self) -> None:
        """Test that long status messages are built correctly."""
        config = Config(
            agents={
                "agent": AgentConfig(
                    display_name="Agent",
                    role="A" * 200,  # Very long role (truncated to 100 chars internally)
                    tools=["tool1", "tool2", "tool3", "tool4", "tool5", "tool6", "tool7", "tool8"],
                    model="very_long_model_name",
                ),
            },
            models={
                "very_long_model_name": ModelConfig(
                    provider="provider",
                    id="model_id",
                ),
            },
            defaults={"tools": []},
        )

        status = build_agent_status_message("agent", config)

        # Just verify it builds without error and includes expected parts
        assert "Model: provider/model_id" in status
        assert "A" * 100 in status  # Role truncated to 100 chars
        assert "8 tools available" in status

    def test_unknown_model_fallback(self) -> None:
        """Test fallback when model is not in config.models."""
        config = Config(
            agents={
                "agent": AgentConfig(
                    display_name="Agent",
                    model="unknown_model",
                ),
            },
        )

        status = build_agent_status_message("agent", config)

        assert "🤖 Model: unknown_model" in status

    def test_regular_agent_status_counts_default_tools(self) -> None:
        """Tool count includes defaults.tools when the agent has no local tools."""
        config = Config(
            agents={
                "assistant": AgentConfig(
                    display_name="Assistant",
                    role="General assistant",
                    tools=[],
                    model="default",
                ),
            },
            models={"default": ModelConfig(provider="ollama", id="llama3")},
            defaults={"tools": ["scheduler", "calculator"]},
        )

        status = build_agent_status_message("assistant", config)

        assert "🔧 2 tools available" in status

    def test_regular_agent_status_respects_include_default_tools_false(self) -> None:
        """Tool count excludes defaults.tools when agent opts out."""
        config = Config(
            agents={
                "assistant": AgentConfig(
                    display_name="Assistant",
                    role="General assistant",
                    tools=[],
                    include_default_tools=False,
                    model="default",
                ),
            },
            models={"default": ModelConfig(provider="ollama", id="llama3")},
            defaults={"tools": ["scheduler", "calculator"]},
        )

        status = build_agent_status_message("assistant", config)

        assert "🔧" not in status


class TestIsUserOnline:
    """Test the is_user_online function."""

    @pytest.mark.asyncio
    async def test_user_online(self) -> None:
        """Test detecting online user."""
        mock_client = AsyncMock()
        mock_response = MagicMock()
        mock_response.presence = "online"
        mock_response.last_active_ago = None
        mock_client.get_presence.return_value = mock_response

        result = await is_user_online(mock_client, "@user:example.com")

        assert result is True
        mock_client.get_presence.assert_called_once_with("@user:example.com")

    @pytest.mark.asyncio
    async def test_user_unavailable_considered_online(self) -> None:
        """Test that 'unavailable' status is considered online for streaming."""
        mock_client = AsyncMock()
        mock_response = MagicMock()
        mock_response.presence = "unavailable"  # Idle but client open
        mock_response.last_active_ago = 300000  # 5 minutes
        mock_client.get_presence.return_value = mock_response

        result = await is_user_online(mock_client, "@user:example.com")

        assert result is True

    @pytest.mark.asyncio
    async def test_user_offline(self) -> None:
        """Test detecting offline user."""
        mock_client = AsyncMock()
        mock_response = MagicMock()
        mock_response.presence = "offline"
        mock_response.last_active_ago = 3600000  # 1 hour
        mock_client.get_presence.return_value = mock_response

        result = await is_user_online(mock_client, "@user:example.com")

        assert result is False

    @pytest.mark.asyncio
    async def test_presence_error(self) -> None:
        """Test handling presence API error."""
        mock_client = AsyncMock()
        mock_response = nio.PresenceGetError(message="Not found")
        mock_client.get_presence.return_value = mock_response

        result = await is_user_online(mock_client, "@user:example.com")

        assert result is False

    @pytest.mark.asyncio
    async def test_presence_exception(self) -> None:
        """Test handling exception during presence check."""
        mock_client = AsyncMock()
        mock_client.get_presence.side_effect = Exception("Network error")

        result = await is_user_online(mock_client, "@user:example.com")

        assert result is False  # Defaults to False on error

    @pytest.mark.asyncio
    async def test_room_cache_hit_returns_without_presence_lookup(self) -> None:
        """Room-specific cache hit should short-circuit before hitting the API."""
        mock_client = SimpleNamespace(
            rooms={
                "!room:example.com": SimpleNamespace(
                    room_id="!room:example.com",
                    users={"@user:example.com": SimpleNamespace(presence="online", last_active_ago=0)},
                ),
            },
            get_presence=AsyncMock(),
        )

        result = await is_user_online(mock_client, "@user:example.com", room_id="!room:example.com")

        assert result is True
        mock_client.get_presence.assert_not_called()

    @pytest.mark.asyncio
    async def test_all_room_cache_hit_returns_without_presence_lookup(self) -> None:
        """Global cache scan should return early when any cached room marks the user online."""
        mock_client = SimpleNamespace(
            rooms={
                "!room-a:example.com": SimpleNamespace(
                    room_id="!room-a:example.com",
                    users={"@user:example.com": SimpleNamespace(presence="offline", last_active_ago=3600000)},
                ),
                "!room-b:example.com": SimpleNamespace(
                    room_id="!room-b:example.com",
                    users={"@user:example.com": SimpleNamespace(presence="unavailable", last_active_ago=300000)},
                ),
            },
            get_presence=AsyncMock(),
        )

        result = await is_user_online(mock_client, "@user:example.com")

        assert result is True
        mock_client.get_presence.assert_not_called()


class TestShouldUseStreaming:
    """Test the should_use_streaming function."""

    @pytest.mark.asyncio
    async def test_streaming_disabled_globally(self) -> None:
        """Test that streaming is disabled when enable_streaming is False."""
        mock_client = AsyncMock()

        result = await should_use_streaming(
            mock_client,
            "!room:example.com",
            "@user:example.com",
            enable_streaming=False,
        )

        assert result is False
        mock_client.get_presence.assert_not_called()

    @pytest.mark.asyncio
    async def test_streaming_no_requester(self) -> None:
        """Test defaulting to streaming when no requester specified."""
        mock_client = AsyncMock()

        result = await should_use_streaming(
            mock_client,
            "!room:example.com",
            None,
            enable_streaming=True,
        )

        assert result is True
        mock_client.get_presence.assert_not_called()

    @pytest.mark.asyncio
    async def test_streaming_user_online(self) -> None:
        """Test enabling streaming when user is online."""
        mock_client = AsyncMock()

        with patch("mindroom.matrix.presence.is_user_online", return_value=True) as mock_is_online:
            result = await should_use_streaming(
                mock_client,
                "!room:example.com",
                "@user:example.com",
                enable_streaming=True,
            )

        assert result is True
        mock_is_online.assert_called_once_with(
            mock_client,
            "@user:example.com",
            room_id="!room:example.com",
        )

    @pytest.mark.asyncio
    async def test_streaming_user_offline(self) -> None:
        """Test disabling streaming when user is offline."""
        mock_client = AsyncMock()

        with patch("mindroom.matrix.presence.is_user_online", return_value=False) as mock_is_online:
            result = await should_use_streaming(
                mock_client,
                "!room:example.com",
                "@user:example.com",
                enable_streaming=True,
            )

        assert result is False
        mock_is_online.assert_called_once_with(
            mock_client,
            "@user:example.com",
            room_id="!room:example.com",
        )
