"""Step definitions for tdd_tui_suggestions_query_extraction_4741.feature.

Regression test for issue #4741:
  on_input_submitted must extract only the @token text (without the @
  sign and without surrounding non-reference words) as the query passed to
  suggestions(). Previously, text.replace("@", "").strip() was used, which
  corrupted the query by including all non-reference words from the prompt.
"""

from __future__ import annotations

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

from behave import given, then, when

# ---------------------------------------------------------------------------
# Mock Textual infrastructure (mirrors tui_app_coverage_steps.py)
# ---------------------------------------------------------------------------

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


def _build_mock_textual_4741() -> dict[str, ModuleType]:
    """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: object, **kwargs: object) -> None:
            self._widgets: dict[str, object] = {}

        def query_one(self, selector: str, widget_type: type | None = None) -> object:
            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
            from unittest.mock import MagicMock

            return MagicMock()

    class MockVertical:
        def __init__(self, *args: object, **kwargs: object) -> None:
            pass

        def __enter__(self) -> MockVertical:
            return self

        def __exit__(self, *args: object) -> None:
            pass

    class MockHeader:
        def __init__(self, *args: object, **kwargs: object) -> None:
            pass

    class MockFooter:
        def __init__(self, *args: object, **kwargs: object) -> None:
            pass

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

        def update(self, text: str) -> None:
            self._text = text

    class MockTextArea:
        """Minimal TextArea stand-in for the Textual base class."""

        text: str = ""

        def __init__(self, *args: object, **kwargs: object) -> None:
            self.text = ""

    setattr(mock_textual_app, "App", MockApp)
    setattr(mock_textual_containers, "Vertical", MockVertical)
    setattr(mock_textual_widgets, "Header", MockHeader)
    setattr(mock_textual_widgets, "Footer", MockFooter)
    setattr(mock_textual_widgets, "Static", MockStatic)
    setattr(mock_textual_widgets, "TextArea", MockTextArea)

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


def _install_mock_textual_4741(context: object) -> None:
    """Inject mock textual into sys.modules and reload the app module."""
    mocks = _build_mock_textual_4741()
    context._tui4741_saved_modules: dict[str, ModuleType | None] = {}
    for key in _MOCK_TEXTUAL_KEYS:
        context._tui4741_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/TextArea 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._tui4741_app_mod = app_mod
    context._tui4741_mock_static = mocks["textual.widgets"].Static


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

    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_4741(context: object) -> object:
    """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._tui4741_tmpdir = tmp
    registry = PersonaRegistry(config_dir=Path(tmp))
    registry.ensure_default()
    return PersonaState(registry=registry)


def _cleanup_tmpdir_4741(context: object) -> None:
    tmp = getattr(context, "_tui4741_tmpdir", None)
    if tmp:
        shutil.rmtree(tmp, ignore_errors=True)


# ---------------------------------------------------------------------------
# Background
# ---------------------------------------------------------------------------


@given("the TUI app module is imported with mocked Textual for issue 4741")
def step_import_with_mock_textual_4741(context: object) -> None:
    """Install mock Textual, reload app module, register cleanup."""
    _install_mock_textual_4741(context)
    context.add_cleanup(lambda: _restore_modules_4741(context))
    context.add_cleanup(lambda: _cleanup_tmpdir_4741(context))


# ---------------------------------------------------------------------------
# App setup
# ---------------------------------------------------------------------------


class _FakeCommandRouter4741:
    """Test command router that returns predictable responses."""

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


@given("a mock command router and persona state for issue 4741")
def step_create_mock_deps_4741(context: object) -> None:
    context._tui4741_cmd_router = _FakeCommandRouter4741()
    context._tui4741_persona_state = _make_persona_state_4741(context)


@when("I instantiate the TUI app for issue 4741")
def step_instantiate_app_4741(context: object) -> None:
    AppClass = context._tui4741_app_mod._ResolvedTuiApp
    context._tui4741_app = AppClass(
        command_router=context._tui4741_cmd_router,
        persona_state=context._tui4741_persona_state,
    )


@when("I call on_mount on the app for issue 4741")
def step_call_on_mount_4741(context: object) -> None:
    context._tui4741_app.on_mount()


# ---------------------------------------------------------------------------
# Core regression steps: verify correct query extraction
# ---------------------------------------------------------------------------


@when('I submit "{text}" to the app for issue 4741 and capture the suggestions query')
def step_submit_and_capture_query_4741(context: object, text: str) -> None:
    """Submit text and capture the query argument passed to suggestions()."""
    from cleveragents.tui.widgets.prompt import PromptInput

    captured: list[str] = []

    def _fake_suggestions(
        query: str, *, category: str | None = None, limit: int = 8
    ) -> list[str]:
        captured.append(query)
        return []

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

    with patch(
        "cleveragents.tui.app.suggestions",
        side_effect=_fake_suggestions,
    ):
        context._tui4741_app.on_input_submitted(event)

    context._tui4741_captured_query = captured[0] if captured else None


@then('the suggestions query should be "{expected}" not "{wrong}"')
def step_assert_suggestions_query_4741(
    context: object, expected: str, wrong: str
) -> None:
    """Assert the captured query matches expected and not the wrong (buggy) value."""
    actual = context._tui4741_captured_query
    assert actual is not None, (
        f"suggestions() was not called — no @token found in submitted text. "
        f"Expected query: '{expected}'"
    )
    assert actual == expected, (
        f"suggestions() received wrong query.\n"
        f"  Expected: '{expected}'\n"
        f"  Got:      '{actual}'\n"
        f"  (The buggy value would have been: '{wrong}')"
    )


@then("suggestions was not called for issue 4741")
def step_assert_suggestions_not_called_4741(context: object) -> None:
    """Assert that suggestions() was not called (no @token in prompt)."""
    actual = context._tui4741_captured_query
    assert actual is None, (
        f"suggestions() was unexpectedly called with query: '{actual}'. "
        f"Expected it not to be called when no @token is present in the prompt."
    )
