"""Step definitions for tui_first_run.feature.

Covers:
- is_first_run helper
- create_default_persona_for_actor helper
- render_actor_selection helper
- ActorSelectionOverlay widget
- TUI app integration (on_mount first-run detection, _complete_first_run)
"""

from __future__ import annotations

import sys
import tempfile
from pathlib import Path
from types import ModuleType
from unittest.mock import MagicMock

from behave import given, then, when

# ---------------------------------------------------------------------------
# Shared mock-Textual infrastructure (mirrors tui_app_coverage_steps.py)
# ---------------------------------------------------------------------------

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


def _build_mock_textual() -> dict[str, ModuleType]:
    mock_textual = ModuleType("textual")
    mock_textual_app = ModuleType("textual.app")
    mock_textual_containers = ModuleType("textual.containers")
    mock_textual_widgets = ModuleType("textual.widgets")

    class MockApp:
        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
            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

    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

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


def _reload_tui_modules(mock_modules: dict[str, ModuleType]) -> None:
    """Reload TUI modules with mocked Textual in sys.modules."""
    tui_keys = [
        key for key in list(sys.modules.keys()) if key.startswith("cleveragents.tui")
    ]
    for key in tui_keys:
        del sys.modules[key]
    for name, mod in mock_modules.items():
        sys.modules[name] = mod


def _restore_textual(original: dict[str, object]) -> None:
    for key in _MOCK_TEXTUAL_KEYS:
        if key in original:
            sys.modules[key] = original[key]  # type: ignore[assignment]
        else:
            sys.modules.pop(key, None)


# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------


def _make_registry(tmp_dir: Path) -> object:
    from cleveragents.tui.persona.registry import PersonaRegistry

    return PersonaRegistry(config_dir=tmp_dir)


# ---------------------------------------------------------------------------
# is_first_run
# ---------------------------------------------------------------------------


@given("an empty persona registry")
def step_empty_registry(context: object) -> None:
    tmp = tempfile.mkdtemp()
    context._tmp_dir = Path(tmp)
    context._registry = _make_registry(context._tmp_dir)


@given('a persona registry with a default persona using actor "{actor}"')
def step_registry_with_persona(context: object, actor: str) -> None:
    tmp = tempfile.mkdtemp()
    context._tmp_dir = Path(tmp)
    context._registry = _make_registry(context._tmp_dir)
    from cleveragents.tui.persona.schema import Persona

    persona = Persona(name="default", actor=actor, description="test")
    context._registry.save(persona)


@then("is_first_run should return True")
def step_is_first_run_true(context: object) -> None:
    from cleveragents.tui.first_run import is_first_run

    assert is_first_run(context._registry) is True


@then("is_first_run should return False")
def step_is_first_run_false(context: object) -> None:
    from cleveragents.tui.first_run import is_first_run

    assert is_first_run(context._registry) is False


# ---------------------------------------------------------------------------
# create_default_persona_for_actor
# ---------------------------------------------------------------------------


@when('I call create_default_persona_for_actor with actor "{actor}"')
def step_create_default_persona(context: object, actor: str) -> None:
    from cleveragents.tui.first_run import create_default_persona_for_actor

    context._created_persona = create_default_persona_for_actor(
        context._registry, actor
    )


@then('the registry should contain a persona named "{name}"')
def step_registry_has_persona(context: object, name: str) -> None:
    persona = context._registry.get(name)
    assert persona is not None, f"Expected persona '{name}' in registry"


@then('the default persona actor should be "{actor}"')
def step_default_persona_actor(context: object, actor: str) -> None:
    persona = context._registry.get("default")
    assert persona is not None
    assert persona.actor == actor, f"Expected actor '{actor}', got '{persona.actor}'"


@then('the registry last persona should be "{name}"')
def step_registry_last_persona(context: object, name: str) -> None:
    last = context._registry.get_last_persona()
    assert last == name, f"Expected last persona '{name}', got '{last}'"


# ---------------------------------------------------------------------------
# render_actor_selection
# ---------------------------------------------------------------------------


@when("I render the actor selection with default actors")
def step_render_default(context: object) -> None:
    from cleveragents.tui.widgets.actor_selection_overlay import (
        _DEFAULT_ACTORS,
        render_actor_selection,
    )

    context._rendered = render_actor_selection(_DEFAULT_ACTORS, 0, "")


@when("I render the actor selection with selected index 2")
def step_render_selected_index(context: object) -> None:
    from cleveragents.tui.widgets.actor_selection_overlay import (
        _DEFAULT_ACTORS,
        render_actor_selection,
    )

    context._rendered = render_actor_selection(_DEFAULT_ACTORS, 2, "")


@then("the rendered text should contain a cursor marker")
def step_rendered_contains_cursor(context: object) -> None:
    # The cursor character is the heavy right-pointing angle quotation mark
    # used as a selection indicator per the spec mockup.
    cursor = "\u276f"
    assert cursor in context._rendered, (
        f"Expected cursor marker in rendered text:\n{context._rendered}"
    )


@when('I render the actor selection with search query "{query}"')
def step_render_with_query(context: object, query: str) -> None:
    from cleveragents.tui.widgets.actor_selection_overlay import (
        _DEFAULT_ACTORS,
        render_actor_selection,
    )

    context._rendered = render_actor_selection(_DEFAULT_ACTORS, 0, query)


@then('the rendered text should contain "{text}"')
def step_rendered_contains(context: object, text: str) -> None:
    assert text in context._rendered, (
        f"Expected '{text}' in rendered text:\n{context._rendered}"
    )


# ---------------------------------------------------------------------------
# ActorSelectionOverlay widget
# ---------------------------------------------------------------------------


@given("a new ActorSelectionOverlay")
def step_new_overlay(context: object) -> None:
    from cleveragents.tui.widgets.actor_selection_overlay import ActorSelectionOverlay

    context._overlay = ActorSelectionOverlay()


@when("I call show on the overlay")
def step_overlay_show(context: object) -> None:
    context._overlay.show()


@when("I call hide on the overlay")
def step_overlay_hide(context: object) -> None:
    context._overlay.hide()


@when("I call show on the overlay with actors {actors_json}")
def step_overlay_show_custom(context: object, actors_json: str) -> None:
    import json

    actors = json.loads(actors_json)
    context._overlay.show(actors=actors)


@when("I call move_down on the overlay")
def step_overlay_move_down(context: object) -> None:
    context._overlay.move_down()


@when("I call move_up on the overlay")
def step_overlay_move_up(context: object) -> None:
    context._overlay.move_up()


@when("I move_down past the last actor")
def step_overlay_move_down_past_last(context: object) -> None:
    count = len(context._overlay.filtered_actors)
    for _ in range(count):
        context._overlay.move_down()


@when('I call set_search with query "{query}"')
def step_overlay_set_search(context: object, query: str) -> None:
    context._overlay.set_search(query)


@when('I call set_search with query ""')
def step_overlay_set_search_empty(context: object) -> None:
    context._overlay.set_search("")


@when("I call confirm on the overlay")
def step_overlay_confirm(context: object) -> None:
    context._confirmed_result = context._overlay.confirm()


@then("the overlay should be visible")
def step_overlay_visible(context: object) -> None:
    assert context._overlay.visible is True


@then("the overlay should not be visible")
def step_overlay_not_visible(context: object) -> None:
    assert context._overlay.visible is False


@then('the overlay actors list should contain "{actor}"')
def step_overlay_actors_contains(context: object, actor: str) -> None:
    assert actor in context._overlay.actors, (
        f"Expected '{actor}' in actors: {context._overlay.actors}"
    )


@then("the overlay selected index should be 1")
def step_overlay_selected_1(context: object) -> None:
    assert context._overlay.selected_index == 1, (
        f"Expected 1, got {context._overlay.selected_index}"
    )


@then("the overlay selected index should be the last index")
def step_overlay_selected_last(context: object) -> None:
    last = len(context._overlay.filtered_actors) - 1
    assert context._overlay.selected_index == last, (
        f"Expected {last}, got {context._overlay.selected_index}"
    )


@then("the overlay selected index should be 0")
def step_overlay_selected_0(context: object) -> None:
    assert context._overlay.selected_index == 0, (
        f"Expected 0, got {context._overlay.selected_index}"
    )


@then('the overlay filtered actors should only contain actors matching "{query}"')
def step_overlay_filtered_match(context: object, query: str) -> None:
    for actor in context._overlay.filtered_actors:
        assert query.lower() in actor.lower(), (
            f"Actor '{actor}' does not match query '{query}'"
        )


@then("the overlay filtered actors count should equal the full actors count")
def step_overlay_filtered_count_full(context: object) -> None:
    assert len(context._overlay.filtered_actors) == len(context._overlay.actors), (
        f"Filtered: {len(context._overlay.filtered_actors)}, "
        f"Full: {len(context._overlay.actors)}"
    )


@then('the confirmed actor should be "{actor}"')
def step_confirmed_actor(context: object, actor: str) -> None:
    assert context._confirmed_result == actor, (
        f"Expected '{actor}', got '{context._confirmed_result}'"
    )


@then("the confirmed actor should be None")
def step_confirmed_actor_none(context: object) -> None:
    assert context._confirmed_result is None, (
        f"Expected None, got '{context._confirmed_result}'"
    )


@then("the overlay confirmed flag should be True")
def step_overlay_confirmed_flag(context: object) -> None:
    assert context._overlay.confirmed is True


@then("the overlay selected_actor should be None")
def step_overlay_selected_actor_none(context: object) -> None:
    assert context._overlay.selected_actor is None


@then('the overlay search_query should be ""')
def step_overlay_search_query_empty(context: object) -> None:
    assert context._overlay.search_query == "", (
        f"Expected empty string, got '{context._overlay.search_query}'"
    )


# ---------------------------------------------------------------------------
# TUI app integration
# ---------------------------------------------------------------------------


def _build_first_run_app(context: object, *, has_personas: bool) -> None:
    """Build a mocked TUI app for first-run integration tests."""
    tmp = tempfile.mkdtemp()
    context._tmp_dir = Path(tmp)

    original = {k: sys.modules[k] for k in _MOCK_TEXTUAL_KEYS if k in sys.modules}
    # Save all cleveragents.tui.* modules before deleting them so we can
    # restore them after the test (prevents module re-import issues in
    # subsequent tests that patch cleveragents.tui.commands.get_container).
    original_tui = {
        k: sys.modules[k]
        for k in list(sys.modules.keys())
        if k.startswith("cleveragents.tui")
    }
    mock_modules = _build_mock_textual()
    _reload_tui_modules(mock_modules)

    def _cleanup() -> None:
        _restore_textual(original)
        # Restore cleveragents.tui.* modules to prevent re-import issues
        for key, mod in original_tui.items():
            sys.modules[key] = mod

    context.add_cleanup(_cleanup)

    from cleveragents.tui.persona.registry import PersonaRegistry
    from cleveragents.tui.persona.schema import Persona
    from cleveragents.tui.persona.state import PersonaState

    registry = PersonaRegistry(config_dir=context._tmp_dir)
    if has_personas:
        persona = Persona(name="default", actor="local/mock", description="test")
        registry.save(persona)

    context._registry = registry
    persona_state = PersonaState(registry=registry)

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

    from cleveragents.tui.app import CleverAgentsTuiApp

    app = CleverAgentsTuiApp(command_router=_MockRouter(), persona_state=persona_state)
    context._first_run_app = app


@given("the TUI app is initialised with an empty persona registry")
def step_app_empty_registry(context: object) -> None:
    _build_first_run_app(context, has_personas=False)


@given("the TUI app is initialised with an existing persona registry")
def step_app_existing_registry(context: object) -> None:
    _build_first_run_app(context, has_personas=True)


@when("I call on_mount on the first-run app")
def step_app_on_mount(context: object) -> None:
    context._first_run_app.on_mount()


@then("the actor selection overlay should be visible")
def step_actor_overlay_visible(context: object) -> None:
    from cleveragents.tui.widgets.actor_selection_overlay import ActorSelectionOverlay

    overlay = context._first_run_app.query_one(
        "#actor-selection", ActorSelectionOverlay
    )
    assert overlay.visible is True, "Expected actor selection overlay to be visible"


@then("the actor selection overlay should not be visible")
def step_actor_overlay_not_visible(context: object) -> None:
    from cleveragents.tui.widgets.actor_selection_overlay import ActorSelectionOverlay

    overlay = context._first_run_app.query_one(
        "#actor-selection", ActorSelectionOverlay
    )
    assert overlay.visible is False, "Expected actor selection overlay to be hidden"


@when('I call _complete_first_run with actor "{actor}"')
def step_complete_first_run(context: object, actor: str) -> None:
    context._first_run_app._complete_first_run(actor)


@then("the persona bar should reflect the new actor")
def step_persona_bar_reflects_actor(context: object) -> None:
    from cleveragents.tui.widgets.persona_bar import PersonaBar

    bar = context._first_run_app.query_one("#persona-bar", PersonaBar)
    assert "anthropic/claude-4-sonnet" in bar._text, (
        f"Expected actor in persona bar, got: {bar._text}"
    )
