"""Tests for the todo tool renderers (compact, op-aware cards)."""

from __future__ import annotations

from rich.console import Console

from clients.terminal.tui.renderers.todo import TodoResultRenderer, TodoStartedRenderer
from clients.terminal.tui.renderers.tool import ToolStartedRenderer, ToolResultRenderer
from clients.terminal.tui.themes import set_active_theme


def _render_text(renderable) -> str:
    console = Console(record=True, width=80, force_terminal=True, color_system=None)
    console.print(renderable)
    return console.export_text()


# ── accepts() gating ────────────────────────────────────────────────────────


def test_started_accepts_only_todo_started() -> None:
    started = TodoStartedRenderer()
    assert started.accepts({"type": "tool_started", "tool": "todo", "args": {"op": "view"}})
    assert not started.accepts({"type": "tool_started", "tool": "filesystem", "args": {}})
    assert not started.accepts({"type": "tool_call", "tool": "todo"})


def test_result_accepts_only_todo_call() -> None:
    result = TodoResultRenderer()
    assert result.accepts({"type": "tool_call", "tool": "todo", "result": "Plan — 1/2 done:", "success": True})
    assert not result.accepts({"type": "tool_call", "tool": "filesystem", "result": "ok"})
    assert not result.accepts({"type": "tool_started", "tool": "todo"})


def test_todo_renderers_win_over_generic_tool() -> None:
    """The registry checks todo renderers before the generic tool ones, so a
    todo call never falls through to the JSON-dump ToolStartedRenderer."""
    from clients.terminal.tui.renderers import default_registry

    reg = default_registry()
    msg = {"type": "tool_started", "tool": "todo", "args": {"op": "set", "tasks": ["a"]}}
    rendered = reg.render(msg)
    title = str(getattr(rendered, "title", ""))
    assert "set plan" in title
    # And the generic renderer does NOT accept a todo call (it would, but the
    # todo renderer is checked first — sanity-check the generic still accepts
    # it in isolation to confirm ordering is what protects us, not accepts()).
    assert ToolStartedRenderer().accepts(msg)


# ── started cards by op ──────────────────────────────────────────────────────


def test_set_card_lists_all_tasks_numbered_with_pending_icon() -> None:
    set_active_theme("gnexus-dark")
    renderer = TodoStartedRenderer()
    msg = {
        "type": "tool_started",
        "tool": "todo",
        "args": {"op": "set", "tasks": ["read the spec", "write the parser", "run tests"]},
    }
    panel = renderer.render(msg)
    assert "set plan (3)" in str(panel.title)
    out = _render_text(panel)
    assert "○" in out  # pending marker
    assert "1. read the spec" in out
    assert "2. write the parser" in out
    assert "3. run tests" in out
    # No raw JSON quoted-array dump.
    assert '"tasks"' not in out


def test_set_card_empty_plan() -> None:
    set_active_theme("gnexus-dark")
    panel = TodoStartedRenderer().render({"type": "tool_started", "tool": "todo", "args": {"op": "set", "tasks": []}})
    assert "(empty plan)" in _render_text(panel)


def test_update_card_shows_step_text_and_validation() -> None:
    """The call card for an update shows the text of the step being changed
    (from ToolStarted.metadata, enriched on the backend) plus the validation."""
    set_active_theme("gnexus-dark")
    msg = {
        "type": "tool_started",
        "tool": "todo",
        "args": {"op": "update", "index": 3, "status": "done", "validation": "ran pytest — 12 passed"},
        "metadata": {"step_text": "run the tests"},
    }
    panel = TodoStartedRenderer().render(msg)
    assert "#3 → done" in str(panel.title)
    out = _render_text(panel)
    assert "run the tests" in out
    assert "ran pytest — 12 passed" in out
    assert "verified:" in out


def test_update_card_step_text_without_validation() -> None:
    """An in_progress update has no validation — the step text alone is shown,
    without a noisy 'no validation' placeholder."""
    set_active_theme("gnexus-dark")
    msg = {
        "type": "tool_started",
        "tool": "todo",
        "args": {"op": "update", "index": 1, "status": "in_progress"},
        "metadata": {"step_text": "write the parser"},
    }
    panel = TodoStartedRenderer().render(msg)
    assert "#1 → in_progress" in str(panel.title)
    out = _render_text(panel)
    assert "write the parser" in out


def test_update_card_non_string_validation_does_not_crash() -> None:
    """``validation`` arrives as arbitrary JSON; a non-string value (dict/list)
    must not raise AttributeError on .strip(). Regression for robustness."""
    set_active_theme("gnexus-dark")
    msg = {
        "type": "tool_started",
        "tool": "todo",
        "args": {"op": "update", "index": 1, "status": "done", "validation": {"note": "ok"}},
    }
    panel = TodoStartedRenderer().render(msg)  # must not raise
    out = _render_text(panel)
    assert "verified" in out
    assert "no validation" not in out


def test_update_card_history_no_metadata_falls_back() -> None:
    """History replay has no metadata (step text wasn't persisted) and may
    have no validation — keep the placeholder so the card isn't empty."""
    set_active_theme("gnexus-dark")
    msg = {"type": "tool_started", "tool": "todo", "args": {"op": "update", "index": 1, "status": "in_progress"}}
    panel = TodoStartedRenderer().render(msg)
    assert "#1 → in_progress" in str(panel.title)
    assert "no validation" in _render_text(panel)


def test_update_card_validation_without_step_text() -> None:
    """No metadata but validation present — still show the validation line."""
    set_active_theme("gnexus-dark")
    msg = {
        "type": "tool_started",
        "tool": "todo",
        "args": {"op": "update", "index": 2, "status": "done", "validation": "checked output"},
    }
    out = _render_text(TodoStartedRenderer().render(msg))
    assert "verified: checked output" in out


def test_view_and_clear_cards_are_minimal() -> None:
    set_active_theme("gnexus-dark")
    view = TodoStartedRenderer().render({"type": "tool_started", "tool": "todo", "args": {"op": "view"}})
    assert "view" in str(view.title)
    clear = TodoStartedRenderer().render({"type": "tool_started", "tool": "todo", "args": {"op": "clear"}})
    assert "clear" in str(clear.title)


def test_unknown_op_falls_back_to_readable_json() -> None:
    set_active_theme("gnexus-dark")
    msg = {"type": "tool_started", "tool": "todo", "args": {"op": "nope", "foo": 1}}
    out = _render_text(TodoStartedRenderer().render(msg))
    # Legible key/value, not a Python dict repr.
    assert '"foo"' in out and "nope" in out


def test_subagent_todo_started_is_indented() -> None:
    set_active_theme("gnexus-dark")
    panel = TodoStartedRenderer().render(
        {"type": "tool_started", "tool": "todo", "args": {"op": "view"}, "is_subagent": True}
    )
    assert any(line.startswith("  ") for line in _render_text(panel).splitlines())


# ── result cards ────────────────────────────────────────────────────────────


def test_result_card_compact_status_line_from_plan_render() -> None:
    set_active_theme("gnexus-dark")
    result_text = "Plan — 2/5 done:\n  ✓ 1. read spec\n  ○ 2. write code\n"
    panel = TodoResultRenderer().render(
        {"type": "tool_call", "tool": "todo", "result": result_text, "success": True}
    )
    assert "✓" in str(panel.title)
    out = _render_text(panel)
    assert "plan · 2/5 done" in out
    # The full per-step dump is NOT in the chat card (it lives in the side panel).
    assert "read spec" not in out
    assert "write code" not in out


def test_result_card_empty_plan_label() -> None:
    set_active_theme("gnexus-dark")
    panel = TodoResultRenderer().render(
        {"type": "tool_call", "tool": "todo", "result": "Plan is empty.", "success": True}
    )
    assert "plan · empty" in _render_text(panel)


def test_result_card_cleared_label() -> None:
    set_active_theme("gnexus-dark")
    panel = TodoResultRenderer().render(
        {"type": "tool_call", "tool": "todo", "result": "Plan cleared.", "success": True}
    )
    assert "plan · cleared" in _render_text(panel)


def test_result_card_no_plan_label() -> None:
    set_active_theme("gnexus-dark")
    panel = TodoResultRenderer().render(
        {"type": "tool_call", "tool": "todo", "result": "No plan set for this session.", "success": True}
    )
    assert "plan · no plan" in _render_text(panel)


def test_result_card_failed_shows_first_line_compactly() -> None:
    set_active_theme("gnexus-dark")
    result_text = (
        "Cannot mark step 3 as done without validation.\n"
        "Provide a 'validation' field describing how you verified the result.\n"
    )
    panel = TodoResultRenderer().render(
        {"type": "tool_call", "tool": "todo", "result": result_text, "success": False}
    )
    title = str(panel.title)
    assert "✗" in title
    out = _render_text(panel)
    assert "Cannot mark step 3 as done without validation" in out
    # Only the first line, not the whole multi-line guidance.
    assert "Provide a 'validation'" not in out


def test_subagent_todo_result_is_indented() -> None:
    set_active_theme("gnexus-dark")
    panel = TodoResultRenderer().render(
        {"type": "tool_call", "tool": "todo", "result": "Plan — 1/1 done:", "success": True, "is_subagent": True}
    )
    assert any(line.startswith("  ") for line in _render_text(panel).splitlines())


def test_generic_tool_result_renderer_does_not_handle_todo_first():
    """Sanity: the generic ToolResultRenderer would accept a todo call, so the
    registry ordering (todo before tool) is what gives us the compact card."""
    assert ToolResultRenderer().accepts({"type": "tool_call", "tool": "todo", "result": "x", "success": True})