"""Robot Framework helper for output rendering end-to-end tests."""

from __future__ import annotations

import json
import re
import sys
from pathlib import Path

import yaml

# Ensure the local source tree is importable
_SRC = str(Path(__file__).resolve().parents[1] / "src")
if _SRC not in sys.path:
    sys.path.insert(0, _SRC)

from cleveragents.cli.formatting import format_output  # noqa: E402
from cleveragents.cli.output.handles import DiffLine  # noqa: E402
from cleveragents.cli.output.materializers import (  # noqa: E402
    ColorMaterializer,
    JsonMaterializer,
    PlainMaterializer,
    RichMaterializer,
    TableMaterializer,
    YamlMaterializer,
)
from cleveragents.cli.output.selection import (  # noqa: E402
    TerminalCapabilities,
    select_materializer,
)
from cleveragents.cli.output.session import OutputSession  # noqa: E402

ANSI_RE = re.compile(r"\033\[")


def test_plain_panel() -> None:
    """Plain format renders panel without ANSI codes."""
    strategy = PlainMaterializer()
    with OutputSession(format="plain", command="test", strategy=strategy) as session:
        panel = session.panel("Project Info")
        panel.set_entry("Name", "my-project")
        panel.set_entry("Version", "1.0.0")
        panel.close()
    output = strategy.get_output()
    assert "Project Info" in output
    assert "Name: my-project" in output
    assert not ANSI_RE.search(output), "Plain output should not contain ANSI codes"
    print("output-rendering-plain-panel-ok")


def test_json_output() -> None:
    """JSON format produces valid JSON with elements."""
    strategy = JsonMaterializer()
    with OutputSession(format="json", command="test", strategy=strategy) as session:
        panel = session.panel("Details")
        panel.set_entry("Key", "Value")
        panel.close()
        table = session.table(columns=["Col1", "Col2"])
        table.add_row({"Col1": "a", "Col2": "b"})
        table.close()
    output = strategy.get_output()
    data = json.loads(output)
    assert "data" in data
    assert len(data["data"]) == 2
    assert "status" in data
    assert data["status"] == "ok"
    print("output-rendering-json-output-ok")


def test_yaml_output() -> None:
    """YAML format produces valid YAML with elements."""
    strategy = YamlMaterializer()
    with OutputSession(format="yaml", command="test", strategy=strategy) as session:
        panel = session.panel("YAML Details")
        panel.set_entry("Alpha", "Beta")
        panel.close()
    output = strategy.get_output()
    data = yaml.safe_load(output)
    assert "data" in data
    assert "status" in data
    assert data["status"] == "ok"
    print("output-rendering-yaml-output-ok")


def test_table_boxdraw() -> None:
    """Table format uses Unicode box-drawing characters (P2-5 upgrade)."""
    strategy = TableMaterializer()
    with OutputSession(format="table", command="test", strategy=strategy) as session:
        table = session.table(columns=["Name", "Status"])
        table.add_row({"Name": "svc", "Status": "up"})
        table.close()
    output = strategy.get_output()
    assert "│" in output or "╭" in output, (
        "Table output should contain Unicode box chars"
    )
    assert "svc" in output
    print("output-rendering-table-boxdraw-ok")


def test_color_ansi() -> None:
    """Color format includes ANSI escape codes."""
    strategy = ColorMaterializer()
    with OutputSession(format="color", command="test", strategy=strategy) as session:
        panel = session.panel("Coloured Title")
        panel.set_entry("Key", "val")
        panel.close()
    output = strategy.get_output()
    assert ANSI_RE.search(output), "Color output should contain ANSI codes"
    print("output-rendering-color-ansi-ok")


def test_json_error() -> None:
    """Error envelope in JSON mode has correct structure."""
    mat = JsonMaterializer()
    output = mat.get_error_output("NOT_FOUND", "Item not found", {"id": "123"})
    data = json.loads(output)
    assert "error" in data
    assert data["error"]["code"] == "NOT_FOUND"
    assert data["error"]["message"] == "Item not found"
    assert data["error"]["details"]["id"] == "123"
    print("output-rendering-json-error-ok")


def test_backward_compat() -> None:
    """format_output still works for all formats.

    For machine-readable formats (json, yaml, plain) ``format_output``
    writes directly to ``sys.stdout`` and returns ``""``.  We capture
    stdout so the assertion checks the *produced* output rather than
    just the return value.
    """
    import sys as _sys
    from io import StringIO as _SIO

    data = {"Name": "test-proj", "Status": "active"}
    for fmt in ("json", "yaml", "plain", "table", "rich"):
        buf = _SIO()
        old_stdout = _sys.stdout
        _sys.stdout = buf
        try:
            result = format_output(data, fmt)
        finally:
            _sys.stdout = old_stdout
        # Combine: either the return value has content, or stdout was written to
        effective = result or buf.getvalue().rstrip("\n")
        assert effective, f"format_output({fmt}) produced no output"
    print("output-rendering-backward-compat-ok")


def test_selection_fallback() -> None:
    """Materializer selection with fallback logic."""
    no_tty = TerminalCapabilities(
        is_tty=False, supports_ansi=False, supports_cursor=False, term=""
    )

    # Rich falls back to plain when no TTY
    mat = select_materializer("rich", capabilities=no_tty)
    assert isinstance(mat, PlainMaterializer)

    # JSON always works
    mat = select_materializer("json", capabilities=no_tty)
    assert isinstance(mat, JsonMaterializer)

    # Explicit overrides fallback
    mat = select_materializer("rich", capabilities=no_tty, explicit=True)
    assert isinstance(mat, RichMaterializer)

    print("output-rendering-selection-fallback-ok")


def test_tree_rendering() -> None:
    """Tree element renders across all formats."""
    for fmt, cls in [("plain", PlainMaterializer), ("json", JsonMaterializer)]:
        strategy = cls()
        with OutputSession(format=fmt, command="bench", strategy=strategy) as session:
            tree = session.tree("Root")
            tree.add_child("Root", "src")
            tree.add_child("Root", "tests")
            tree.add_child("Root/src", "main.py")
            tree.close()
        output = strategy.get_output()
        assert "Root" in output, f"Tree root not in {fmt} output"
        assert "src" in output, f"Tree child 'src' not in {fmt} output"
    print("output-rendering-tree-ok")


def test_code_rendering() -> None:
    """Code element renders with line numbers."""
    strategy = PlainMaterializer()
    with OutputSession(format="plain", command="bench", strategy=strategy) as session:
        code = session.code("x = 1\ny = 2", language="python", line_numbers=True)
        code.close()
    output = strategy.get_output()
    assert "1 |" in output, "Code should have line numbers"
    assert "x = 1" in output
    print("output-rendering-code-ok")


def test_diff_rendering() -> None:
    """Diff element renders unified diff format."""
    strategy = PlainMaterializer()
    with OutputSession(format="plain", command="bench", strategy=strategy) as session:
        diff = session.diff(file_a="old.py", file_b="new.py")
        diff.add_hunk(
            "@@ -1,3 +1,4 @@",
            [
                DiffLine(type="context", content="import os"),
                DiffLine(type="remove", content="x = 1"),
                DiffLine(type="add", content="x = 2"),
                DiffLine(type="add", content="y = 3"),
            ],
        )
        diff.set_stats(2, 1)
        diff.close()
    output = strategy.get_output()
    assert "--- old.py" in output
    assert "+++ new.py" in output
    assert "+x = 2" in output
    assert "-x = 1" in output
    assert "2 insertion(s)" in output
    print("output-rendering-diff-ok")


def test_text_separator_hint() -> None:
    """Text, separator, and action hint render correctly."""
    strategy = PlainMaterializer()
    with OutputSession(format="plain", command="bench", strategy=strategy) as session:
        text = session.text("Hello World", indent=2)
        text.close()
        session.separator("line")
        session.action_hint(["agents list", "agents deploy"], "Try these:")
    output = strategy.get_output()
    assert "Hello World" in output
    assert "----" in output
    assert "agents list" in output
    print("output-rendering-text-sep-hint-ok")


def test_json_all_elements() -> None:
    """JSON format serializes all 10 element types."""
    strategy = JsonMaterializer()
    with OutputSession(format="json", command="all", strategy=strategy) as session:
        # Panel
        p = session.panel("P")
        p.set_entry("k", "v")
        p.close()
        # Table
        t = session.table(columns=["A"])
        t.add_row({"A": "1"})
        t.close()
        # Status
        s = session.status("ok")
        s.close()
        # Progress
        pr = session.progress("go", total=10)
        pr.set_progress(5, 10)
        pr.close()
        # Tree
        tr = session.tree("Root")
        tr.add_child("Root", "leaf")
        tr.close()
        # Text
        tx = session.text("hello")
        tx.close()
        # Code
        co = session.code("x=1", language="py")
        co.close()
        # Diff
        d = session.diff(file_a="a", file_b="b")
        d.add_hunk("@@", [DiffLine(type="add", content="new")])
        d.close()
        # Separator
        session.separator("line")
        # ActionHint
        session.action_hint(["cmd"])
    output = strategy.get_output()
    data = json.loads(output)
    types = [e["type"] for e in data["data"]]
    expected = [
        "panel",
        "table",
        "status",
        "progress",
        "tree",
        "text",
        "code",
        "diff",
        "separator",
        "action_hint",
    ]
    assert types == expected, f"Expected {expected}, got {types}"
    print("output-rendering-json-all-ok")


def test_yaml_all_elements() -> None:
    """YAML format serializes all 10 element types (M5 fix — Luis review)."""
    strategy = YamlMaterializer()
    with OutputSession(format="yaml", command="all", strategy=strategy) as session:
        # Panel
        p = session.panel("P")
        p.set_entry("k", "v")
        p.close()
        # Table
        t = session.table(columns=["A"])
        t.add_row({"A": "1"})
        t.close()
        # Status
        s = session.status("ok")
        s.close()
        # Progress
        pr = session.progress("go", total=10)
        pr.set_progress(5, 10)
        pr.close()
        # Tree
        tr = session.tree("Root")
        tr.add_child("Root", "leaf")
        tr.close()
        # Text
        tx = session.text("hello")
        tx.close()
        # Code
        co = session.code("x=1", language="py")
        co.close()
        # Diff
        d = session.diff(file_a="a", file_b="b")
        d.add_hunk("@@", [DiffLine(type="add", content="new")])
        d.close()
        # Separator
        session.separator("line")
        # ActionHint
        session.action_hint(["cmd"])
    output = strategy.get_output()
    data = yaml.safe_load(output)
    types = [e["type"] for e in data["data"]]
    expected = [
        "panel",
        "table",
        "status",
        "progress",
        "tree",
        "text",
        "code",
        "diff",
        "separator",
        "action_hint",
    ]
    assert types == expected, f"Expected {expected}, got {types}"
    print("output-rendering-yaml-all-ok")


_DISPATCH = {
    "plain-panel": test_plain_panel,
    "json-output": test_json_output,
    "yaml-output": test_yaml_output,
    "table-boxdraw": test_table_boxdraw,
    "color-ansi": test_color_ansi,
    "json-error": test_json_error,
    "backward-compat": test_backward_compat,
    "selection-fallback": test_selection_fallback,
    "tree": test_tree_rendering,
    "code": test_code_rendering,
    "diff": test_diff_rendering,
    "text-sep-hint": test_text_separator_hint,
    "json-all": test_json_all_elements,
    "yaml-all": test_yaml_all_elements,
}


if __name__ == "__main__":
    if len(sys.argv) < 2:
        print(f"Usage: {sys.argv[0]} <test-name>", file=sys.stderr)
        sys.exit(1)
    test_name = sys.argv[1]
    if test_name not in _DISPATCH:
        print(f"Unknown test: {test_name}", file=sys.stderr)
        sys.exit(1)
    _DISPATCH[test_name]()
