"""Step definitions for tui_app_coverage.feature.

These steps target uncovered lines in cleveragents/tui/app.py:
- Lines 31-38: Textual import success path (mocked)
- Lines 81-100: _TextualCleverAgentsTuiApp class definition + __init__
- Lines 102-112: compose method
- Lines 114-121: on_mount method
- Lines 123-125: action_help method
- Lines 127-129: action_cycle_preset method
- Lines 131-142: _refresh_persona_bar method
- Lines 144-185: on_input_submitted (all branches)
- Line 189: CleverAgentsTuiApp alias
"""

import importlib
import os
import shutil
import sys
import tempfile
from pathlib import Path
from types import ModuleType, SimpleNamespace
from unittest.mock import MagicMock, patch

from behave import given, then, when

# ---------------------------------------------------------------------------
# Mock Textual infrastructure
# ---------------------------------------------------------------------------

_MOCK_TEXTUAL_KEYS = [
    "textual",
    "textual.app",
    "textual.containers",
    "textual.widgets",
]


def _build_mock_textual():
    """Build mock textual modules that satisfy the app import gate."""
    mock_textual = ModuleType("textual")
    mock_textual_app = ModuleType("textual.app")
    mock_textual_containers = ModuleType("textual.containers")
    mock_textual_widgets = ModuleType("textual.widgets")

    class MockApp:
        """Minimal App stand-in for the Textual base class."""

        def __init__(self, *args, **kwargs):
            self._widgets = {}

        def query_one(self, selector, widget_type=None):
            if selector in self._widgets:
                return self._widgets[selector]
            if widget_type is not None:
                widget = widget_type(id=selector.lstrip("#"))
                self._widgets[selector] = widget
                return widget
            return MagicMock()

    class MockVertical:
        def __init__(self, *args, **kwargs):
            pass

        def __enter__(self):
            return self

        def __exit__(self, *args):
            pass

    class MockHeader:
        def __init__(self, *args, **kwargs):
            pass

    class MockFooter:
        def __init__(self, *args, **kwargs):
            pass

    class MockStatic:
        def __init__(self, *args, **kwargs):
            self._text = ""

        def update(self, text):
            self._text = text

    class MockInput:
        """Minimal Input stand-in for the Textual base class."""

        value = ""

        def __init__(self, *args, **kwargs):
            self.value = ""

    mock_textual_app.App = MockApp
    mock_textual_containers.Vertical = MockVertical
    mock_textual_widgets.Header = MockHeader
    mock_textual_widgets.Footer = MockFooter
    mock_textual_widgets.Static = MockStatic
    mock_textual_widgets.Input = MockInput

    return {
        "textual": mock_textual,
        "textual.app": mock_textual_app,
        "textual.containers": mock_textual_containers,
        "textual.widgets": mock_textual_widgets,
    }


def _install_mock_textual(context):
    """Inject mock textual into sys.modules and reload the app module."""
    mocks = _build_mock_textual()
    context._tui_saved_modules = {}
    for key in _MOCK_TEXTUAL_KEYS:
        context._tui_saved_modules[key] = sys.modules.pop(key, None)
    for key, mod in mocks.items():
        sys.modules[key] = mod

    # Reload widget modules so they pick up the mock Static/Input base class
    import cleveragents.tui.widgets.help_panel_overlay as hp_mod
    import cleveragents.tui.widgets.persona_bar as pb_mod
    import cleveragents.tui.widgets.prompt as prompt_mod
    import cleveragents.tui.widgets.reference_picker as rp_mod
    import cleveragents.tui.widgets.slash_command_overlay as sco_mod

    importlib.reload(hp_mod)
    importlib.reload(pb_mod)
    importlib.reload(prompt_mod)
    importlib.reload(rp_mod)
    importlib.reload(sco_mod)

    import cleveragents.tui.app as app_mod

    importlib.reload(app_mod)
    context._tui_app_mod = app_mod
    context._tui_mock_static = mocks["textual.widgets"].Static


def _restore_modules(context):
    """Restore original sys.modules and reload the app module."""
    for key, val in getattr(context, "_tui_saved_modules", {}).items():
        if val is None:
            sys.modules.pop(key, None)
        else:
            sys.modules[key] = val

    # Reload widget modules so they pick up the real Static/Input base class again
    import cleveragents.tui.widgets.help_panel_overlay as hp_mod
    import cleveragents.tui.widgets.persona_bar as pb_mod
    import cleveragents.tui.widgets.prompt as prompt_mod
    import cleveragents.tui.widgets.reference_picker as rp_mod
    import cleveragents.tui.widgets.slash_command_overlay as sco_mod

    importlib.reload(hp_mod)
    importlib.reload(pb_mod)
    importlib.reload(prompt_mod)
    importlib.reload(rp_mod)
    importlib.reload(sco_mod)

    import cleveragents.tui.app as app_mod

    importlib.reload(app_mod)


def _make_persona_state(context):
    """Create a real PersonaState backed by a temp directory."""
    from cleveragents.tui.persona.registry import PersonaRegistry
    from cleveragents.tui.persona.state import PersonaState

    tmp = tempfile.mkdtemp()
    context._tui_tmpdir = tmp
    registry = PersonaRegistry(config_dir=Path(tmp))
    registry.ensure_default()
    return PersonaState(registry=registry)


def _cleanup_tmpdir(context):
    tmp = getattr(context, "_tui_tmpdir", None)
    if tmp:
        shutil.rmtree(tmp, ignore_errors=True)


# ---------------------------------------------------------------------------
# Background
# ---------------------------------------------------------------------------
@given("the TUI app module is imported with mocked Textual")
def step_import_with_mock_textual(context):
    """Install mock Textual, reload app module, register cleanup."""
    _install_mock_textual(context)
    context.add_cleanup(lambda: _restore_modules(context))
    context.add_cleanup(lambda: _cleanup_tmpdir(context))


# ---------------------------------------------------------------------------
# Module-level import gate (lines 31-38)
# ---------------------------------------------------------------------------
@then("the reloaded app module should have _TEXTUAL_AVAILABLE True")
def step_textual_available_true(context):
    assert context._tui_app_mod._TEXTUAL_AVAILABLE is True


@then("the reloaded app module should expose _TextualCleverAgentsTuiApp")
def step_has_textual_class(context):
    assert hasattr(context._tui_app_mod, "_TextualCleverAgentsTuiApp")


# ---------------------------------------------------------------------------
# textual_available function (lines 43-45)
# ---------------------------------------------------------------------------
@then("textual_available should return True on the reloaded module")
def step_textual_available_returns_true(context):
    assert context._tui_app_mod.textual_available() is True


# ---------------------------------------------------------------------------
# SessionView (lines 48-54)
# ---------------------------------------------------------------------------
@when('I create a SessionView with session_id "{sid}" and transcript entries')
def step_create_session_view(context, sid):
    context._tui_session_view = context._tui_app_mod.SessionView(
        session_id=sid, transcript=["line1", "line2"]
    )


@then('the SessionView session_id should be "{sid}"')
def step_session_view_id(context, sid):
    assert context._tui_session_view.session_id == sid


@then("the SessionView transcript should contain {count:d} entries")
def step_session_view_transcript_count(context, count):
    assert len(context._tui_session_view.transcript) == count


# ---------------------------------------------------------------------------
# _CommandRouter protocol (lines 56-61)
# ---------------------------------------------------------------------------
@when("I create a _CommandRouter implementation")
def step_create_command_router_impl(context):
    class MyRouter:
        def handle(self, raw, *, session_id):
            return f"response:{raw}:{session_id}"

    context._tui_router_impl = MyRouter()


@then("calling handle on the implementation should return a response")
def step_call_handle(context):
    result = context._tui_router_impl.handle("test", session_id="s1")
    assert result == "response:test:s1"


# ---------------------------------------------------------------------------
# App instantiation (lines 91-100)
# ---------------------------------------------------------------------------
class _FakeCommandRouter:
    """Test command router that returns predictable responses."""

    def handle(self, raw, *, session_id):
        return f"handled:{raw}"


@given("a mock command router and persona state")
def step_create_mock_deps(context):
    context._tui_cmd_router = _FakeCommandRouter()
    context._tui_persona_state = _make_persona_state(context)


@when("I instantiate the Textual TUI app")
def step_instantiate_app(context):
    AppClass = context._tui_app_mod._ResolvedTuiApp
    context._tui_app = AppClass(
        command_router=context._tui_cmd_router,
        persona_state=context._tui_persona_state,
    )


@then('the app should have a _session with session_id "{sid}"')
def step_app_session_id(context, sid):
    assert context._tui_app._session.session_id == sid


@then("the app should store the command router")
def step_app_has_router(context):
    assert context._tui_app._command_router is context._tui_cmd_router


@then("the app should store the persona state")
def step_app_has_persona_state(context):
    assert context._tui_app._persona_state is context._tui_persona_state


# ---------------------------------------------------------------------------
# Class variables (lines 84-89)
# ---------------------------------------------------------------------------
@then('the app class should have CSS_PATH set to "{path}"')
def step_css_path(context, path):
    assert path == context._tui_app.CSS_PATH


@then("the app class should have {count:d} key bindings")
def step_bindings_count(context, count):
    assert len(context._tui_app.BINDINGS) == count


# ---------------------------------------------------------------------------
# compose (lines 102-112)
# ---------------------------------------------------------------------------
@when("I call compose on the app")
def step_call_compose(context):
    context._tui_compose_items = list(context._tui_app.compose())


@then("compose should yield at least {count:d} widgets")
def step_compose_count(context, count):
    assert len(context._tui_compose_items) >= count


# ---------------------------------------------------------------------------
# on_mount (lines 114-121)
# ---------------------------------------------------------------------------
@when("I call on_mount on the app")
def step_call_on_mount(context):
    context._tui_app.on_mount()


@then("the persona bar should have content set")
def step_persona_bar_has_content(context):
    from cleveragents.tui.widgets.persona_bar import PersonaBar

    bar = context._tui_app.query_one("#persona-bar", PersonaBar)
    assert hasattr(bar, "_text")
    assert bar._text  # non-empty


@then("the help panel should be hidden on mount")
def step_help_panel_hidden_on_mount(context):
    from cleveragents.tui.widgets.help_panel_overlay import HelpPanelOverlay

    panel = context._tui_app.query_one("#help-panel", HelpPanelOverlay)
    assert panel.visible is False
    assert panel._text == ""


@then("the reference picker should have suggestions initialised")
def step_ref_picker_initialised(context):
    from cleveragents.tui.widgets.reference_picker import ReferencePickerOverlay

    picker = context._tui_app.query_one("#reference-picker", ReferencePickerOverlay)
    assert hasattr(picker, "_text")


@then("the slash overlay should have commands initialised")
def step_slash_overlay_initialised(context):
    from cleveragents.tui.widgets.slash_command_overlay import SlashCommandOverlay

    overlay = context._tui_app.query_one("#slash-overlay", SlashCommandOverlay)
    assert hasattr(overlay, "_text")
    assert overlay._text  # non-empty


# ---------------------------------------------------------------------------
# action_help (lines 123-125)
# ---------------------------------------------------------------------------
@when("I call action_help on the app")
def step_call_action_help(context):
    context._tui_app.action_help()


@when('I set the prompt text to "{text}"')
def step_set_prompt_text(context, text):
    from cleveragents.tui.widgets.prompt import PromptInput

    prompt = context._tui_app.query_one("#prompt", PromptInput)
    prompt.value = text


@then('the conversation widget should contain "{text}"')
def step_conversation_contains(context, text):
    MockStatic = context._tui_mock_static
    conv = context._tui_app.query_one("#conversation", MockStatic)
    assert text in conv._text, f"Expected '{text}' in '{conv._text}'"


@then('the help panel text should contain "{text}"')
def step_help_panel_contains(context, text):
    from cleveragents.tui.widgets.help_panel_overlay import HelpPanelOverlay

    panel = context._tui_app.query_one("#help-panel", HelpPanelOverlay)
    assert text in panel._text, f"Expected '{text}' in '{panel._text}'"


# ---------------------------------------------------------------------------
# action_cycle_preset (lines 127-129)
# ---------------------------------------------------------------------------
@when("I call action_cycle_preset on the app")
def step_call_action_cycle_preset(context):
    context._tui_app.action_cycle_preset()


@then("the persona bar content should be refreshed")
def step_persona_bar_refreshed(context):
    from cleveragents.tui.widgets.persona_bar import PersonaBar

    bar = context._tui_app.query_one("#persona-bar", PersonaBar)
    assert bar._text  # non-empty after refresh


# ---------------------------------------------------------------------------
# _refresh_persona_bar (lines 131-142)
# ---------------------------------------------------------------------------
@when("I call _refresh_persona_bar on the app")
def step_call_refresh_persona_bar(context):
    context._tui_app._refresh_persona_bar()


@then("the persona bar should display the persona name and scope")
def step_persona_bar_shows_name(context):
    from cleveragents.tui.widgets.persona_bar import PersonaBar

    bar = context._tui_app.query_one("#persona-bar", PersonaBar)
    assert "default" in bar._text
    assert "scope refs" in bar._text


# ---------------------------------------------------------------------------
# on_input_submitted helpers
# ---------------------------------------------------------------------------
def _submit_text(context, text):
    """Set prompt value and fire on_input_submitted."""
    from cleveragents.tui.widgets.prompt import PromptInput

    prompt = context._tui_app.query_one("#prompt", PromptInput)
    prompt.value = text
    event = SimpleNamespace()
    context._tui_app.on_input_submitted(event)


# ---------------------------------------------------------------------------
# on_input_submitted: empty text (lines 144-150)
# ---------------------------------------------------------------------------
@when("I submit empty text to the app")
def step_submit_empty(context):
    MockStatic = context._tui_mock_static
    conv = context._tui_app.query_one("#conversation", MockStatic)
    context._tui_conv_before = conv._text
    _submit_text(context, "")


@then("the conversation widget should not be updated by input")
def step_conv_not_updated(context):
    MockStatic = context._tui_mock_static
    conv = context._tui_app.query_one("#conversation", MockStatic)
    assert conv._text == context._tui_conv_before


# ---------------------------------------------------------------------------
# on_input_submitted: command mode (lines 152-167)
# ---------------------------------------------------------------------------
@when('I submit "{text}" to the app')
def step_submit_text(context, text):
    os.environ["CLEVERAGENTS_ALLOW_DANGEROUS_SHELL"] = "1"

    def restore_env():
        os.environ.pop("CLEVERAGENTS_ALLOW_DANGEROUS_SHELL", None)

    context.add_cleanup(restore_env)
    _submit_text(context, text)


# ---------------------------------------------------------------------------
# on_input_submitted: shell returning None (lines 170-171)
# ---------------------------------------------------------------------------
@when("I submit shell text that produces a None shell result")
def step_submit_shell_none(context):
    from cleveragents.tui.input.modes import InputMode, ModeResult

    # We patch the InputModeRouter.process to return a shell result with None
    with patch(
        "cleveragents.tui.app.InputModeRouter.process",
        return_value=ModeResult(
            mode=InputMode.SHELL,
            expanded_text="!nothing",
            references=[],
            shell_result=None,
            command_result=None,
        ),
    ):
        _submit_text(context, "!nothing")


# ---------------------------------------------------------------------------
# on_input_submitted: normal text with @ (lines 179-185)
# ---------------------------------------------------------------------------
@then("the reference picker should have been updated")
def step_ref_picker_updated(context):
    from cleveragents.tui.widgets.reference_picker import ReferencePickerOverlay

    picker = context._tui_app.query_one("#reference-picker", ReferencePickerOverlay)
    assert hasattr(picker, "_text")
    assert picker._text  # updated with suggestions


# ---------------------------------------------------------------------------
# CleverAgentsTuiApp alias (line 189)
# ---------------------------------------------------------------------------
@then("CleverAgentsTuiApp should be _TextualCleverAgentsTuiApp on the reloaded module")
def step_alias_check(context):
    assert (
        context._tui_app_mod.CleverAgentsTuiApp is context._tui_app_mod._ResolvedTuiApp
    )
    assert (
        context._tui_app_mod.CleverAgentsTuiApp.__name__ == "_TextualCleverAgentsTuiApp"
    )
