"""Step definitions for A2A facade coverage-boost scenarios.

Targets uncovered lines in ``src/cleveragents/a2a/facade.py``:

| Line | Code path                                              |
|------|--------------------------------------------------------|
| 82   | TypeError when ``services`` is not a dict or None      |
| 256  | ValueError in plan.execute when plan_id is empty       |
| 266  | ValueError in plan.status  when plan_id is empty       |
| 281  | ValueError in plan.diff   when plan_id is empty        |
| 295  | ValueError in plan.apply  when plan_id is empty        |
"""

from __future__ import annotations

import json
from typing import Any
from unittest.mock import MagicMock

from behave import given, then, use_step_matcher, when
from behave.runner import Context

try:
    from cleveragents.a2a.facade import A2aLocalFacade
    from cleveragents.a2a.models import A2aRequest
except ImportError:
    pass  # a2a module not available
from features.steps._a2a_code_map import A2A_CODE_MAP

use_step_matcher("re")


# ---------------------------------------------------------------------------
# Mock helpers (mirrors wiring steps — kept local to avoid coupling)
# ---------------------------------------------------------------------------


class _MockPlanIdentity:
    def __init__(self, plan_id: str = "MOCK-PLAN-001") -> None:
        self.plan_id = plan_id


class _MockPlan:
    def __init__(self, plan_id: str = "MOCK-PLAN-001") -> None:
        self.identity = _MockPlanIdentity(plan_id)
        self.phase = MagicMock()
        self.phase.value = "strategize"
        self.state = MagicMock()
        self.state.value = "queued"


def _build_mock_plan_lifecycle_service() -> MagicMock:
    svc = MagicMock()
    svc.use_action.return_value = _MockPlan()
    svc.execute_plan.return_value = _MockPlan()
    svc.get_plan.return_value = _MockPlan()
    svc.apply_plan.return_value = _MockPlan()
    return svc


# ---------------------------------------------------------------------------
# Given steps
# ---------------------------------------------------------------------------


@given(r"a coverage-boost facade with a mock PlanLifecycleService")
def step_cb_facade_plan(context: Context) -> None:
    context.cb_facade = A2aLocalFacade(
        services={"plan_lifecycle_service": _build_mock_plan_lifecycle_service()}
    )


# ---------------------------------------------------------------------------
# When steps
# ---------------------------------------------------------------------------


@when(r"I try to create an A2aLocalFacade with a non-dict services argument")
def step_cb_create_facade_non_dict(context: Context) -> None:
    context.cb_caught_error = None
    try:
        A2aLocalFacade(services=["not", "a", "dict"])  # type: ignore[arg-type]
    except TypeError as exc:
        context.cb_caught_error = exc


@when(
    r'I dispatch coverage-boost operation "(?P<operation>[^"]+)" '
    r"with params (?P<params_json>.+)"
)
def step_cb_dispatch(context: Context, operation: str, params_json: str) -> None:
    params: dict[str, Any] = json.loads(params_json)
    request = A2aRequest(method=operation, params=params)
    context.cb_response = context.cb_facade.dispatch(request)


# ---------------------------------------------------------------------------
# Then steps
# ---------------------------------------------------------------------------


@then(r'a TypeError should be raised with message "(?P<msg>[^"]+)"')
def step_cb_type_error_raised(context: Context, msg: str) -> None:
    assert isinstance(context.cb_caught_error, TypeError), (
        f"Expected TypeError, got {type(context.cb_caught_error)}"
    )
    assert msg in str(context.cb_caught_error), (
        f"Expected message containing '{msg}', got '{context.cb_caught_error}'"
    )


@then(r'the coverage-boost response status should be "(?P<status>[^"]+)"')
def step_cb_response_status(context: Context, status: str) -> None:
    assert (context.cb_response.error is None) == (status == "ok"), (
        f"Expected status '{status}', got error={context.cb_response.error}"
    )


@then(r'the coverage-boost response error code should be "(?P<code>[^"]+)"')
def step_cb_error_code(context: Context, code: str) -> None:
    expected_code: int = A2A_CODE_MAP.get(
        code, int(code) if code.lstrip("-").isdigit() else -1
    )
    assert context.cb_response.error is not None, "No error in response"
    assert context.cb_response.error.code == expected_code, (
        f"Expected error code {expected_code} ('{code}'), "
        f"got '{context.cb_response.error.code}'"
    )


@then(r'the coverage-boost response error message should contain "(?P<fragment>[^"]+)"')
def step_cb_error_message_contains(context: Context, fragment: str) -> None:
    assert context.cb_response.error is not None, "No error in response"
    assert fragment in context.cb_response.error.message, (
        f"Expected message containing '{fragment}', "
        f"got '{context.cb_response.error.message}'"
    )


# Reset step matcher to parse (default) so subsequent step files are not affected
use_step_matcher("parse")
