"""Tests for slash-command hints and Tab completion in the input box."""

from __future__ import annotations

from pathlib import Path

import pytest

from clients.terminal.tui.tui_app import NaviCodeTui


@pytest.fixture(autouse=True)
def tmp_state_dir(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> Path:
    """Override the state dir so tests never touch ~/.navi_code."""
    from clients.terminal import config

    original = config.settings.state_dir
    config.settings.state_dir = tmp_path
    import clients.terminal.tui.settings as settings_module

    settings_module._tui_settings = None
    yield tmp_path
    config.settings.state_dir = original
    settings_module._tui_settings = None


@pytest.fixture(autouse=True)
def mock_tui_api(monkeypatch: pytest.MonkeyPatch) -> None:
    import clients.terminal.api as api_module

    async def fake_create_session(profile_id=None):
        return {"session_id": "test-session", "profile_id": "navi_code"}

    async def fake_get_session(sid):
        return {"session_id": sid, "profile_id": "navi_code"}

    async def fake_list_sessions():
        return []

    async def fake_get_profile_model(pid):
        return "configured-model"

    async def fake_list_terminals(session_id):
        return []

    monkeypatch.setattr(api_module, "create_session", fake_create_session)
    monkeypatch.setattr(api_module, "get_session", fake_get_session)
    monkeypatch.setattr(api_module, "list_sessions", fake_list_sessions)
    monkeypatch.setattr(api_module, "get_profile_model", fake_get_profile_model)
    monkeypatch.setattr(api_module, "list_terminals", fake_list_terminals)


async def _set_text(pilot, text: str) -> None:
    """Set the prompt text and let the TUI react to the change message."""
    input_box = pilot.app.query_one("InputBox")
    input_box._input.text = text
    await pilot.pause()


@pytest.mark.anyio
async def test_hints_hidden_without_slash() -> None:
    async with NaviCodeTui(new_session=True).run_test() as pilot:
        await pilot.pause()
        await _set_text(pilot, "hello there")
        hints = pilot.app.query_one("CommandHints")
        assert hints.display is False


@pytest.mark.anyio
async def test_hints_shown_for_slash_prefix() -> None:
    async with NaviCodeTui(new_session=True).run_test() as pilot:
        await pilot.pause()
        await _set_text(pilot, "/s")
        hints = pilot.app.query_one("CommandHints")
        assert hints.display is True
        names = {c.meta.name for c in hints._matches}
        assert "sessions" in names and "switch" in names


@pytest.mark.anyio
async def test_hints_show_all_for_bare_slash() -> None:
    async with NaviCodeTui(new_session=True).run_test() as pilot:
        await pilot.pause()
        await _set_text(pilot, "/")
        hints = pilot.app.query_one("CommandHints")
        assert hints.display is True
        assert len(hints._matches) >= 1


@pytest.mark.anyio
async def test_hints_hidden_once_whitespace_typed() -> None:
    async with NaviCodeTui(new_session=True).run_test() as pilot:
        await pilot.pause()
        await _set_text(pilot, "/switch abc")
        hints = pilot.app.query_one("CommandHints")
        assert hints.display is False


@pytest.mark.anyio
async def test_hints_hidden_when_no_match() -> None:
    async with NaviCodeTui(new_session=True).run_test() as pilot:
        await pilot.pause()
        await _set_text(pilot, "/zzzzzz")
        hints = pilot.app.query_one("CommandHints")
        assert hints.display is False


@pytest.mark.anyio
async def test_tab_completes_command_name() -> None:
    async with NaviCodeTui(new_session=True).run_test() as pilot:
        await pilot.pause()
        input_box = pilot.app.query_one("InputBox")
        await _set_text(pilot, "/sw")
        await pilot.press("tab")
        await pilot.pause()
        assert input_box._input.text == "/switch "


def test_tab_bare_slash_without_hints_does_not_auto_pick() -> None:
    """A bare ``/`` with no hint list available must not auto-complete to the
    first command in the registry — the user has chosen nothing (3.B3)."""
    from clients.terminal.tui.widgets.input_box import _PromptInput

    p = _PromptInput(text="/", hints=None)
    assert p._complete_command() is False
    assert p.text == "/"


@pytest.mark.anyio
async def test_tab_does_not_complete_without_slash() -> None:
    async with NaviCodeTui(new_session=True).run_test() as pilot:
        await pilot.pause()
        input_box = pilot.app.query_one("InputBox")
        await _set_text(pilot, "sw")
        await pilot.press("tab")
        await pilot.pause()
        # No slash -> completion not applied; text unchanged (or default tab behavior).
        assert input_box._input.text == "sw"


@pytest.mark.anyio
async def test_tab_does_not_complete_when_args_present() -> None:
    async with NaviCodeTui(new_session=True).run_test() as pilot:
        await pilot.pause()
        input_box = pilot.app.query_one("InputBox")
        await _set_text(pilot, "/switch abc")
        await pilot.press("tab")
        await pilot.pause()
        assert input_box._input.text == "/switch abc"


@pytest.mark.anyio
async def test_enter_runs_slash_command_not_sent_to_agent() -> None:
    """A slash command on Enter is dispatched as a command, not a user message."""
    async with NaviCodeTui(new_session=True).run_test() as pilot:
        await pilot.pause()
        chat = pilot.app.query_one("ChatPanel")
        before = len(chat._model.items)
        await _set_text(pilot, "/help")
        await pilot.press("enter")
        await pilot.pause()
        # /help emits a status entry into the chat model — no user_message added.
        kinds = [i.kind for i in chat._model.items[before:]]
        assert "user_message" not in kinds
        assert any(k in ("status", "error") for k in kinds)


@pytest.mark.anyio
async def test_down_arrow_moves_highlight() -> None:
    async with NaviCodeTui(new_session=True).run_test() as pilot:
        await pilot.pause()
        await _set_text(pilot, "/t")  # matches: terminals, thinking, themes
        hints = pilot.app.query_one("CommandHints")
        assert hints.current_match().meta.name == "terminals"
        await pilot.press("down")
        await pilot.pause()
        assert hints.current_match().meta.name == "thinking"
        await pilot.press("up")
        await pilot.pause()
        assert hints.current_match().meta.name == "terminals"


@pytest.mark.anyio
async def test_arrow_keys_wrap_around() -> None:
    async with NaviCodeTui(new_session=True).run_test() as pilot:
        await pilot.pause()
        await _set_text(pilot, "/t")
        hints = pilot.app.query_one("CommandHints")
        # Up from the top wraps to the bottom.
        await pilot.press("up")
        await pilot.pause()
        assert hints.current_match().meta.name == "themes"


@pytest.mark.anyio
async def test_arrows_no_op_when_hints_hidden() -> None:
    async with NaviCodeTui(new_session=True).run_test() as pilot:
        await pilot.pause()
        input_box = pilot.app.query_one("InputBox")
        await _set_text(pilot, "hello")
        await pilot.press("down")
        await pilot.press("up")
        await pilot.pause()
        assert input_box._input.text == "hello"


@pytest.mark.anyio
async def test_enter_runs_highlighted_command(monkeypatch: pytest.MonkeyPatch) -> None:
    """Enter while hints are open runs the highlighted command, not the typed prefix."""
    invoked: list[tuple[str, str]] = []

    async def spy(self, cmd, args: str) -> None:
        invoked.append((cmd.meta.name, args))

    monkeypatch.setattr(NaviCodeTui, "_command_worker", spy)

    async with NaviCodeTui(new_session=True).run_test() as pilot:
        await pilot.pause()
        await _set_text(pilot, "/t")  # matches: terminals (top), thinking, themes
        await pilot.press("down")  # highlight thinking
        await pilot.press("enter")
        await pilot.pause()
        assert invoked == [("thinking", "")]


@pytest.mark.anyio
async def test_enter_without_highlight_runs_top_match(monkeypatch: pytest.MonkeyPatch) -> None:
    invoked: list[tuple[str, str]] = []

    async def spy(self, cmd, args: str) -> None:
        invoked.append((cmd.meta.name, args))

    monkeypatch.setattr(NaviCodeTui, "_command_worker", spy)

    async with NaviCodeTui(new_session=True).run_test() as pilot:
        await pilot.pause()
        await _set_text(pilot, "/h")  # only "help" matches
        await pilot.press("enter")
        await pilot.pause()
        assert invoked == [("help", "")]


# ── message history (Up/Down recall) ────────────────────────────────────────


def test_append_history_caps_and_dedups_consecutive() -> None:
    from clients.terminal.tui.widgets.input_box import InputBox

    box = InputBox()
    for i in range(15):
        box.append_history(f"m{i}")
    assert len(box._history) == 10
    assert box._history == [f"m{i}" for i in range(5, 15)]
    # Consecutive duplicate is suppressed.
    box.append_history("m14")
    assert len(box._history) == 10
    assert box._history[-1] == "m14"
    # A different entry after it IS recorded.
    box.append_history("m14")
    box.append_history("new")
    assert box._history[-1] == "new"


def test_append_history_skips_commands_and_shell() -> None:
    from clients.terminal.tui.widgets.input_box import InputBox

    box = InputBox()
    box.append_history("/themes")
    box.append_history("!ls -la")
    box.append_history("hello agent")
    box.append_history("/help")
    assert box._history == ["hello agent"]


def test_history_up_down_navigation() -> None:
    from clients.terminal.tui.widgets.input_box import InputBox

    box = InputBox()
    for m in ("a", "b", "c"):
        box.append_history(m)
    # Up from empty → newest, then older, then stay at oldest.
    assert box.history_up("") == "c"
    assert box.history_up("c") == "b"
    assert box.history_up("b") == "a"
    assert box.history_up("a") is None  # at oldest — stay put
    # Down back toward newest, then past-end restores the draft (empty).
    assert box.history_down() == "b"
    assert box.history_down() == "c"
    assert box.history_down() == ""
    assert not box.browsing


def test_history_up_empty_history_returns_none() -> None:
    from clients.terminal.tui.widgets.input_box import InputBox

    box = InputBox()
    assert box.history_up("") is None
    assert not box.browsing


def test_history_down_without_browsing_returns_none() -> None:
    from clients.terminal.tui.widgets.input_box import InputBox

    box = InputBox()
    box.append_history("a")
    assert box.history_down() is None  # not browsing — nothing to do


def test_reset_history_clears_state() -> None:
    from clients.terminal.tui.widgets.input_box import InputBox

    box = InputBox()
    box.append_history("a")
    box.history_up("")  # enter browsing
    assert box.browsing
    box.reset_history()
    assert box._history == []
    assert not box.browsing
    assert box._draft == ""


@pytest.mark.anyio
async def test_arrow_up_on_empty_recalls_last_message() -> None:
    async with NaviCodeTui(new_session=True).run_test() as pilot:
        await pilot.pause()
        box = pilot.app.query_one("InputBox")
        box.append_history("previous message")
        # Field is empty + focused + no hints → Up recalls history.
        await pilot.press("up")
        await pilot.pause()
        assert box._input.text == "previous message"
        assert box.browsing


@pytest.mark.anyio
async def test_arrow_down_past_newest_restores_empty_draft() -> None:
    async with NaviCodeTui(new_session=True).run_test() as pilot:
        await pilot.pause()
        box = pilot.app.query_one("InputBox")
        box.append_history("msg")
        await pilot.press("up")  # fill "msg"
        await pilot.pause()
        assert box._input.text == "msg"
        await pilot.press("down")  # past newest → draft (empty)
        await pilot.pause()
        assert box._input.text == ""
        assert not box.browsing


@pytest.mark.anyio
async def test_arrow_up_on_nonempty_does_not_recall() -> None:
    async with NaviCodeTui(new_session=True).run_test() as pilot:
        await pilot.pause()
        box = pilot.app.query_one("InputBox")
        box.append_history("history item")
        await _set_text(pilot, "typing")  # non-empty field
        await pilot.press("up")  # → TextArea multiline cursor, NOT history recall
        await pilot.pause()
        assert box._input.text == "typing"
        assert not box.browsing


@pytest.mark.anyio
async def test_submit_records_plain_message_to_history() -> None:
    async with NaviCodeTui(new_session=True).run_test() as pilot:
        await pilot.pause()
        box = pilot.app.query_one("InputBox")
        await _set_text(pilot, "hello agent")
        await pilot.press("enter")
        await pilot.pause()
        assert box._history == ["hello agent"]


@pytest.mark.anyio
async def test_submit_slash_command_not_recorded() -> None:
    async with NaviCodeTui(new_session=True).run_test() as pilot:
        await pilot.pause()
        box = pilot.app.query_one("InputBox")
        await _set_text(pilot, "/help")
        await pilot.press("enter")
        await pilot.pause()
        assert box._history == []