"""Step definitions for TDD Issue #1076 — use_action automation_profile propagation.

These steps exercise ``PlanLifecycleService.use_action()`` and verify that
it resolves the automation profile using the spec's precedence chain
(plan > action > project > global) and sets the resolved profile as an
``AutomationProfileRef`` on the created Plan.

On ``master`` (before the fix), ``use_action()`` constructs the ``Plan()``
without passing ``automation_profile`` to the constructor.  The Plan's
``automation_profile`` field is always ``None`` regardless of the Action's
``automation_profile`` value or any other configuration source.

The assertions in these steps will **fail** until the bug is fixed,
proving the bug exists.  The ``@tdd_expected_fail`` tag inverts the
result so CI passes.

Bug #1076 — captures the test for automation_profile propagation from
the Action to the Plan via use_action(). Uses @tdd_expected_fail until
the fix in #1076 is merged.
"""

from __future__ import annotations

import tempfile
from pathlib import Path
from typing import Any

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

from cleveragents.application.services.config_service import (
    ConfigService,
    ResolvedValue,
)
from cleveragents.application.services.plan_lifecycle_service import (
    PlanLifecycleService,
)
from cleveragents.config.settings import Settings
from cleveragents.domain.models.core.plan import (
    AutomationProfileProvenance,
    Plan,
    ProjectLink,
)

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


def _use_action_on_project(context: Context, project: str) -> None:
    """Use the current action on *project* and store the resulting plan.

    Shared by both profiled- and unprofiled-action When steps so that
    the ``use_action`` call-site is defined in exactly one place.
    """
    links: list[ProjectLink] = [ProjectLink(project_name=project)]
    plan: Plan = context.ap_lifecycle_service.use_action(
        action_name=str(context.ap_action.namespaced_name),
        project_links=links,
    )
    context.ap_plan = plan


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


class _BrokenConfigService(ConfigService):
    """A ConfigService subclass that always raises on resolve().

    Used by the fallback-on-error scenario to verify that the exception
    handler in ``_resolve_plan_profile_ref`` catches expected errors
    and falls back to the settings default gracefully.
    """

    def resolve(
        self,
        key: str,
        *,
        cli_value: Any | None = None,
        project_name: str | None = None,
        verbose: bool = False,
    ) -> ResolvedValue:
        raise ValueError(f"Simulated config resolution failure for key '{key}'")


@given("a plan lifecycle service with a broken config service")
def step_create_lifecycle_service_broken_config(context: Context) -> None:
    """Create a PlanLifecycleService with a config service that always errors."""
    tmpdir: str = tempfile.mkdtemp()
    tmp_path: Path = Path(tmpdir)
    context.ap_config_tmpdir = tmpdir
    broken_config = _BrokenConfigService(
        config_dir=tmp_path,
        config_path=tmp_path / "config.toml",
    )
    settings: Settings = Settings()
    context.ap_lifecycle_service = PlanLifecycleService(
        settings=settings,
        config_service=broken_config,
    )


@given("a plan lifecycle service for automation profile testing")
def step_create_lifecycle_service(context: Context) -> None:
    """Create a PlanLifecycleService instance for automation profile tests."""
    tmpdir: str = tempfile.mkdtemp()
    tmp_path: Path = Path(tmpdir)
    context.ap_config_tmpdir = tmpdir
    context.ap_config_service = ConfigService(
        config_dir=tmp_path,
        config_path=tmp_path / "config.toml",
    )
    settings: Settings = Settings()
    context.ap_lifecycle_service = PlanLifecycleService(
        settings=settings,
        config_service=context.ap_config_service,
    )


@given('an available action "{name}" with automation_profile "{profile}"')
def step_create_action_with_profile(context: Context, name: str, profile: str) -> None:
    """Create an available action with a specified automation_profile."""
    context.ap_action = context.ap_lifecycle_service.create_action(
        name=name,
        description=f"Test action {name} with automation profile",
        definition_of_done="Test definition of done",
        strategy_actor="openai/gpt-4",
        execution_actor="openai/gpt-4",
        automation_profile=profile,
    )
    # Guard: verify the Action itself stored the profile (not silently dropped)
    assert context.ap_action.automation_profile == profile, (
        f"Action.automation_profile is {context.ap_action.automation_profile!r}, "
        f"expected {profile!r}. create_action() did not persist the profile "
        f"on the Action model."
    )


@given('an available action "{name}" without automation_profile')
def step_create_action_without_profile(context: Context, name: str) -> None:
    """Create an available action without an automation_profile."""
    context.ap_action = context.ap_lifecycle_service.create_action(
        name=name,
        description=f"Test action {name} without automation profile",
        definition_of_done="Test definition of done",
        strategy_actor="openai/gpt-4",
        execution_actor="openai/gpt-4",
    )


@given('a project-scoped automation_profile "{profile}" for project "{project}"')
def step_set_project_scoped_profile(
    context: Context, profile: str, project: str
) -> None:
    """Configure a project-scoped automation_profile via ConfigService.

    This sets ``core.automation-profile`` at the project level so that
    the precedence chain (plan > action > **project** > global) has a
    value at level 3.  ``use_action()`` should consult this when the
    action itself has no automation_profile.
    """
    context.ap_config_service.set_project_value(
        project, "core.automation-profile", profile
    )


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


@when('I use the profiled action on project "{project}"')
def step_use_profiled_action(context: Context, project: str) -> None:
    """Use the profiled action on a project via use_action()."""
    _use_action_on_project(context, project)


@when('I use the action with plan-level profile "{profile}" on project "{project}"')
def step_use_action_with_plan_profile(
    context: Context, profile: str, project: str
) -> None:
    """Use the action with an explicit plan-level profile override."""
    links: list[ProjectLink] = [ProjectLink(project_name=project)]
    plan: Plan = context.ap_lifecycle_service.use_action(
        action_name=str(context.ap_action.namespaced_name),
        project_links=links,
        automation_profile=profile,
    )
    context.ap_plan = plan


@when('I use the unprofiled action on project "{project}"')
def step_use_unprofiled_action(context: Context, project: str) -> None:
    """Use the unprofiled action on a project via use_action()."""
    _use_action_on_project(context, project)


@when('I use the unprofiled action on configured project "{project}"')
def step_use_unprofiled_action_on_configured_project(
    context: Context, project: str
) -> None:
    """Use the unprofiled action on a project that has project-scoped config."""
    _use_action_on_project(context, project)


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


@then("the created plan automation_profile should not be None")
def step_plan_profile_not_none(context: Context) -> None:
    """Assert the plan's automation_profile is set.

    Bug #1076: ``use_action()`` does not pass ``automation_profile`` to
    the ``Plan()`` constructor.  The resulting ``plan.automation_profile``
    is always ``None`` regardless of the Action's ``automation_profile``
    value or any other configuration source in the precedence chain
    (plan > action > project > global).
    """
    plan: Plan = context.ap_plan
    assert plan.automation_profile is not None, (
        "Plan automation_profile is None. "
        "PlanLifecycleService.use_action() does not resolve or "
        "propagate the automation profile from the precedence chain "
        "(plan > action > project > global) to the created Plan "
        "(bug #1076). Expected an AutomationProfileRef, got None."
    )


@then('the created plan automation_profile name should be "{expected}"')
def step_plan_profile_name(context: Context, expected: str) -> None:
    """Assert the plan's automation_profile has the expected profile name.

    Bug #1076: Since automation_profile is always None, the profile name
    is never set.
    """
    plan: Plan = context.ap_plan
    assert plan.automation_profile is not None, (
        "Plan automation_profile is None — cannot verify profile name. "
        "use_action() does not propagate automation_profile (bug #1076)."
    )
    actual: str = plan.automation_profile.profile_name
    assert actual == expected, (
        f"Plan automation_profile.profile_name is '{actual}', "
        f"expected '{expected}'. The precedence resolution in "
        f"use_action() did not resolve the correct profile (bug #1076)."
    )


@then('the created plan automation_profile provenance should be "{expected}"')
def step_plan_profile_provenance(context: Context, expected: str) -> None:
    """Assert the plan's automation_profile has the expected provenance.

    Bug #1076: Since automation_profile is always None, the provenance
    source is never set.
    """
    plan: Plan = context.ap_plan
    assert plan.automation_profile is not None, (
        "Plan automation_profile is None — cannot verify provenance. "
        "use_action() does not propagate automation_profile (bug #1076)."
    )
    expected_provenance: AutomationProfileProvenance = AutomationProfileProvenance(
        expected
    )
    actual: AutomationProfileProvenance = plan.automation_profile.provenance
    assert actual == expected_provenance, (
        f"Plan automation_profile.provenance is '{actual.value}', "
        f"expected '{expected_provenance.value}'. The precedence "
        f"resolution in use_action() did not record the correct "
        f"provenance source (bug #1076)."
    )
