"""Tests for ClaudeCodeClient using Claude Agent SDK (persistent session)."""
import pytest
from unittest.mock import AsyncMock, patch, MagicMock
from neo_cortex.llm import ClaudeCodeClient, LLMClient


class TestClaudeCodeClient:
    def test_implements_llm_client(self):
        client = ClaudeCodeClient(model="haiku")
        assert isinstance(client, LLMClient)

    def test_name_property(self):
        client = ClaudeCodeClient(model="sonnet")
        assert client.name == "claude-code/sonnet"

    def test_default_model_is_haiku(self):
        client = ClaudeCodeClient()
        assert "haiku" in client.name

    def test_not_connected_on_init(self):
        client = ClaudeCodeClient(model="haiku")
        assert client._client is None

    @pytest.mark.asyncio
    async def test_call_connects_and_queries(self):
        """First call should connect, then query the persistent session."""
        client = ClaudeCodeClient(model="haiku")

        mock_msg = MagicMock()
        mock_msg.content = '[{"topic":"test","content":"test"}]'

        mock_sdk_client = AsyncMock()
        mock_sdk_client.connect = AsyncMock()
        mock_sdk_client.query = AsyncMock()

        async def mock_receive():
            yield mock_msg
        mock_sdk_client.receive_response = mock_receive

        with patch("neo_cortex.llm.ClaudeCodeClient._ensure_connected") as mock_ensure:
            async def set_client():
                client._client = mock_sdk_client
            mock_ensure.side_effect = set_client

            result = await client.call("system prompt", "user text")
            assert "test" in result
            mock_sdk_client.query.assert_called_once()

    @pytest.mark.asyncio
    async def test_call_reuses_connection(self):
        """Second call should reuse existing connection, not reconnect."""
        client = ClaudeCodeClient(model="haiku")

        mock_msg = MagicMock()
        mock_msg.content = "ok"

        mock_sdk_client = AsyncMock()
        mock_sdk_client.query = AsyncMock()

        async def mock_receive():
            yield mock_msg
        mock_sdk_client.receive_response = mock_receive

        # Simulate already connected
        client._client = mock_sdk_client

        await client.call("prompt1", "text1")
        await client.call("prompt2", "text2")

        assert mock_sdk_client.query.call_count == 2

    @pytest.mark.asyncio
    async def test_close_disconnects(self):
        """close() should disconnect the persistent session."""
        client = ClaudeCodeClient(model="haiku")
        mock_sdk_client = AsyncMock()
        mock_sdk_client.disconnect = AsyncMock()
        client._client = mock_sdk_client

        await client.close()

        mock_sdk_client.disconnect.assert_called_once()
        assert client._client is None

    @pytest.mark.asyncio
    async def test_close_when_not_connected(self):
        """close() on unconnected client should not raise."""
        client = ClaudeCodeClient(model="haiku")
        await client.close()  # should not raise

    @pytest.mark.asyncio
    async def test_call_with_empty_text(self):
        """When text is empty, send only prompt."""
        client = ClaudeCodeClient(model="haiku")

        mock_msg = MagicMock()
        mock_msg.content = "ok"

        mock_sdk_client = AsyncMock()
        mock_sdk_client.query = AsyncMock()

        async def mock_receive():
            yield mock_msg
        mock_sdk_client.receive_response = mock_receive

        client._client = mock_sdk_client

        await client.call("Just a prompt", "")

        args, kwargs = mock_sdk_client.query.call_args
        assert args[0] == "Just a prompt"
        assert "session_id" in kwargs

    @pytest.mark.asyncio
    async def test_call_joins_prompt_and_text(self):
        """When text is provided, join with newline."""
        client = ClaudeCodeClient(model="haiku")

        mock_msg = MagicMock()
        mock_msg.content = "ok"

        mock_sdk_client = AsyncMock()
        mock_sdk_client.query = AsyncMock()

        async def mock_receive():
            yield mock_msg
        mock_sdk_client.receive_response = mock_receive

        client._client = mock_sdk_client

        await client.call("System instruction", "User data")

        args, kwargs = mock_sdk_client.query.call_args
        assert args[0] == "System instruction\nUser data"
        assert "session_id" in kwargs

    @pytest.mark.asyncio
    async def test_extracts_text_from_list_content(self):
        """Should handle SDK messages with list[Block] content."""
        client = ClaudeCodeClient(model="haiku")

        mock_block = MagicMock()
        mock_block.text = "extracted text"

        mock_msg = MagicMock()
        mock_msg.content = [mock_block]

        mock_sdk_client = AsyncMock()
        mock_sdk_client.query = AsyncMock()

        async def mock_receive():
            yield mock_msg
        mock_sdk_client.receive_response = mock_receive

        client._client = mock_sdk_client

        result = await client.call("prompt", "text")
        assert result == "extracted text"
