"""Interactive chat tests using fake models on the real server.

Tests that `navari chat` correctly displays agent messages, tool cards,
and prompts for user input via stdin/stdout, using special commands
that activate fake models on the server.

Requires:
1. A running Navari server with test models enabled (DEV)
2. Valid credentials (run `navari login <url>` first)

Run with: uv run pytest cli/tests/integration/test_chat_interactive.py -v -s
"""

import json
import os
import subprocess
import threading
import time

import pytest
from navari_cli.cli import app
from typer.testing import CliRunner

runner = CliRunner()

# Environment for subprocess: force UTF-8 output and disable Rich formatting
# to avoid Windows cp1252 encoding errors with Unicode characters (e.g. checkmarks)
_SUBPROCESS_ENV = {**os.environ, "PYTHONIOENCODING": "utf-8", "NO_COLOR": "1"}


@pytest.fixture
def _require_server(server_url):
    """Skip if no server configured."""


@pytest.mark.usefixtures("_require_server")
class TestChatAskTools:
    """Test interactive chat with :test_ask_tools fake model via real CLI process.

    The :test_ask_tools fake model sends the following tool calls:
    1. send_message_to_user: "Hello user, let's mock some questions!"
    2. ask_user: "What do you want to know?"  -> waits for input
    3. send_message_to_user: "ok next question:"
    4. ask_user_with_examples: "select an option" -> waits for input
    5. ask_user_for_confirmation: "Would you like to continue?" -> waits for input
    6. final_result: "thank you"

    Tests run the CLI as a subprocess with stdin piped, simulating
    a user typing messages in the REPL.
    """

    def test_ask_tools_full_interactive(self):
        """Verifies:
        - "Created conversation" status with title and ID
        - send_message_to_user content displayed (no tool card)
        - Tool cards for ask_user, ask_user_with_examples, ask_user_for_confirmation
        - ask_user_with_examples shows numbered examples
        - final_result tool card and summary
        """
        # Stdin lines are written with delays via Popen so the CLI processes
        # each answer before the next one arrives (simulates a real user).
        # (line, wait_after_in_seconds)
        stdin_script = [
            (":test_ask_tools", 1),  # Send query, wait for server to process
            ("I want to know about weather", 1),  # Response to ask_user
            ("first option", 1),  # Response to ask_user_with_examples
            ("yes", 1),  # Response to ask_user_for_confirmation
            ("/quit", 0),  # Exit the REPL
        ]

        proc = subprocess.Popen(
            ["navari", "chat", "-n", "-t", "CLI Ask Tools Interactive"],
            stdin=subprocess.PIPE,
            stdout=subprocess.PIPE,
            stderr=subprocess.PIPE,
            encoding="utf-8",
            env=_SUBPROCESS_ENV,
        )

        # Read stdout/stderr in threads (communicate() can't be used because
        # it closes stdin immediately, killing delayed writes).
        stdout_parts: list[str] = []
        stderr_parts: list[str] = []

        def _read_stdout():
            for line in proc.stdout:
                stdout_parts.append(line)

        def _read_stderr():
            for line in proc.stderr:
                stderr_parts.append(line)

        def _write_stdin():
            try:
                for line, wait_after in stdin_script:
                    proc.stdin.write(line + "\n")
                    proc.stdin.flush()
                    if wait_after:
                        time.sleep(wait_after)
                proc.stdin.close()
            except (OSError, ValueError):
                pass  # Process already exited

        threads = [
            threading.Thread(target=_read_stdout, daemon=True),
            threading.Thread(target=_read_stderr, daemon=True),
            threading.Thread(target=_write_stdin, daemon=True),
        ]
        for t in threads:
            t.start()
        proc.wait(timeout=120)
        for t in threads:
            t.join(timeout=5)

        output = "".join(stdout_parts) + "".join(stderr_parts)

        # Verify conversation was created
        assert "Created conversation" in output, f"Missing conversation creation in:\n{output}"

        # Verify send_message_to_user content is displayed (no tool card for this one)
        assert "Hello user" in output, f"Missing greeting in:\n{output}"

        # Verify tool cards are shown for ask_user tools (consistent with frontend)
        assert 'use tool "ask_user"' in output, f"Missing ask_user tool card in:\n{output}"
        assert 'use tool "ask_user_with_examples"' in output, f"Missing ask_user_with_examples tool card in:\n{output}"
        assert 'use tool "ask_user_for_confirmation"' in output, (
            f"Missing ask_user_for_confirmation tool card in:\n{output}"
        )

        # Verify ask_user question is displayed
        assert "What do you want to know" in output, f"Missing ask_user question in:\n{output}"

        # Verify ask_user_with_examples shows examples
        assert "first available option" in output, f"Missing example 1 in:\n{output}"
        assert "second option" in output, f"Missing example 2 in:\n{output}"

        # Verify ask_user_for_confirmation question
        assert "Would you like to continue" in output, f"Missing confirmation question in:\n{output}"

        # Verify final_result tool card and summary
        assert 'use tool "final_result"' in output, f"Missing final_result tool card in:\n{output}"
        assert "thank you" in output.lower(), f"Missing final result in:\n{output}"


@pytest.mark.usefixtures("_require_server")
class TestChatTodoList:
    """Test interactive chat with :test_todo_list fake model.

    The :test_todo_list model calls send_message_to_user and write_to_do_file
    multiple times, then final_result. Verifies that the CLI displays all
    tool cards and agent messages correctly.
    """

    # Expected strings that should appear in both chat output and stored messages
    EXPECTED = [
        "Starting authentication system implementation",
        "Created task list",
        "write_to_do_file",
        "Build a User Authentication System",
        "Design authentication architecture",
        "Authentication system implementation completed",
    ]

    def test_todo_list_tool_display(self):
        """Verifies:
        - "Joining" status shows title and conversation ID
        - Tool cards and agent messages are displayed correctly
        - Todo list summary with done count is rendered
        - "Ending chat session" includes conversation ID
        - Auto-rename: conversation title derived from first message
        - `conversations messages` renders the same data as live chat
        """
        # Create conversation with default title
        result = runner.invoke(app, ["conversations", "create", "--format", "json"])
        assert result.exit_code == 0
        conv = json.loads(result.output)
        conv_id = conv["id"]
        assert conv["title"] == "New Conversation"

        # Run chat via subprocess against the created conversation
        stdin_input = ":test_todo_list\n/quit\n"

        chat_result = subprocess.run(
            ["navari", "chat", conv_id],
            input=stdin_input,
            capture_output=True,
            encoding="utf-8",
            timeout=120,
            env=_SUBPROCESS_ENV,
        )

        chat_output = chat_result.stdout + chat_result.stderr

        # Verify "Joining" status shows title and ID
        assert f"Joining: New Conversation ({conv_id})" in chat_output, (
            f"Missing 'Joining' status in chat output:\n{chat_output}"
        )

        # Verify expected content in live chat output
        for expected in self.EXPECTED:
            assert expected in chat_output, f"Missing '{expected}' in chat output:\n{chat_output}"

        # Chat-only assertions (not in stored messages)
        assert "done" in chat_output, f"Missing done count in chat output:\n{chat_output}"

        # Verify "Ending" message includes conversation ID
        assert f"Ending chat session ({conv_id})" in chat_output, (
            f"Missing conversation ID in ending message:\n{chat_output}"
        )

        # Verify auto-rename: title should be derived from ":test_todo_list"
        result = runner.invoke(app, ["conversations", "get", conv_id, "--format", "json"])
        assert result.exit_code == 0
        conv = json.loads(result.output)
        assert conv["title"] == ":test_todo_list", (
            f"Expected auto-renamed title ':test_todo_list', got '{conv['title']}'"
        )

        # Verify the same data renders in `conversations messages` (plain format)
        msg_result = runner.invoke(app, ["conversations", "messages", conv_id])
        assert msg_result.exit_code == 0, f"messages command failed:\n{msg_result.output}"
        msg_output = msg_result.output

        for expected in self.EXPECTED:
            assert expected in msg_output, f"Missing '{expected}' in messages:\n{msg_output}"

        # Plain format should also render the todo done count
        assert "done" in msg_output, f"Missing done count in messages:\n{msg_output}"


@pytest.mark.usefixtures("_require_server")
class TestChatErrorsContinue:
    """Test interactive chat with :test_errors_continue fake model.

    The :test_errors_continue model triggers 4 consecutive HTTP errors,
    exceeding the retry limit (LLM_PROVIDER_MAX_RETRIES=3). The server
    should exhaust retries and send an error event. The CLI should display
    the error gracefully and allow the user to continue the conversation.
    """

    def test_errors_continue_and_recover(self):
        """Verifies:
        - "Created conversation" status with title and ID
        - Error event is displayed after retries exhausted (4 errors > max 3 retries)
        - REPL doesn't hang or crash — accepts /quit cleanly
        """
        stdin_lines = [
            ":test_errors_continue",  # Triggers 4 errors that exhaust retries (max=3)
            "/quit",
        ]
        stdin_input = "\n".join(stdin_lines) + "\n"

        result = subprocess.run(
            ["navari", "chat", "-n", "-t", "CLI Errors Continue Test"],
            input=stdin_input,
            capture_output=True,
            encoding="utf-8",
            timeout=120,
            env=_SUBPROCESS_ENV,
        )

        output = result.stdout + result.stderr

        # Verify conversation was created
        assert "Created conversation" in output, f"Missing conversation creation in:\n{output}"

        # Verify the error was displayed (server sends error event after retries exhausted)
        assert "Error:" in output, f"Missing error message in:\n{output}"

        # Verify the REPL didn't crash — it accepted /quit cleanly
        assert result.returncode == 0, f"CLI exited with code {result.returncode}:\n{output}"
