"""Test that the router agent joins all configured rooms."""

from __future__ import annotations

import asyncio
from pathlib import Path  # noqa: TC003
from unittest.mock import AsyncMock

import pytest

from mindroom.bot import TeamBot, create_bot_for_entity
from mindroom.config.agent import AgentConfig, TeamConfig
from mindroom.config.main import Config
from mindroom.constants import ROUTER_AGENT_NAME
from mindroom.matrix.client_room_admin import RoomJoinOutcome
from mindroom.matrix.state import MatrixState
from mindroom.matrix.users import AgentMatrixUser
from mindroom.orchestrator import _MultiAgentOrchestrator
from tests.conftest import (
    TEST_PASSWORD,
    bind_runtime_paths,
    install_runtime_cache_support,
    orchestrator_runtime_paths,
    runtime_paths_for,
)
from tests.identity_helpers import persist_entity_accounts


def _bind_runtime_paths(config: Config, tmp_path: Path) -> Config:
    runtime_paths = orchestrator_runtime_paths(
        tmp_path,
        config_path=tmp_path / "config.yaml",
    )
    bound_config = bind_runtime_paths(
        config,
        runtime_paths,
    )
    persist_entity_accounts(bound_config, runtime_paths)
    return bound_config


@pytest.fixture
def config_with_rooms() -> Config:
    """Create a config with agents and teams that have rooms."""
    return Config(
        agents={
            "agent1": AgentConfig(
                display_name="Agent 1",
                role="Test agent",
                rooms=["room1", "room2"],
            ),
            "agent2": AgentConfig(
                display_name="Agent 2",
                role="Another test agent",
                rooms=["room3"],
            ),
        },
        teams={
            "team1": TeamConfig(
                display_name="Team 1",
                role="Test team",
                agents=["agent1", "agent2"],
                rooms=["room4"],
            ),
        },
    )


@pytest.mark.asyncio
async def test_router_gets_all_configured_rooms(
    config_with_rooms: Config,
    monkeypatch: pytest.MonkeyPatch,
    tmp_path: Path,
) -> None:
    """Test that the router agent is configured to join all rooms from agents and teams."""
    config_with_rooms = _bind_runtime_paths(config_with_rooms, tmp_path)

    # Mock resolve_room_aliases to return the same aliases (no resolution)
    def mock_resolve_room_aliases(
        aliases: list[str],
        runtime_paths: object | None = None,
    ) -> list[str]:
        del runtime_paths
        return list(aliases)

    monkeypatch.setattr("mindroom.bot.resolve_room_aliases", mock_resolve_room_aliases)

    # Create a temporary user for the router
    router_user = AgentMatrixUser(
        agent_name=ROUTER_AGENT_NAME,
        user_id=f"@mindroom_{ROUTER_AGENT_NAME}:localhost",
        display_name="RouterAgent",
        password=TEST_PASSWORD,
    )

    # Create the router bot
    router_bot = create_bot_for_entity(
        ROUTER_AGENT_NAME,
        router_user,
        config_with_rooms,
        runtime_paths_for(config_with_rooms),
        tmp_path,
    )

    # Check that the router has all rooms
    expected_rooms = {"room1", "room2", "room3", "room4"}
    assert router_bot is not None
    assert set(router_bot.rooms) == expected_rooms


def test_team_bot_uses_defaults_streaming_setting(
    config_with_rooms: Config,
    monkeypatch: pytest.MonkeyPatch,
    tmp_path: Path,
) -> None:
    """Team bots should inherit defaults.enable_streaming from config."""
    config_with_rooms = _bind_runtime_paths(config_with_rooms, tmp_path)
    runtime_paths = runtime_paths_for(config_with_rooms)

    # Mock resolve_room_aliases to return the same aliases (no resolution)
    def mock_resolve_room_aliases(
        aliases: list[str],
        runtime_paths: object | None = None,
    ) -> list[str]:
        del runtime_paths
        return list(aliases)

    monkeypatch.setattr("mindroom.bot.resolve_room_aliases", mock_resolve_room_aliases)
    config_with_rooms.defaults.enable_streaming = False

    team_user = AgentMatrixUser(
        agent_name="team1",
        user_id="@mindroom_team1:localhost",
        display_name="Team 1",
        password=TEST_PASSWORD,
    )

    team_bot = create_bot_for_entity(
        "team1",
        team_user,
        config_with_rooms,
        runtime_paths,
        tmp_path,
    )

    assert team_bot is not None
    assert team_bot.enable_streaming is False


def test_team_bot_uses_persisted_member_usernames(
    config_with_rooms: Config,
    monkeypatch: pytest.MonkeyPatch,
    tmp_path: Path,
) -> None:
    """Team bots should resolve configured members through live persisted usernames."""
    config_with_rooms = _bind_runtime_paths(config_with_rooms, tmp_path)
    runtime_paths = runtime_paths_for(config_with_rooms)
    state = MatrixState.load(runtime_paths=runtime_paths)
    state.add_account("agent_agent1", "mindroom_agent1_oldns", "pw", domain=config_with_rooms.get_domain(runtime_paths))
    state.add_account("agent_agent2", "mindroom_agent2_oldns", "pw", domain=config_with_rooms.get_domain(runtime_paths))
    state.save(runtime_paths=runtime_paths)

    def mock_resolve_room_aliases(
        aliases: list[str],
        runtime_paths: object | None = None,
    ) -> list[str]:
        del runtime_paths
        return list(aliases)

    monkeypatch.setattr("mindroom.bot.resolve_room_aliases", mock_resolve_room_aliases)

    team_bot = create_bot_for_entity(
        "team1",
        AgentMatrixUser(
            agent_name="team1",
            user_id="@mindroom_team1:localhost",
            display_name="Team 1",
            password=TEST_PASSWORD,
        ),
        config_with_rooms,
        runtime_paths,
        tmp_path,
    )

    assert isinstance(team_bot, TeamBot)
    assert [member.full_id for member in team_bot.current_configured_team_agents()] == [
        "@mindroom_agent1_oldns:localhost",
        "@mindroom_agent2_oldns:localhost",
    ]


@pytest.mark.asyncio
async def test_router_joins_rooms_on_start(
    config_with_rooms: Config,
    monkeypatch: pytest.MonkeyPatch,
    tmp_path: Path,
) -> None:
    """Test that the router actually joins all configured rooms when started."""
    config_with_rooms = _bind_runtime_paths(config_with_rooms, tmp_path)
    # Track which rooms were joined
    joined_rooms: list[str] = []

    async def mock_join_room(_client: AsyncMock, room_id: str) -> RoomJoinOutcome:
        joined_rooms.append(room_id)
        return RoomJoinOutcome.JOINED

    monkeypatch.setattr("mindroom.bot_room_lifecycle.join_room", mock_join_room)
    monkeypatch.setattr("mindroom.bot_room_lifecycle.get_joined_rooms", AsyncMock(return_value=[]))

    # Mock restore_scheduled_tasks
    async def mock_restore_scheduled_tasks(
        _client: AsyncMock,
        _room_id: str,
        _config: Config,
        _runtime_paths: object,
        _event_cache: object,
        _conversation_cache: object,
    ) -> int:
        return 0

    monkeypatch.setattr("mindroom.bot.restore_scheduled_tasks", mock_restore_scheduled_tasks)

    # Mock resolve_room_aliases to return the same aliases (no resolution)
    def mock_resolve_room_aliases(
        aliases: list[str],
        runtime_paths: object | None = None,
    ) -> list[str]:
        del runtime_paths
        return list(aliases)

    monkeypatch.setattr("mindroom.bot.resolve_room_aliases", mock_resolve_room_aliases)

    # Create router user
    router_user = AgentMatrixUser(
        agent_name=ROUTER_AGENT_NAME,
        user_id=f"@mindroom_{ROUTER_AGENT_NAME}:localhost",
        display_name="RouterAgent",
        password=TEST_PASSWORD,
    )

    # Create and configure the router bot
    router_bot = create_bot_for_entity(
        ROUTER_AGENT_NAME,
        router_user,
        config_with_rooms,
        runtime_paths_for(config_with_rooms),
        tmp_path,
    )

    # Mock the client
    mock_client = AsyncMock()
    assert router_bot is not None
    router_bot.client = mock_client
    install_runtime_cache_support(router_bot)

    # Test that the router joins all configured rooms
    await router_bot.join_configured_rooms()

    # Verify all rooms were joined
    expected_rooms = {"room1", "room2", "room3", "room4"}
    assert set(joined_rooms) == expected_rooms


@pytest.mark.asyncio
@pytest.mark.requires_matrix  # Requires real Matrix server for router room management
@pytest.mark.timeout(10)  # Add timeout to prevent hanging on real server connection
async def test_orchestrator_creates_router_with_all_rooms(
    config_with_rooms: Config,
    monkeypatch: pytest.MonkeyPatch,
    tmp_path: Path,
) -> None:
    """Test that the orchestrator properly initializes the router with all rooms."""
    config_with_rooms = _bind_runtime_paths(config_with_rooms, tmp_path)

    # Mock resolve_room_aliases to return the same aliases (no resolution needed for test)
    def mock_resolve_room_aliases(
        aliases: list[str],
        runtime_paths: object | None = None,
    ) -> list[str]:
        del runtime_paths
        return list(aliases)

    monkeypatch.setattr("mindroom.bot.resolve_room_aliases", mock_resolve_room_aliases)

    # Mock load_config to return our test config
    def mock_load_config(_runtime_paths: object | None = None, **_kwargs: object) -> Config:
        return config_with_rooms

    monkeypatch.setattr("mindroom.orchestrator.load_config", mock_load_config)
    monkeypatch.setattr("mindroom.orchestrator._MultiAgentOrchestrator._ensure_user_account", AsyncMock())
    monkeypatch.setattr("mindroom.orchestrator._MultiAgentOrchestrator._setup_rooms_and_memberships", AsyncMock())

    # Create orchestrator
    orchestrator = _MultiAgentOrchestrator(runtime_paths=orchestrator_runtime_paths(tmp_path))

    try:
        # Initialize (creates all bots)
        await orchestrator.initialize()

        # Check that router exists and has all rooms
        assert ROUTER_AGENT_NAME in orchestrator.agent_bots
        router_bot = orchestrator.agent_bots[ROUTER_AGENT_NAME]

        expected_rooms = {"room1", "room2", "room3", "room4"}
        assert set(router_bot.rooms) == expected_rooms
    finally:
        await orchestrator.stop()


@pytest.mark.asyncio
@pytest.mark.requires_matrix  # Requires real Matrix server for router room updates
@pytest.mark.timeout(10)  # Add timeout to prevent hanging on real server connection
async def test_router_updates_rooms_on_config_change(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None:
    """Test that the router updates its room list when config changes."""
    # Initial config with some rooms
    initial_config = Config(
        agents={
            "agent1": AgentConfig(
                display_name="Agent 1",
                role="Test agent",
                rooms=["room1"],
            ),
        },
    )

    # Updated config with more rooms
    updated_config = Config(
        agents={
            "agent1": AgentConfig(
                display_name="Agent 1",
                role="Test agent",
                rooms=["room1", "room2"],
            ),
            "agent2": AgentConfig(
                display_name="Agent 2",
                role="New agent",
                rooms=["room3"],
            ),
        },
    )

    def mock_resolve_room_aliases(
        aliases: list[str],
        runtime_paths: object | None = None,
    ) -> list[str]:
        del runtime_paths
        return list(aliases)

    monkeypatch.setattr("mindroom.bot.resolve_room_aliases", mock_resolve_room_aliases)

    # Mock load_config to return different configs on different calls
    load_config_returns = [initial_config, updated_config]
    load_config_counter = [0]

    def mock_load_config(_runtime_paths: object | None = None, **_kwargs: object) -> Config:
        result = load_config_returns[min(load_config_counter[0], len(load_config_returns) - 1)]
        load_config_counter[0] += 1
        return result

    monkeypatch.setattr("mindroom.orchestrator.load_config", mock_load_config)
    monkeypatch.setattr("mindroom.orchestration.config_lifecycle.load_config", mock_load_config)
    monkeypatch.setattr("mindroom.orchestrator._MultiAgentOrchestrator._ensure_user_account", AsyncMock())
    monkeypatch.setattr("mindroom.orchestrator._MultiAgentOrchestrator._setup_rooms_and_memberships", AsyncMock())

    # Create orchestrator with initial config
    # Mock start/sync_forever at class level so newly created bots in update_config don't perform real login/sync
    monkeypatch.setattr("mindroom.bot.AgentBot.start", AsyncMock())
    monkeypatch.setattr("mindroom.bot.AgentBot.sync_forever", AsyncMock())
    monkeypatch.setattr("mindroom.bot.AgentBot.join_configured_rooms", AsyncMock())
    monkeypatch.setattr("mindroom.bot.AgentBot.leave_unconfigured_rooms", AsyncMock())
    monkeypatch.setattr("mindroom.bot.TeamBot.start", AsyncMock())
    monkeypatch.setattr("mindroom.bot.TeamBot.sync_forever", AsyncMock())
    monkeypatch.setattr("mindroom.bot.TeamBot.join_configured_rooms", AsyncMock())
    monkeypatch.setattr("mindroom.bot.TeamBot.leave_unconfigured_rooms", AsyncMock())

    orchestrator = _MultiAgentOrchestrator(runtime_paths=orchestrator_runtime_paths(tmp_path))

    try:
        await orchestrator.initialize()

        # Check initial router rooms
        router_bot = orchestrator.agent_bots[ROUTER_AGENT_NAME]
        assert set(router_bot.rooms) == {"room1"}

        # Mock bot operations using monkeypatch to avoid method assignment errors
        async def mock_stop(*, shutdown_intent: object | None = None) -> None:
            del shutdown_intent

        async def mock_start() -> None:
            pass

        async def mock_ensure_user_account() -> None:
            pass

        async def mock_sync_forever() -> None:
            raise asyncio.CancelledError

        for bot in orchestrator.agent_bots.values():
            monkeypatch.setattr(bot, "stop", mock_stop)
            monkeypatch.setattr(bot, "start", mock_start)
            monkeypatch.setattr(bot, "ensure_user_account", mock_ensure_user_account)
            monkeypatch.setattr(bot, "sync_forever", mock_sync_forever)

        # Update config
        updated = await orchestrator.config_reload._update_config()
        assert updated  # Should return True since router needs restart

        # Router should be recreated with new rooms
        new_router_bot = orchestrator.agent_bots[ROUTER_AGENT_NAME]
        assert set(new_router_bot.rooms) == {"room1", "room2", "room3"}
    finally:
        await orchestrator.stop()
