"""Tests for the TodoList side-panel widget."""

from __future__ import annotations

from pathlib import Path

import pytest
from rich.text import Text

from clients.terminal.tui.widgets.todo_list import TodoList


def _plain(content: str) -> str:
    """Strip Rich markup so assertions see the visible text, not colour tags."""
    return Text.from_markup(content).plain


@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.mark.anyio
async def test_todo_list_empty_state() -> None:
    """A fresh TodoList shows the 'No plan yet' placeholder."""
    from clients.terminal.tui.tui_app import NaviCodeTui

    async with NaviCodeTui(new_session=True).run_test() as pilot:
        await pilot.pause()
        todo = pilot.app.query_one(TodoList)
        # _build_content is the source of truth (never name this _render —
        # it shadows Widget._render and crashes Textual's layout pass).
        assert "No plan yet" in todo._build_content()


@pytest.mark.anyio
async def test_todo_list_renders_tasks_with_header() -> None:
    from clients.terminal.tui.tui_app import NaviCodeTui

    tasks = [
        {"index": 1, "text": "step one", "status": "done", "validation": "ran tests"},
        {"index": 2, "text": "step two", "status": "in_progress", "validation": ""},
        {"index": 3, "text": "step three", "status": "failed", "validation": "boom"},
    ]
    async with NaviCodeTui(new_session=True).run_test() as pilot:
        await pilot.pause()
        todo = pilot.app.query_one(TodoList)
        todo.set_tasks(tasks)
        plain = _plain(todo._build_content())
        assert "1/3 done" in plain
        assert "step one" in plain
        assert "step two" in plain
        assert "(in_progress)" in plain
        assert "(failed)" in plain
        assert "✓ ran tests" in plain  # validation note on the done step
        assert "✓ boom" in plain       # validation note on the failed step


@pytest.mark.anyio
async def test_todo_list_clear_resets_to_placeholder() -> None:
    from clients.terminal.tui.tui_app import NaviCodeTui

    async with NaviCodeTui(new_session=True).run_test() as pilot:
        await pilot.pause()
        todo = pilot.app.query_one(TodoList)
        todo.set_tasks([{"index": 1, "text": "x", "status": "pending", "validation": ""}])
        assert "x" in todo._build_content()
        todo.clear()
        assert "No plan yet" in todo._build_content()


def test_build_content_never_named_render():
    """Guard against the Textual _render shadow pitfall: a method called _render
    on a Widget subclass crashes the layout pass (AttributeError get_height)."""
    assert "_render" not in TodoList.__dict__, (
        "TodoList must not override _render — it shadows Widget._render "
        "(Textual calls it expecting a Strip, gets a str → AttributeError)."
    )
    assert "_build_content" in TodoList.__dict__


@pytest.mark.anyio
async def test_status_markers_are_coloured_and_emphasised() -> None:
    """Each status gets its own marker colour, and the active step is bolded
    while completed/skipped steps are dimmed — so progress is legible at a glance."""
    from clients.terminal.tui.tui_app import NaviCodeTui
    from clients.terminal.tui.themes import get_active_theme

    tasks = [
        {"index": 1, "text": "done step", "status": "done", "validation": ""},
        {"index": 2, "text": "active step", "status": "in_progress", "validation": ""},
        {"index": 3, "text": "failed step", "status": "failed", "validation": ""},
        {"index": 4, "text": "queued step", "status": "pending", "validation": ""},
    ]
    theme = get_active_theme()
    async with NaviCodeTui(new_session=True).run_test() as pilot:
        await pilot.pause()
        todo = pilot.app.query_one(TodoList)
        todo.set_tasks(tasks)
        content = todo._build_content()
        # Marker icons are wrapped in their status colour.
        assert f"[{theme.success.hex}]✓" in content   # done
        assert f"[{theme.accent.hex}]◎" in content    # in_progress
        assert f"[{theme.error.hex}]✗" in content     # failed
        assert f"[{theme.text_dim.hex}]○" in content  # pending
        # The active step is bold; completed steps are dimmed.
        assert "bold" in content
        assert "dim" in content
        # Plain text still carries every step.
        plain = _plain(content)
        assert "active step" in plain
        assert "queued step" in plain


@pytest.mark.anyio
async def test_todo_list_groups_by_status_in_progress_first_done_last() -> None:
    """Steps are grouped for display: in_progress on top, then pending, then
    failed/skipped, then done at the bottom — regardless of the payload order.
    Original order is preserved within each group; the header still counts all
    done steps."""
    from clients.terminal.tui.tui_app import NaviCodeTui

    # Payload in plan order: done, pending, in_progress, done, failed, skipped.
    tasks = [
        {"index": 1, "text": "done one", "status": "done", "validation": ""},
        {"index": 2, "text": "queued one", "status": "pending", "validation": ""},
        {"index": 3, "text": "active one", "status": "in_progress", "validation": ""},
        {"index": 4, "text": "done two", "status": "done", "validation": ""},
        {"index": 5, "text": "boom", "status": "failed", "validation": ""},
        {"index": 6, "text": "skip", "status": "skipped", "validation": ""},
    ]
    async with NaviCodeTui(new_session=True).run_test() as pilot:
        await pilot.pause()
        todo = pilot.app.query_one(TodoList)
        todo.set_tasks(tasks)
        plain = _plain(todo._build_content())
        # Header counts both done steps.
        assert "2/6 done" in plain
        # The visible order is the display order, not the payload order.
        assert plain.index("active one") < plain.index("queued one")
        assert plain.index("queued one") < plain.index("boom")
        assert plain.index("boom") < plain.index("done one")
        assert plain.index("skip") < plain.index("done one")
        assert plain.index("done one") < plain.index("done two")
        # Original order preserved within the done group (done one before done two).
        assert plain.index("done one") < plain.index("done two")


@pytest.mark.anyio
async def test_todo_panel_is_scrollable_and_wraps_list() -> None:
    """The todo lives in its own scrollable panel below the info block, not
    inside StatusPanel."""
    from textual.containers import ScrollableContainer

    from clients.terminal.tui.tui_app import NaviCodeTui
    from clients.terminal.tui.widgets.todo_list import TodoPanel

    async with NaviCodeTui(new_session=True).run_test() as pilot:
        await pilot.pause()
        panel = pilot.app.query_one(TodoPanel)
        assert isinstance(panel, ScrollableContainer)
        # The TodoList is a child of the panel.
        assert panel.query_one(TodoList) is not None
        # The panel fills the remaining column height; the info block shrinks.
        assert str(panel.styles.height) == "1fr"