"""Test probe_streaming_support returns promptly on infinite SSE streams.

Regression test for: probe_streaming_support must not hang by doing blocking GET on infinite SSE body.

Spec source: talk-streaming-transport.md Non-Streaming Fallback
"""

from __future__ import annotations

import asyncio
from pathlib import Path
from unittest.mock import MagicMock

import httpx
import pytest

from anima.shell.cli_commands.talk_streaming import probe_streaming_support


class MockStreamingResponse:
    """Mock httpx streaming response that simulates infinite SSE stream."""

    def __init__(self, status_code: int = 200):
        self.status_code = status_code
        self._entered = False

    async def __aenter__(self) -> "MockStreamingResponse":
        self._entered = True
        return self

    async def __aexit__(self, *args: object) -> None:
        """Context manager exit - close the stream."""
        pass


class TestProbeStreamingSupport:
    """Tests for probe_streaming_support promptness on infinite streams."""

    @pytest.mark.asyncio
    async def test_probe_returns_promptly_on_stream_endpoint(self) -> None:
        """Probe must return within timeout when /stream exists and sends infinite data.

        This is the regression test: old implementation used client.get() which would
        hang trying to read the full infinite SSE body. New implementation uses
        client.stream() to check status code only, then closes immediately.

        The test proves:
        1. Probe opens streaming request
        2. Probe checks status code
        3. Probe returns True/False without consuming body
        4. Probe completes within timeout (< 1 second)
        """
        # Track whether we closed the stream
        stream_closed = []

        class MockStreamingContextManager:
            """Mock httpx streaming context manager."""

            async def __aenter__(self) -> MockStreamingResponse:
                return MockStreamingResponse(status_code=200)

            async def __aexit__(self, *args: object) -> None:
                stream_closed.append(True)

        def mock_stream(method: str, url: str) -> MockStreamingContextManager:
            """Mock stream() that returns async context manager."""
            assert method == "GET"
            assert url == "http://localhost/stream"
            return MockStreamingContextManager()

        client = MagicMock(spec=httpx.AsyncClient)
        del client.stream  # Remove the spec attribute
        client.stream = mock_stream  # type: ignore[method-assign]

        socket_path = Path("/tmp/test.sock")

        # Must complete within 1 second (would timeout if old blocking GET used)
        result = await asyncio.wait_for(
            probe_streaming_support(client, socket_path), timeout=1.0
        )

        assert result is True, "Probe should return True for 200 status"
        assert stream_closed, "Probe must close the stream after checking status"

    @pytest.mark.asyncio
    async def test_probe_returns_false_on_404(self) -> None:
        """Probe returns False when /stream returns 404."""

        class MockStreamingContextManager:
            async def __aenter__(self) -> MockStreamingResponse:
                return MockStreamingResponse(status_code=404)

            async def __aexit__(self, *args: object) -> None:
                pass

        def mock_stream(method: str, url: str) -> MockStreamingContextManager:
            return MockStreamingContextManager()

        client = MagicMock(spec=httpx.AsyncClient)
        del client.stream
        client.stream = mock_stream  # type: ignore[method-assign]

        socket_path = Path("/tmp/test.sock")

        result = await probe_streaming_support(client, socket_path)

        assert result is False, "Probe should return False for 404"

    @pytest.mark.asyncio
    async def test_probe_returns_false_on_connection_error(self) -> None:
        """Probe returns False on connection error (old agent without /stream)."""

        class MockStreamingContextManager:
            async def __aenter__(self) -> None:
                raise httpx.ConnectError("Connection refused")

            async def __aexit__(self, *args: object) -> None:
                pass

        def mock_stream(method: str, url: str) -> MockStreamingContextManager:
            return MockStreamingContextManager()

        client = MagicMock(spec=httpx.AsyncClient)
        del client.stream
        client.stream = mock_stream  # type: ignore[method-assign]

        socket_path = Path("/tmp/test.sock")

        result = await probe_streaming_support(client, socket_path)

        assert result is False, "Probe should return False on connection error"

    @pytest.mark.asyncio
    async def test_probe_uses_stream_method_not_get(self) -> None:
        """Verify probe uses client.stream(), not client.get().

        This is critical: client.get() would hang on infinite SSE, but
        client.stream() allows checking headers and closing immediately.
        """

        method_calls: list[str] = []

        class MockStreamingContextManager:
            async def __aenter__(self) -> MockStreamingResponse:
                method_calls.append("stream(GET)")
                return MockStreamingResponse(status_code=200)

            async def __aexit__(self, *args: object) -> None:
                pass

        def mock_stream(method: str, url: str) -> MockStreamingContextManager:
            return MockStreamingContextManager()

        client = MagicMock(spec=httpx.AsyncClient)
        del client.stream
        client.stream = mock_stream  # type: ignore[method-assign]

        # Track get() calls to ensure it's NOT used
        async def mock_get(url: str) -> None:
            method_calls.append(f"get({url})")
            raise AssertionError(
                "Probe must NOT use client.get() - it blocks on infinite streams"
            )

        del client.get
        client.get = mock_get  # type: ignore[method-assign]

        socket_path = Path("/tmp/test.sock")

        result = await probe_streaming_support(client, socket_path)

        assert result is True
        assert "stream(GET)" in method_calls, "Probe must use stream('GET', ...)"
        assert all("get(" not in call for call in method_calls), (
            "Probe must NOT use client.get()"
        )
