"""Tests for concurrent streaming in the sidecar."""

from __future__ import annotations

import asyncio
from typing import Any
from unittest.mock import MagicMock, patch

import pytest
from icarus.session.thread_state import ThreadState, ThreadStateManager


class TestSidecarConcurrency:
    """Verify multi-stream behavior."""

    @pytest.fixture
    def sidecar(self) -> Any:
        from icarus.sidecar.main import Sidecar
        s = Sidecar()
        s._agent = MagicMock()
        return s

    def test_require_agent_includes_thread_id(self, sidecar: Any) -> None:
        sidecar._agent = None
        with patch("icarus.sidecar.main.write_response") as mock_write:
            result = sidecar._require_agent("send", thread_id="t1")
            assert result is False
            calls = [c[0][0] for c in mock_write.call_args_list]
            for call in calls:
                assert call.get("thread_id") == "t1"

    @pytest.mark.asyncio
    async def test_per_thread_hitl_resolve(self, sidecar: Any) -> None:
        """HITL approve resolves only the target thread's future."""
        engine = sidecar._engine
        # Pre-set threads to STREAMING so HITL transition is valid
        engine._state_manager.set_state("t1", ThreadState.STREAMING)
        engine._state_manager.set_state("t2", ThreadState.STREAMING)

        emitted: list[dict] = []
        resolver1 = engine._make_hitl_resolver("t1", emitted.append)
        resolver2 = engine._make_hitl_resolver("t2", emitted.append)

        async def simulate_hitl(resolver) -> dict[str, Any]:
            return await resolver(
                [{"name": "execute", "args": {}}], "default",
            )

        task1 = asyncio.create_task(simulate_hitl(resolver1))
        task2 = asyncio.create_task(simulate_hitl(resolver2))
        await asyncio.sleep(0)

        assert "t1" in engine._hitl_futures
        assert "t2" in engine._hitl_futures

        engine.approve("t1")
        result1 = await task1
        assert result1 == {"type": "approve"}

        # t2 still pending
        assert "t2" in engine._hitl_futures
        assert not engine._hitl_futures["t2"].done()

        engine.reject("t2")
        result2 = await task2
        assert result2 == {"type": "reject"}

    @pytest.mark.asyncio
    async def test_send_resets_error_state(self, sidecar: Any) -> None:
        """Sending to a thread in error state resets to idle first."""
        engine = sidecar._engine
        engine._known_threads.add("t1")
        engine._state_manager.set_state("t1", ThreadState.STREAMING)
        engine._state_manager.set_state("t1", ThreadState.ERROR)

        with patch("icarus.sidecar.main.write_response"):
            with patch.object(engine, "_stream", return_value=None):
                await sidecar._handle_send(
                    {"cmd": "send", "thread_id": "t1", "content": "hello again"}
                )

        # Should be STREAMING now (error -> idle -> streaming)
        assert engine._state_manager.get_state("t1") == ThreadState.STREAMING
