"""Step definitions for lsp_hover_definitions.feature.

Comprehensive tests for get_hover and get_definitions on LspClient
and LspRuntime, covering all response paths and input validation.
"""

from __future__ import annotations

from typing import Any
from unittest.mock import MagicMock

from behave import then, when

from cleveragents.lsp.client import LspClient
from cleveragents.lsp.runtime import LspRuntime


def _mock_client(**send_return: Any) -> LspClient:
    transport = MagicMock()
    transport.is_alive = True
    client = LspClient(transport=transport)
    client._send_request = MagicMock(**send_return)
    return client


def _runtime_no_init() -> LspRuntime:
    return LspRuntime.__new__(LspRuntime)


# ── Method existence ─────────────────────────────────────


@then('LspClient should have a "{method}" method for lsp_hd')
def step_client_has(context: object, method: str) -> None:
    assert hasattr(LspClient, method) and callable(getattr(LspClient, method))


@then('LspRuntime should have a "{method}" method for lsp_hd')
def step_runtime_has(context: object, method: str) -> None:
    assert hasattr(LspRuntime, method) and callable(getattr(LspRuntime, method))


# ── LspClient get_hover paths ────────────────────────────


@when("I call LspClient.get_hover with mock returning a dict for lsp_hd")
def step_hover_dict(context: object) -> None:
    client = _mock_client(return_value={"contents": "**int**"})
    context.lsp_hd_hover = client.get_hover("file:///t.py", 0, 0)


@when("I call LspClient.get_hover with mock returning null for lsp_hd")
def step_hover_null(context: object) -> None:
    client = _mock_client(return_value=None)
    context.lsp_hd_hover = client.get_hover("file:///t.py", 0, 0)


@when("I call LspClient.get_hover with mock returning an integer for lsp_hd")
def step_hover_int(context: object) -> None:
    client = _mock_client(return_value=42)
    context.lsp_hd_hover = client.get_hover("file:///t.py", 0, 0)


@then("the hover result should be a dict for lsp_hd")
def step_hover_is_dict(context: object) -> None:
    assert isinstance(context.lsp_hd_hover, dict)


@then("the hover result should be None for lsp_hd")
def step_hover_is_none(context: object) -> None:
    assert context.lsp_hd_hover is None


# ── LspClient get_definitions paths ──────────────────────


@when("I call LspClient.get_definitions with mock returning a list for lsp_hd")
def step_defs_list(context: object) -> None:
    client = _mock_client(return_value=[{"uri": "f", "range": {}}])
    context.lsp_hd_defs = client.get_definitions("f", 0, 0)


@when("I call LspClient.get_definitions with mock returning a single dict for lsp_hd")
def step_defs_single(context: object) -> None:
    client = _mock_client(return_value={"uri": "f", "range": {}})
    context.lsp_hd_defs = client.get_definitions("f", 0, 0)


@when("I call LspClient.get_definitions with mock returning null for lsp_hd")
def step_defs_null(context: object) -> None:
    client = _mock_client(return_value=None)
    context.lsp_hd_defs = client.get_definitions("f", 0, 0)


@when("I call LspClient.get_definitions with mock returning an integer for lsp_hd")
def step_defs_int(context: object) -> None:
    client = _mock_client(return_value=42)
    context.lsp_hd_defs = client.get_definitions("f", 0, 0)


@then("the definitions result should be a list with 1 item for lsp_hd")
def step_defs_one(context: object) -> None:
    defs = context.lsp_hd_defs
    assert isinstance(defs, list) and len(defs) == 1


@then("the definitions result should be an empty list for lsp_hd")
def step_defs_empty(context: object) -> None:
    defs = context.lsp_hd_defs
    assert isinstance(defs, list) and len(defs) == 0


# ── LspRuntime input validation ──────────────────────────


def _call_rt(method: str, **overrides: Any) -> Exception | None:
    rt = _runtime_no_init()
    kw = {"name": "x", "file_path": "/t", "line": 1, "column": 1}
    kw.update(overrides)
    try:
        getattr(rt, method)(**kw)
    except ValueError as exc:
        return exc
    return None


@when("I call LspRuntime.get_hover with empty name for lsp_hd")
def step_rt_hover_name(context: object) -> None:
    context.lsp_hd_err = _call_rt("get_hover", name="")


@when("I call LspRuntime.get_hover with empty file_path for lsp_hd")
def step_rt_hover_path(context: object) -> None:
    context.lsp_hd_err = _call_rt("get_hover", file_path="")


@when("I call LspRuntime.get_hover with line 0 for lsp_hd")
def step_rt_hover_line(context: object) -> None:
    context.lsp_hd_err = _call_rt("get_hover", line=0)


@when("I call LspRuntime.get_hover with column 0 for lsp_hd")
def step_rt_hover_col(context: object) -> None:
    context.lsp_hd_err = _call_rt("get_hover", column=0)


@when("I call LspRuntime.get_definitions with empty name for lsp_hd")
def step_rt_defs_name(context: object) -> None:
    context.lsp_hd_err = _call_rt("get_definitions", name="")


@when("I call LspRuntime.get_definitions with empty file_path for lsp_hd")
def step_rt_defs_path(context: object) -> None:
    context.lsp_hd_err = _call_rt("get_definitions", file_path="")


@when("I call LspRuntime.get_definitions with line 0 for lsp_hd")
def step_rt_defs_line(context: object) -> None:
    context.lsp_hd_err = _call_rt("get_definitions", line=0)


@when("I call LspRuntime.get_definitions with column 0 for lsp_hd")
def step_rt_defs_col(context: object) -> None:
    context.lsp_hd_err = _call_rt("get_definitions", column=0)


@then('a ValueError should be raised for lsp_hd with message "{msg}"')
def step_val_err(context: object, msg: str) -> None:
    err = context.lsp_hd_err
    assert err is not None, "Expected ValueError"
    assert msg in str(err), f"'{msg}' not in: {err}"


# ── Tool adapter ─────────────────────────────────────────


@then('the tool adapter capability map should include "{cap}" for lsp_hd')
def step_adapter_cap(context: object, cap: str) -> None:
    from cleveragents.lsp.tool_adapter import _CAPABILITY_TOOL_MAP

    caps = [c.value for c in _CAPABILITY_TOOL_MAP]
    assert cap in caps, f"'{cap}' not in: {caps}"
