"""Step definitions for TDD Issue #592 — actor list validation error.

These steps exercise the *real* ``ActorRegistry._actor_name()`` code path
with a provider whose default model name contains ``/`` characters.  The
``@tdd_expected_fail`` tag on the scenarios inverts the result: these tests
**pass** CI while the bug is present and will **fail** once the bug is fixed
(signalling that the tag should be removed).

Root cause
~~~~~~~~~~
``ActorRegistry._actor_name()`` builds names via
``f"{provider}/{model}"``.  For providers whose default model already
contains ``/`` (e.g. OpenRouter's ``anthropic/claude-sonnet-4-20250514``),
the result has 2+ slashes.  ``ActorService._normalize_name()`` then raises
``ValidationError("Actor names must include exactly one '/' separator")``.
"""

from __future__ import annotations

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

from behave import given, then, when
from typer.testing import CliRunner

from cleveragents.cli.commands.actor import app as actor_app
from features.mocks.fake_provider import FakeProviderInfo, make_registry

runner = CliRunner()

_PATCH_GET_SERVICES = "cleveragents.cli.commands.actor._get_services"


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


@given("an actor registry with a multi-slash model provider for tdd-actor-validation")
def step_multi_slash_provider(context: Any) -> None:
    """Set up a real ``ActorRegistry`` with a multi-slash model provider.

    The provider name is ``Openrouter`` and the default model is
    ``anthropic/claude-sonnet-4-20250514`` — reproducing the exact
    combination that triggers bug #592.
    """
    fake_provider = FakeProviderInfo(
        name="Openrouter",
        default_model="anthropic/claude-sonnet-4-20250514",
    )
    service, registry = make_registry([fake_provider])
    context.tdd_actor_service = service
    context.tdd_actor_registry = registry


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


@when("I run actor list via the tdd-actor-validation CLI")
def step_run_actor_list(context: Any) -> None:
    """Invoke ``actor list`` with the prepared registry.

    Built-in actors are populated via ``ensure_built_in_actors()``
    **before** the list call because ``list_actors()`` is now read-only
    and no longer triggers writes (bug #797 fix).
    """
    context.tdd_actor_registry.ensure_built_in_actors()
    with patch(
        _PATCH_GET_SERVICES,
        return_value=(
            context.tdd_actor_service,
            context.tdd_actor_registry,
        ),
    ):
        context.tdd_actor_result = runner.invoke(actor_app, ["list"])


@when("I run actor list with format json via the tdd-actor-validation CLI")
def step_run_actor_list_json(context: Any) -> None:
    """Invoke ``actor list --format json`` with the prepared registry.

    Built-in actors are populated via ``ensure_built_in_actors()``
    **before** the list call because ``list_actors()`` is now read-only
    and no longer triggers writes (bug #797 fix).
    """
    context.tdd_actor_registry.ensure_built_in_actors()
    with patch(
        _PATCH_GET_SERVICES,
        return_value=(
            context.tdd_actor_service,
            context.tdd_actor_registry,
        ),
    ):
        context.tdd_actor_result = runner.invoke(
            actor_app, ["list", "--format", "json"]
        )


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


@then("the tdd-actor-validation exit code should be {code:d}")
def step_exit_code(context: Any, code: int) -> None:
    result = context.tdd_actor_result
    assert result is not None, "actor list was not invoked"
    actual = result.exit_code
    assert actual == code, (
        f"Expected exit code {code}, got {actual}. Output:\n{result.output}"
    )


@then('the tdd-actor-validation output should not contain "{text}"')
def step_output_not_contains(context: Any, text: str) -> None:
    result = context.tdd_actor_result
    assert result is not None, "actor list was not invoked"
    output = result.output
    assert text.lower() not in output.lower(), (
        f"Did NOT expect '{text}' in output but found it:\n{output}"
    )


@then("the tdd-actor-validation upserted actor name should contain exactly one slash")
def step_upserted_name_single_slash(context: Any) -> None:
    """Verify that every name passed to ``upsert_actor`` has exactly one ``/``.

    With the bug present, ``_actor_name()`` produces names like
    ``Openrouter/anthropic/claude-sonnet-4-20250514`` (2+ slashes), so this
    assertion fails — which the ``@tdd_expected_fail`` tag inverts to a pass.
    """
    service = context.tdd_actor_service
    assert service.upsert_actor.call_count > 0, "upsert_actor was never called"
    for call in service.upsert_actor.call_args_list:
        name = call.kwargs.get("name") or call.args[0]
        slash_count = name.count("/")
        assert slash_count == 1, (
            f"Expected exactly 1 slash in actor name '{name}', found {slash_count}"
        )


@then("the tdd-actor-validation output should be valid JSON")
def step_output_valid_json(context: Any) -> None:
    result = context.tdd_actor_result
    assert result is not None, "actor list was not invoked"
    try:
        json.loads(result.output)
    except json.JSONDecodeError as exc:
        raise AssertionError(f"Output is not valid JSON:\n{result.output}") from exc
