"""Tests for CLI REPL module."""

from unittest.mock import AsyncMock, MagicMock, patch

import pytest

from fliiq.cli.repl import ChatSession, run_repl


@pytest.fixture
def mock_llm():
    """Create a mock LLM."""
    llm = AsyncMock()
    llm.generate = AsyncMock()
    return llm


@pytest.fixture
def mock_tools():
    """Create a mock ToolRegistry."""
    tools = MagicMock()
    tools.get_tool_definitions.return_value = []
    tools.execute = AsyncMock(return_value="tool result")
    return tools


@pytest.fixture
def chat_session(mock_llm, mock_tools, tmp_path):
    """Create a ChatSession for testing."""
    return ChatSession(
        llm=mock_llm,
        tools=mock_tools,
        soul="Test soul",
        user_soul=None,
        skill_info=[],
        memory_context=None,
        project_root=tmp_path,
        mode="autonomous",
    )


def test_chat_session_initial_state(chat_session):
    """ChatSession initializes with correct state."""
    assert chat_session.mode == "autonomous"
    assert chat_session.messages == []
    assert chat_session.iteration_total == 0


def test_chat_session_cycle_mode(chat_session):
    """cycle_mode cycles through modes correctly."""
    assert chat_session.mode == "autonomous"

    new_mode = chat_session.cycle_mode()
    assert new_mode == "plan"
    assert chat_session.mode == "plan"

    new_mode = chat_session.cycle_mode()
    assert new_mode == "supervised"
    assert chat_session.mode == "supervised"

    new_mode = chat_session.cycle_mode()
    assert new_mode == "autonomous"
    assert chat_session.mode == "autonomous"


def test_chat_session_clear(chat_session):
    """clear resets messages and iteration count."""
    chat_session.messages = [{"role": "user", "content": "test"}]
    chat_session.iteration_total = 5

    chat_session.clear()

    assert chat_session.messages == []
    assert chat_session.iteration_total == 0


def test_chat_session_status(chat_session):
    """status property returns correct info."""
    chat_session.messages = [{"role": "user", "content": "test"}]
    chat_session.iteration_total = 3

    status = chat_session.status
    assert status["mode"] == "autonomous"
    assert status["message_count"] == 1
    assert status["iteration_total"] == 3


async def test_chat_session_send_adds_message(chat_session, mock_llm):
    """send() adds user message and updates history."""
    from fliiq.runtime.agent.loop import AgentResult
    from fliiq.runtime.llm.providers import LLMProvider, LLMResponse

    # Mock the agent_loop to return a result
    mock_response = LLMResponse(
        content="Hello!",
        model="test",
        provider=LLMProvider.ANTHROPIC,
        stop_reason="end_turn",
        raw_content=[{"type": "text", "text": "Hello!"}],
    )
    mock_llm.generate.return_value = mock_response

    result = await chat_session.send("Hi there")

    assert result.final_text == "Hello!"
    assert chat_session.iteration_total == 1
    # First message should be the user message
    assert chat_session.messages[0]["role"] == "user"
    assert chat_session.messages[0]["content"] == "Hi there"


async def test_chat_session_preserves_history(chat_session, mock_llm):
    """send() preserves message history across turns."""
    from fliiq.runtime.llm.providers import LLMProvider, LLMResponse

    mock_response = LLMResponse(
        content="Response",
        model="test",
        provider=LLMProvider.ANTHROPIC,
        stop_reason="end_turn",
        raw_content=[{"type": "text", "text": "Response"}],
    )
    mock_llm.generate.return_value = mock_response

    await chat_session.send("First message")
    await chat_session.send("Second message")

    # Should have 4 messages: user1, assistant1, user2, assistant2
    assert len(chat_session.messages) == 4
    assert chat_session.messages[0]["content"] == "First message"
    assert chat_session.messages[2]["content"] == "Second message"


async def test_run_repl_exit_command():
    """run_repl exits on /exit command."""
    session = MagicMock()
    session.mode = "autonomous"
    session.status = {"mode": "autonomous", "message_count": 0, "iteration_total": 0}

    with patch("fliiq.cli.repl.display") as mock_display:
        # Simulate user typing /exit
        mock_display.console.input.return_value = "/exit"
        await run_repl(session)
        # Should have printed exit message
        assert mock_display.console.print.called


async def test_run_repl_help_command():
    """run_repl shows help on /help command."""
    session = MagicMock()
    session.mode = "autonomous"

    with patch("fliiq.cli.repl.display") as mock_display:
        # Simulate /help then /exit
        mock_display.console.input.side_effect = ["/help", "/exit"]
        await run_repl(session)
        # Should have called print_markdown for help
        assert mock_display.print_markdown.called


async def test_run_repl_mode_command():
    """run_repl cycles mode on /mode command."""
    session = MagicMock()
    session.mode = "autonomous"
    session.cycle_mode.return_value = "plan"

    with patch("fliiq.cli.repl.display") as mock_display:
        mock_display.console.input.side_effect = ["/mode", "/exit"]
        await run_repl(session)
        session.cycle_mode.assert_called_once()
        mock_display.print_mode.assert_called()


async def test_run_repl_clear_command():
    """run_repl clears session on /clear command."""
    session = MagicMock()
    session.mode = "autonomous"

    with patch("fliiq.cli.repl.display") as mock_display:
        mock_display.console.input.side_effect = ["/clear", "/exit"]
        await run_repl(session)
        session.clear.assert_called_once()


async def test_run_repl_status_command():
    """run_repl shows status on /status command."""
    session = MagicMock()
    session.mode = "autonomous"
    session.status = {"mode": "autonomous", "message_count": 5, "iteration_total": 10}

    with patch("fliiq.cli.repl.display") as mock_display:
        mock_display.console.input.side_effect = ["/status", "/exit"]
        await run_repl(session)
        mock_display.print_status.assert_called_with("autonomous", 5, 10)


async def test_run_repl_unknown_command():
    """run_repl shows error for unknown commands."""
    session = MagicMock()
    session.mode = "autonomous"

    with patch("fliiq.cli.repl.display") as mock_display:
        mock_display.console.input.side_effect = ["/unknown", "/exit"]
        await run_repl(session)
        # Should have printed unknown command message
        calls = [str(call) for call in mock_display.console.print.call_args_list]
        assert any("Unknown command" in str(c) for c in calls)


async def test_run_repl_keyboard_interrupt():
    """run_repl handles KeyboardInterrupt gracefully."""
    session = MagicMock()
    session.mode = "autonomous"

    with patch("fliiq.cli.repl.display") as mock_display:
        mock_display.console.input.side_effect = KeyboardInterrupt()
        # Should exit without raising
        await run_repl(session)
