"""Tests for WidgetTreeSerializer — DOM to text tree (M5)."""

import pytest

from textual_mcp.serializers.widget_tree import WidgetTreeSerializer


# ---------------------------------------------------------------------------
# Test 5.1: serialize(app) returns string with Screen as root
# ---------------------------------------------------------------------------
@pytest.mark.asyncio
async def test_serialize_has_screen_root():
    """serialize() should return a string whose first line contains 'Screen'."""
    from tests.apps.simple_app import SimpleApp

    serializer = WidgetTreeSerializer()

    async with SimpleApp().run_test() as pilot:
        result = serializer.serialize(pilot.app)

    assert isinstance(result, str)
    lines = result.strip().splitlines()
    assert len(lines) > 0
    assert "Screen" in lines[0]


# ---------------------------------------------------------------------------
# Test 5.2: interactive widgets (can_focus=True) get [ref=N] prefix
# ---------------------------------------------------------------------------
@pytest.mark.asyncio
async def test_interactive_widgets_get_ref():
    """Interactive widgets (can_focus=True) should have [ref=N] prefix."""
    from tests.apps.simple_app import SimpleApp

    serializer = WidgetTreeSerializer()

    async with SimpleApp().run_test() as pilot:
        result = serializer.serialize(pilot.app)

    # Input and Button are interactive (can_focus=True)
    lines = result.strip().splitlines()
    input_lines = [l for l in lines if "Input" in l]
    button_lines = [l for l in lines if "Button" in l]
    assert any("[ref=" in l for l in input_lines), f"Input should have ref: {input_lines}"
    assert any("[ref=" in l for l in button_lines), f"Button should have ref: {button_lines}"


# ---------------------------------------------------------------------------
# Test 5.3: non-interactive containers have no ref
# ---------------------------------------------------------------------------
@pytest.mark.asyncio
async def test_non_interactive_no_ref():
    """Non-interactive containers (can_focus=False) should not have [ref=N]."""
    from tests.apps.simple_app import SimpleApp

    serializer = WidgetTreeSerializer()

    async with SimpleApp().run_test() as pilot:
        result = serializer.serialize(pilot.app)

    lines = result.strip().splitlines()
    # Screen is a container, should not have ref
    screen_line = lines[0]
    assert "[ref=" not in screen_line

    # Static with can_focus=False should not have ref
    static_lines = [l for l in lines if "Static" in l]
    for l in static_lines:
        assert "[ref=" not in l, f"Static should not have ref: {l}"


# ---------------------------------------------------------------------------
# Test 5.4: refs are sequential integers starting at 1
# ---------------------------------------------------------------------------
@pytest.mark.asyncio
async def test_refs_sequential_from_1():
    """Refs should be sequential integers starting at 1."""
    from tests.apps.simple_app import SimpleApp

    serializer = WidgetTreeSerializer()

    async with SimpleApp().run_test() as pilot:
        result = serializer.serialize(pilot.app)

    import re

    refs = [int(m) for m in re.findall(r"\[ref=(\d+)\]", result)]
    assert len(refs) >= 2  # At least Input and Button
    assert refs == list(range(1, len(refs) + 1))


# ---------------------------------------------------------------------------
# Test 5.5: widget IDs appear as #id
# ---------------------------------------------------------------------------
@pytest.mark.asyncio
async def test_widget_ids_appear():
    """Widget IDs should appear as #id in the output."""
    from tests.apps.simple_app import SimpleApp

    serializer = WidgetTreeSerializer()

    async with SimpleApp().run_test() as pilot:
        result = serializer.serialize(pilot.app)

    assert "#greeting" in result
    assert "#text-input" in result
    assert "#btn" in result


# ---------------------------------------------------------------------------
# Test 5.6: CSS classes appear as .class
# ---------------------------------------------------------------------------
@pytest.mark.asyncio
async def test_css_classes_appear():
    """CSS classes should appear as .class in the output."""
    from textual.app import App, ComposeResult
    from textual.widgets import Static

    class ClassApp(App):
        def compose(self) -> ComposeResult:
            yield Static("test", classes="highlight bold")

    serializer = WidgetTreeSerializer()

    async with ClassApp().run_test() as pilot:
        result = serializer.serialize(pilot.app)

    assert ".highlight" in result
    assert ".bold" in result


# ---------------------------------------------------------------------------
# Test 5.7: focused widget shows FOCUSED flag
# ---------------------------------------------------------------------------
@pytest.mark.asyncio
async def test_focused_widget_shows_flag():
    """The focused widget should show 'FOCUSED' flag."""
    from tests.apps.simple_app import SimpleApp

    serializer = WidgetTreeSerializer()

    async with SimpleApp().run_test() as pilot:
        # Tab to focus the Input
        await pilot.press("tab")
        await pilot.pause()
        result = serializer.serialize(pilot.app)

    # The focused widget should have FOCUSED
    lines = result.strip().splitlines()
    focused_lines = [l for l in lines if "FOCUSED" in l]
    assert len(focused_lines) == 1, f"Expected exactly one focused widget, got: {focused_lines}"


# ---------------------------------------------------------------------------
# Test 5.8: disabled widget shows "disabled" flag
# ---------------------------------------------------------------------------
@pytest.mark.asyncio
async def test_disabled_widget_shows_flag():
    """A disabled widget should show 'disabled' flag."""
    from textual.app import App, ComposeResult
    from textual.widgets import Button

    class DisabledApp(App):
        def compose(self) -> ComposeResult:
            yield Button("Disabled", id="dis-btn", disabled=True)

    serializer = WidgetTreeSerializer()

    async with DisabledApp().run_test() as pilot:
        result = serializer.serialize(pilot.app)

    lines = result.strip().splitlines()
    btn_lines = [l for l in lines if "Button" in l]
    assert any("disabled" in l for l in btn_lines), f"Expected disabled flag: {btn_lines}"


# ---------------------------------------------------------------------------
# Test 5.9: hidden widgets (display=False) are omitted
# ---------------------------------------------------------------------------
@pytest.mark.asyncio
async def test_hidden_widgets_omitted():
    """Widgets with display=False should be omitted from the tree."""
    from textual.app import App, ComposeResult
    from textual.widgets import Static, Button

    class HiddenApp(App):
        def compose(self) -> ComposeResult:
            yield Static("visible", id="vis")
            yield Static("hidden", id="hid")
            yield Button("OK")

        def on_mount(self) -> None:
            self.query_one("#hid").display = False

    serializer = WidgetTreeSerializer()

    async with HiddenApp().run_test() as pilot:
        result = serializer.serialize(pilot.app)

    assert "#vis" in result
    assert "#hid" not in result


# ---------------------------------------------------------------------------
# Test 5.10: indentation reflects nesting depth (2 spaces per level)
# ---------------------------------------------------------------------------
@pytest.mark.asyncio
async def test_indentation_nesting():
    """Indentation should reflect nesting depth (2 spaces per level)."""
    from textual.app import App, ComposeResult
    from textual.containers import Vertical
    from textual.widgets import Static

    class NestedApp(App):
        def compose(self) -> ComposeResult:
            with Vertical():
                yield Static("nested")

    serializer = WidgetTreeSerializer()

    async with NestedApp().run_test() as pilot:
        result = serializer.serialize(pilot.app)

    lines = result.strip().splitlines()
    # Screen at depth 0 (no indent)
    assert not lines[0].startswith(" ")
    # Find a nested widget — should have indentation
    nested_lines = [l for l in lines if "Static" in l]
    assert len(nested_lines) >= 1
    # Static is inside Vertical inside Screen, so at least 4 spaces (depth 2)
    for l in nested_lines:
        indent = len(l) - len(l.lstrip())
        assert indent >= 4, f"Expected indent >= 4 for nested widget, got {indent}: {l}"


# ---------------------------------------------------------------------------
# Test 5.11: type-specific properties appear (e.g. Input value=)
# ---------------------------------------------------------------------------
@pytest.mark.asyncio
async def test_type_specific_properties():
    """Type-specific properties from WidgetStateExtractor should appear."""
    from tests.apps.simple_app import SimpleApp

    serializer = WidgetTreeSerializer()

    async with SimpleApp().run_test() as pilot:
        result = serializer.serialize(pilot.app)

    # Input should have value= and placeholder=
    lines = result.strip().splitlines()
    input_lines = [l for l in lines if "Input" in l]
    assert any("placeholder=" in l for l in input_lines), f"Expected placeholder: {input_lines}"

    # Button should have label=
    button_lines = [l for l in lines if "Button" in l]
    assert any("label=" in l for l in button_lines), f"Expected label: {button_lines}"


# ---------------------------------------------------------------------------
# Test 5.12: resolve_ref(N) returns the correct Widget instance
# ---------------------------------------------------------------------------
@pytest.mark.asyncio
async def test_resolve_ref_valid():
    """resolve_ref(N) should return the correct Widget instance."""
    from tests.apps.simple_app import SimpleApp

    serializer = WidgetTreeSerializer()

    async with SimpleApp().run_test() as pilot:
        serializer.serialize(pilot.app)
        # Ref 1 should exist and be a widget
        widget = serializer.resolve_ref(1)
        assert widget is not None
        assert hasattr(widget, "id")


# ---------------------------------------------------------------------------
# Test 5.13: resolve_ref(invalid) returns None
# ---------------------------------------------------------------------------
def test_resolve_ref_invalid():
    """resolve_ref() with an invalid ref should return None."""
    serializer = WidgetTreeSerializer()
    assert serializer.resolve_ref(999) is None


# ---------------------------------------------------------------------------
# Test 5.14: max_depth parameter limits tree depth
# ---------------------------------------------------------------------------
@pytest.mark.asyncio
async def test_max_depth_limits_tree():
    """max_depth parameter should limit how deep the tree is serialized."""
    from textual.app import App, ComposeResult
    from textual.containers import Vertical
    from textual.widgets import Static, Button

    class DeepApp(App):
        def compose(self) -> ComposeResult:
            with Vertical():
                yield Static("deep")
                yield Button("deep btn")

    serializer = WidgetTreeSerializer()

    async with DeepApp().run_test() as pilot:
        full = serializer.serialize(pilot.app)
        shallow = serializer.serialize(pilot.app, max_depth=0)

    full_lines = full.strip().splitlines()
    shallow_lines = shallow.strip().splitlines()
    # max_depth=0 should only show Screen (root)
    assert len(shallow_lines) == 1
    assert "Screen" in shallow_lines[0]
    assert len(full_lines) > len(shallow_lines)


# ---------------------------------------------------------------------------
# Test 5.15: Scrollbar widgets are excluded from output
# ---------------------------------------------------------------------------
@pytest.mark.asyncio
async def test_scrollbar_excluded():
    """Scrollbar widgets should be excluded from the tree output."""
    from tests.apps.simple_app import SimpleApp

    serializer = WidgetTreeSerializer()

    async with SimpleApp().run_test() as pilot:
        result = serializer.serialize(pilot.app)

    assert "ScrollBar" not in result


# ---------------------------------------------------------------------------
# Test 5.16: serialization of SimpleApp produces expected structure
# ---------------------------------------------------------------------------
@pytest.mark.asyncio
async def test_simple_app_structure():
    """SimpleApp serialization should contain Screen, Static, Input, Button."""
    from tests.apps.simple_app import SimpleApp

    serializer = WidgetTreeSerializer()

    async with SimpleApp().run_test() as pilot:
        result = serializer.serialize(pilot.app)

    assert "Screen" in result
    assert "Static" in result
    assert "Input" in result
    assert "Button" in result


# ---------------------------------------------------------------------------
# Test 5.17: re-serialization regenerates refs (no stale refs)
# ---------------------------------------------------------------------------
@pytest.mark.asyncio
async def test_re_serialization_regenerates_refs():
    """Calling serialize() again should regenerate refs from scratch."""
    from tests.apps.simple_app import SimpleApp

    serializer = WidgetTreeSerializer()

    async with SimpleApp().run_test() as pilot:
        result1 = serializer.serialize(pilot.app)
        result2 = serializer.serialize(pilot.app)

    # Both should produce the same output
    assert result1 == result2
    # Ref 1 should still resolve correctly
    widget = serializer.resolve_ref(1)
    assert widget is not None
