"""Tests for the code_exec tool-call renderer (Python highlight + structured result)."""

from __future__ import annotations

from rich.console import Console

from clients.terminal.tui.renderers.code_exec import (
    CodeExecResultRenderer,
    CodeExecStartedRenderer,
    _split_streams,
)
from clients.terminal.tui.themes import get_active_theme, 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()


def _started(code: str, **kw) -> dict:
    msg = {"type": "tool_started", "tool": "code_exec", "args": {"code": code}}
    msg.update(kw)
    return msg


def _result(result: str, success: bool, metadata: dict | None = None, **kw) -> dict:
    msg = {
        "type": "tool_call",
        "tool": "code_exec",
        "args": {"code": "print('x')"},
        "result": result,
        "success": success,
        "metadata": metadata or {},
    }
    msg.update(kw)
    return msg


# ── accepts ────────────────────────────────────────────────────────────────────


def test_started_accepts_only_code_exec_tool_started() -> None:
    r = CodeExecStartedRenderer()
    assert r.accepts(_started("print('x')"))
    assert not r.accepts({"type": "tool_started", "tool": "terminal", "args": {}})
    assert not r.accepts({"type": "tool_call", "tool": "code_exec", "args": {}})


def test_result_accepts_only_code_exec_tool_call() -> None:
    r = CodeExecResultRenderer()
    assert r.accepts(_result("x", True))
    assert not r.accepts({"type": "tool_call", "tool": "terminal", "args": {}})
    assert not r.accepts({"type": "tool_started", "tool": "code_exec", "args": {}})


# ── started ───────────────────────────────────────────────────────────────────


def test_started_shows_code_not_json() -> None:
    set_active_theme("gnexus-dark")
    panel = CodeExecStartedRenderer().render(_started("print('hello world')"))
    text = _render_text(panel)
    # The code is rendered as code, not as a JSON dump (no surrounding quotes /
    # "code": key). The literal source must be visible.
    assert "print('hello world')" in text
    assert '"code"' not in text


def test_started_folds_long_scripts() -> None:
    set_active_theme("gnexus-dark")
    long_code = "\n".join(f"x{i} = {i}" for i in range(120))
    panel = CodeExecStartedRenderer().render(_started(long_code))
    text = _render_text(panel)
    assert "x0 = 0" in text
    assert "more lines" in text
    # The tail of a 120-line script (>=60 lines) must be folded out.
    assert "x119 = 119" not in text


def test_started_shows_working_dir_and_timeout() -> None:
    set_active_theme("gnexus-dark")
    panel = CodeExecStartedRenderer().render(
        _started("print('x')", args={"code": "print('x')", "working_dir": "/proj", "timeout": 120})
    )
    text = _render_text(panel)
    assert "/proj" in text
    assert "120s" in text


# ── result ────────────────────────────────────────────────────────────────────


def test_result_success_anchors_exit_zero() -> None:
    set_active_theme("gnexus-dark")
    panel = CodeExecResultRenderer().render(
        _result("hello", True, metadata={"returncode": 0, "language": "python"})
    )
    assert "exit 0" in str(panel.title)
    assert "✓" in str(panel.title)


def test_result_failure_anchors_exit_code() -> None:
    set_active_theme("gnexus-dark")
    panel = CodeExecResultRenderer().render(
        _result("boom", False, metadata={"returncode": 1, "language": "python"})
    )
    assert "exit 1" in str(panel.title)
    assert "✗" in str(panel.title)


def test_result_splits_stdout_and_stderr() -> None:
    set_active_theme("gnexus-dark")
    fused = "ok line\n[stderr]\ntraceback boom"
    panel = CodeExecResultRenderer().render(
        _result(fused, False, metadata={"returncode": 1, "language": "python"})
    )
    text = _render_text(panel)
    assert "ok line" in text
    assert "traceback boom" in text
    # The fused marker must not leak into the rendered card.
    assert "[stderr]" not in text


def test_result_timeout_status() -> None:
    set_active_theme("gnexus-dark")
    panel = CodeExecResultRenderer().render(
        _result("Code execution timed out after 30s", False, metadata={"timeout": 30})
    )
    assert "timeout 30s" in str(panel.title)
    assert "⏱" in str(panel.title)


def test_result_timeout_detected_from_output_for_legacy_sessions() -> None:
    set_active_theme("gnexus-dark")
    # Pre-metadata session: no "timeout" key, detect from the output text.
    panel = CodeExecResultRenderer().render(
        _result("Code execution timed out after 30s", False, metadata={})
    )
    assert "timeout" in str(panel.title)


def test_result_no_output_message() -> None:
    set_active_theme("gnexus-dark")
    panel = CodeExecResultRenderer().render(
        _result("", True, metadata={"returncode": 0, "language": "python"})
    )
    text = _render_text(panel)
    assert "(no output)" in text


# ── stream splitting helper ────────────────────────────────────────────────────


def test_split_streams_stdout_only() -> None:
    assert _split_streams("hello\nworld") == ("hello\nworld", "")


def test_split_streams_stderr_only() -> None:
    assert _split_streams("[stderr]\nboom") == ("", "boom")


def test_split_streams_both() -> None:
    assert _split_streams("out\n[stderr]\nerr") == ("out", "err")


def test_split_streams_empty() -> None:
    assert _split_streams("") == ("", "")


def test_split_streams_marker_in_stdout_content_is_not_treated_as_boundary() -> None:
    # A literal "[stderr]" line inside real stdout still splits (the server uses
    # the same marker), but a substring within a longer line does not.
    assert _split_streams("see [stderr] inline\n[stderr]\nreal err") == (
        "see [stderr] inline",
        "real err",
    )