"""Step definitions for decision_di_wiring.feature.

Covers DI container wiring of DecisionService and its integration with
PlanLifecycleService for automatic decision recording.

All step text uses the ``decdi-`` prefix to avoid collisions with
existing step definitions.
"""

from __future__ import annotations

import os
from typing import TYPE_CHECKING
from unittest.mock import create_autospec

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

if TYPE_CHECKING:
    from cleveragents.infrastructure.database.unit_of_work import UnitOfWork

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


def _make_test_uow() -> UnitOfWork:
    """Create a UnitOfWork backed by an in-memory SQLite for testing.

    Uses ``UnitOfWork.__init__`` + ``init_database()`` rather than the
    ``__new__()`` anti-pattern to stay in sync with UoW internals.
    """
    from cleveragents.infrastructure.database.unit_of_work import UnitOfWork

    uow = UnitOfWork("sqlite:///:memory:")
    uow.init_database()
    return uow


def _ensure_test_db_env(context: Context) -> None:
    """Set up a test database URL if not already set."""
    if not hasattr(context, "_decdi_env_saved"):
        context._decdi_env_saved = os.environ.get("CLEVERAGENTS_DATABASE_URL")
    os.environ["CLEVERAGENTS_DATABASE_URL"] = "sqlite:///:memory:"


def _restore_env(context: Context) -> None:
    """Restore environment variables."""
    saved = getattr(context, "_decdi_env_saved", None)
    if saved is None:
        os.environ.pop("CLEVERAGENTS_DATABASE_URL", None)
    else:
        os.environ["CLEVERAGENTS_DATABASE_URL"] = saved


def _cleanup_container(context: Context) -> None:
    """Reset the global container after the scenario."""
    from cleveragents.application.container import reset_container

    reset_container()
    _restore_env(context)


# -------------------------------------------------------------------
# Scenario: DI container resolves DecisionService
# -------------------------------------------------------------------


@given("decdi- the application container is initialized")
def step_init_container(context: Context) -> None:
    from cleveragents.application.container import get_container, reset_container

    _ensure_test_db_env(context)
    reset_container()
    context.container = get_container()
    context.add_cleanup(_cleanup_container, context)


@when("decdi- I request a DecisionService from the container")
def step_request_decision_service(context: Context) -> None:
    context.decision_service = context.container.decision_service()


@then("decdi- the container should return a valid DecisionService instance")
def step_check_decision_service(context: Context) -> None:
    from cleveragents.application.services.decision_service import DecisionService

    assert isinstance(context.decision_service, DecisionService), (
        f"Expected DecisionService, got {type(context.decision_service).__name__}"
    )


# -------------------------------------------------------------------
# Scenario: DecisionService records decision during plan strategize
# -------------------------------------------------------------------


@given("decdi- a PlanLifecycleService with a DecisionService")
def step_create_lifecycle_with_decision(context: Context) -> None:
    """Build an in-memory PlanLifecycleService wired with DecisionService."""
    from cleveragents.application.services.decision_service import DecisionService
    from cleveragents.application.services.plan_lifecycle_service import (
        PlanLifecycleService,
    )
    from cleveragents.config.settings import Settings

    uow = _make_test_uow()

    mock_settings = create_autospec(Settings, instance=True)
    mock_settings.database_url = "sqlite:///:memory:"
    mock_settings.async_enabled = False

    decision_svc = DecisionService(settings=mock_settings, unit_of_work=uow)
    lifecycle_svc = PlanLifecycleService(
        settings=mock_settings,
        unit_of_work=uow,
        decision_service=decision_svc,
    )

    context.decision_svc = decision_svc
    context.lifecycle_svc = lifecycle_svc
    context.uow = uow


@given("decdi- an action and plan exist in strategize phase")
def step_create_action_and_plan_strategize(context: Context) -> None:
    context.action = context.lifecycle_svc.create_action(
        name="local/decdi-strategize-test",
        description="Test action for strategize",
        definition_of_done="Done",
        strategy_actor="local/s",
        execution_actor="local/e",
    )
    context.plan = context.lifecycle_svc.use_action("local/decdi-strategize-test")
    context.plan_id = context.plan.identity.plan_id


@when("decdi- I start the strategize phase")
def step_start_strategize(context: Context) -> None:
    context.lifecycle_svc.start_strategize(context.plan_id)


@then("decdi- a strategy_choice decision should be recorded for the plan")
def step_check_strategize_decision(context: Context) -> None:
    decisions = context.decision_svc.list_decisions(context.plan_id)
    strategy_decisions = [
        d for d in decisions if str(d.decision_type) == "strategy_choice"
    ]
    assert len(strategy_decisions) == 1, (
        f"Expected exactly 1 strategy_choice decision, got {len(strategy_decisions)}"
    )


# -------------------------------------------------------------------
# Scenario: DecisionService records decision during plan execute
# -------------------------------------------------------------------


@given("decdi- an action and plan exist in execute phase")
def step_create_action_and_plan_execute(context: Context) -> None:
    context.lifecycle_svc.create_action(
        name="local/decdi-execute-test",
        description="Test action for execute",
        definition_of_done="Done",
        strategy_actor="local/s",
        execution_actor="local/e",
    )
    plan = context.lifecycle_svc.use_action("local/decdi-execute-test")
    plan_id = plan.identity.plan_id

    # Progress to execute phase: start -> complete strategize -> execute_plan
    context.lifecycle_svc.start_strategize(plan_id)
    context.lifecycle_svc.complete_strategize(plan_id)

    # After complete_strategize, auto_progress may transition to execute
    # depending on the automation profile.  Fetch the latest state.
    plan = context.lifecycle_svc.get_plan(plan_id)
    from cleveragents.domain.models.core.plan import PlanPhase

    if plan.phase == PlanPhase.STRATEGIZE:
        context.lifecycle_svc.execute_plan(plan_id)

    context.plan = context.lifecycle_svc.get_plan(plan_id)
    context.plan_id = plan_id


@when("decdi- I start the execute phase")
def step_start_execute(context: Context) -> None:
    context.lifecycle_svc.start_execute(context.plan_id)


@then("decdi- an implementation_choice decision should be recorded for the plan")
def step_check_execute_decision(context: Context) -> None:
    decisions = context.decision_svc.list_decisions(context.plan_id)
    impl_decisions = [
        d for d in decisions if str(d.decision_type) == "implementation_choice"
    ]
    assert len(impl_decisions) == 1, (
        f"Expected exactly 1 implementation_choice decision, got {len(impl_decisions)}"
    )


# -------------------------------------------------------------------
# Scenario: DecisionRepository is accessible via DI
# -------------------------------------------------------------------


@when("decdi- I open a UnitOfWork transaction from the container")
def step_open_uow_transaction(context: Context) -> None:
    uow = context.container.unit_of_work()
    uow.init_database()
    with uow.transaction() as ctx:
        context.uow_ctx = ctx
        context.has_decisions = hasattr(ctx, "decisions")
        context.decisions_type_name = type(ctx.decisions).__name__


@then("decdi- the transaction context should expose a DecisionRepository")
def step_check_decision_repo(context: Context) -> None:
    assert context.has_decisions, (
        "UnitOfWorkContext does not have 'decisions' attribute"
    )
    assert context.decisions_type_name == "DecisionRepository", (
        f"Expected DecisionRepository, got {context.decisions_type_name}"
    )


# -------------------------------------------------------------------
# Scenario: DecisionService scoping is correct
# -------------------------------------------------------------------


@when("decdi- I request two DecisionService instances from the container")
def step_request_two_instances(context: Context) -> None:
    context.svc_a = context.container.decision_service()
    context.svc_b = context.container.decision_service()


@then("decdi- the instances should be different objects because it is a Factory")
def step_check_different_instances(context: Context) -> None:
    assert context.svc_a is not context.svc_b, (
        "Factory provider should return different instances per call"
    )
