"""Step definitions for plan_lifecycle_service_persistence_coverage.feature.

Targets uncovered persistence-fallback lines in PlanLifecycleService:
- Lines 340-341: get_action() persistence fallback when action not in memory
- Lines 376-380: get_action_by_name() persistence fallback when action not
  in memory and linear scan also misses
"""

from __future__ import annotations

from contextlib import contextmanager
from unittest.mock import MagicMock

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

from cleveragents.application.services.plan_lifecycle_service import (
    PlanLifecycleService,
)
from cleveragents.domain.models.core.action import Action, ActionState
from cleveragents.domain.models.core.plan import NamespacedName


def _make_action(name: str) -> Action:
    """Create a real Action domain object with the given namespaced name."""
    return Action(
        namespaced_name=NamespacedName.parse(name),
        description=f"Persisted action {name}",
        definition_of_done="All tests pass",
        strategy_actor="openai/gpt-4",
        execution_actor="openai/gpt-4",
        state=ActionState.AVAILABLE,
    )


def _build_mock_uow(action_by_name_map: dict[str, Action | None]) -> MagicMock:
    """Build a mock UnitOfWork whose transaction context returns actions.

    ``action_by_name_map`` maps action name strings to Action objects
    (or None for not-found).  The mock ctx.actions.get_by_name(name) will
    look up the name in this map and return the corresponding value.
    """
    mock_uow = MagicMock()
    mock_ctx = MagicMock()
    mock_ctx.actions.get_by_name.side_effect = lambda n: action_by_name_map.get(n)

    @contextmanager
    def _transaction():
        yield mock_ctx

    mock_uow.transaction = _transaction
    return mock_uow


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


@given("a plan lifecycle service with a mock unit of work")
def step_create_service_with_mock_uow(context: Context) -> None:
    """Create a PlanLifecycleService backed by a mock UnitOfWork."""
    settings = MagicMock()
    # Start with an empty action map; scenarios will populate it.
    context.action_by_name_map: dict[str, Action | None] = {}
    mock_uow = _build_mock_uow(context.action_by_name_map)
    context.service = PlanLifecycleService(settings=settings, unit_of_work=mock_uow)
    context.result_action = None


# -----------------------------------------------------------------
# get_action persistence fallback (lines 340-341)
# -----------------------------------------------------------------


@given('an action "{name}" exists only in the persistence layer')
def step_action_in_persistence_only(context: Context, name: str) -> None:
    """Add an action to the mock persistence layer but NOT to in-memory cache."""
    action = _make_action(name)
    # Add to the mock UoW lookup map so ctx.actions.get_by_name finds it
    context.action_by_name_map[name] = action
    # Ensure it is NOT in the in-memory cache
    context.service._actions.pop(name, None)


@when('I call get_action with "{name}"')
def step_call_get_action(context: Context, name: str) -> None:
    """Call service.get_action() which should fall back to persistence."""
    context.result_action = context.service.get_action(name)


@then('the returned action should have namespaced name "{name}"')
def step_check_returned_action_name(context: Context, name: str) -> None:
    """Verify the returned action has the expected namespaced name."""
    assert context.result_action is not None, "Expected an action, got None"
    assert str(context.result_action.namespaced_name) == name, (
        f"Expected namespaced name '{name}', "
        f"got '{context.result_action.namespaced_name}'"
    )


@then('the action "{name}" should now be cached in memory')
def step_check_action_cached(context: Context, name: str) -> None:
    """Verify the action was added to the in-memory _actions cache."""
    assert name in context.service._actions, (
        f"Expected '{name}' in _actions cache, "
        f"but found keys: {list(context.service._actions.keys())}"
    )
    assert str(context.service._actions[name].namespaced_name) == name


# -----------------------------------------------------------------
# get_action_by_name persistence fallback (lines 376-380)
# -----------------------------------------------------------------


@given('an action "{name}" exists only in the persistence layer for name lookup')
def step_action_in_persistence_for_name_lookup(context: Context, name: str) -> None:
    """Add an action to the mock persistence layer for get_action_by_name.

    The action must not be in the in-memory _actions dict at all, so that
    both the direct dict lookup AND the linear scan miss, forcing the
    persistence fallback on lines 376-380.
    """
    action = _make_action(name)
    # Add to the mock UoW lookup map
    context.action_by_name_map[name] = action
    # Ensure it is NOT in the in-memory cache
    context.service._actions.pop(name, None)


@when('I call get_action_by_name with "{name}"')
def step_call_get_action_by_name(context: Context, name: str) -> None:
    """Call service.get_action_by_name() which should fall back to persistence."""
    context.result_action = context.service.get_action_by_name(name)


@then('the returned action from name lookup should have namespaced name "{name}"')
def step_check_returned_action_by_name(context: Context, name: str) -> None:
    """Verify the returned action has the expected namespaced name."""
    assert context.result_action is not None, "Expected an action, got None"
    assert str(context.result_action.namespaced_name) == name, (
        f"Expected namespaced name '{name}', "
        f"got '{context.result_action.namespaced_name}'"
    )


@then('the action "{name}" should now be cached in memory after name lookup')
def step_check_action_cached_after_name_lookup(context: Context, name: str) -> None:
    """Verify the action was added to the in-memory _actions cache."""
    assert name in context.service._actions, (
        f"Expected '{name}' in _actions cache, "
        f"but found keys: {list(context.service._actions.keys())}"
    )
    assert str(context.service._actions[name].namespaced_name) == name
