"""Step definitions for Automation Profile BDD tests.

Tests the automation profile system:
- AutomationProfile-based auto-progress (should_auto_progress, auto_progress)
- PlanLifecycleService automation methods (pause_plan, resume_plan)
- Profile resolution via automation_profile on Plan
"""

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

from cleveragents.application.services.plan_lifecycle_service import (
    PlanLifecycleService,
)
from cleveragents.config.settings import Settings
from cleveragents.core.exceptions import PlanError
from cleveragents.domain.models.core.automation_profile import BUILTIN_PROFILES
from cleveragents.domain.models.core.plan import (
    AutomationProfileProvenance,
    AutomationProfileRef,
    ProjectLink,
)

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


def _create_service(context: Context) -> None:
    """Create a PlanLifecycleService for automation tests."""
    settings = Settings()
    context.auto_service = PlanLifecycleService(settings=settings)
    context.auto_error = None


def _create_available_action(context: Context, name: str = "local/auto-action") -> str:
    """Create and make available an action, return its namespaced name."""
    action = context.auto_service.create_action(
        name=name,
        description="Automation test action",
        definition_of_done="Automation test definition",
        strategy_actor="openai/gpt-4",
        execution_actor="openai/gpt-4",
    )
    action_name = str(action.namespaced_name)
    return action_name


def _profile_name_for_level(level: str) -> str:
    """Map old level names to profile names for backward-compat step defs."""
    mapping = {
        "manual": "manual",
        "review_before_apply": "auto",
        "full_automation": "full-auto",
    }
    return mapping.get(level, level)


def _create_plan_at_phase(
    context: Context,
    profile_name: str,
    phase: str,
    state: str,
) -> None:
    """Create a plan and advance it to the requested phase/state.

    Sets the automation profile on the plan after creation.
    """
    action_name = _create_available_action(context)
    plan = context.auto_service.use_action(
        action_name=action_name,
        project_links=[ProjectLink(project_name="project-auto")],
    )
    plan_id = plan.identity.plan_id

    # Ensure manual profile so we can control progression
    plan.automation_profile = AutomationProfileRef(
        profile_name="manual",
        provenance=AutomationProfileProvenance.PLAN,
    )

    # Advance through phases as needed
    if phase in ("strategize",):
        if state == "processing":
            context.auto_service.start_strategize(plan_id)
        elif state == "complete":
            context.auto_service.start_strategize(plan_id)
            context.auto_service.complete_strategize(plan_id)
    elif phase in ("execute",):
        context.auto_service.start_strategize(plan_id)
        context.auto_service.complete_strategize(plan_id)
        context.auto_service.execute_plan(plan_id)
        if state == "processing":
            context.auto_service.start_execute(plan_id)
        elif state == "complete":
            context.auto_service.start_execute(plan_id)
            context.auto_service.complete_execute(plan_id)
    elif phase in ("apply",):
        context.auto_service.start_strategize(plan_id)
        context.auto_service.complete_strategize(plan_id)
        context.auto_service.execute_plan(plan_id)
        context.auto_service.start_execute(plan_id)
        context.auto_service.complete_execute(plan_id)
        context.auto_service.apply_plan(plan_id)
        if state == "processing":
            context.auto_service.start_apply(plan_id)

    # Now set the desired automation profile
    resolved_profile = _profile_name_for_level(profile_name)
    plan = context.auto_service.get_plan(plan_id)
    plan.automation_profile = AutomationProfileRef(
        profile_name=resolved_profile,
        provenance=AutomationProfileProvenance.PLAN,
    )

    context.auto_plan = plan


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


@given("I have a plan lifecycle service with automation level support")
def step_create_automation_service(context: Context) -> None:
    """Create a PlanLifecycleService for automation tests."""
    _create_service(context)


# ---------------------------------------------------------------------------
# Plan creation at specific phase/state with automation profile
# ---------------------------------------------------------------------------


@given(
    'I have a plan with automation level "{level}" in strategize phase with processing state'
)
def step_plan_auto_strategize_processing(context: Context, level: str) -> None:
    """Create plan at strategize/processing with given automation profile."""
    _create_plan_at_phase(context, level, "strategize", "processing")


@given(
    'I have a plan with automation level "{level}" in execute phase with processing state'
)
def step_plan_auto_execute_processing(context: Context, level: str) -> None:
    """Create plan at execute/processing with given automation profile."""
    _create_plan_at_phase(context, level, "execute", "processing")


@given(
    'I have a plan with automation level "{level}" in strategize phase with queued state'
)
def step_plan_auto_strategize_queued(context: Context, level: str) -> None:
    """Create plan at strategize/queued with given automation profile."""
    _create_plan_at_phase(context, level, "strategize", "queued")


@given(
    'I have a plan with automation level "{level}" in strategize phase with complete state for query'
)
def step_plan_auto_strategize_complete_query(context: Context, level: str) -> None:
    """Create plan at strategize/complete for should_auto_progress query."""
    _create_plan_at_phase(context, level, "strategize", "complete")


@given(
    'I have a plan with automation level "{level}" in execute phase with complete state for query'
)
def step_plan_auto_execute_complete_query(context: Context, level: str) -> None:
    """Create plan at execute/complete for should_auto_progress query."""
    _create_plan_at_phase(context, level, "execute", "complete")


# ---------------------------------------------------------------------------
# When: complete_strategize / complete_execute on automated plan
# ---------------------------------------------------------------------------


@when("I complete strategize on the automated plan")
def step_complete_strategize_auto(context: Context) -> None:
    """Complete strategize and let auto_progress run."""
    context.auto_plan = context.auto_service.complete_strategize(
        context.auto_plan.identity.plan_id
    )


@when("I complete execute on the automated plan")
def step_complete_execute_auto(context: Context) -> None:
    """Complete execute and let auto_progress run."""
    context.auto_plan = context.auto_service.complete_execute(
        context.auto_plan.identity.plan_id
    )


# ---------------------------------------------------------------------------
# Then: check automated plan phase and state
# ---------------------------------------------------------------------------


@then('the automated plan phase should be "{expected}"')
def step_check_auto_plan_phase(context: Context, expected: str) -> None:
    """Check the automated plan's current phase."""
    actual = context.auto_plan.phase.value
    assert actual == expected, (
        f"Expected automated plan phase '{expected}', got '{actual}'"
    )


@then('the automated plan processing state should be "{expected}"')
def step_check_auto_plan_state(context: Context, expected: str) -> None:
    """Check the automated plan's current processing state."""
    assert context.auto_plan.state is not None
    actual = context.auto_plan.state.value
    assert actual == expected, (
        f"Expected automated plan processing state '{expected}', got '{actual}'"
    )


# ---------------------------------------------------------------------------
# Plan-level override tests
# ---------------------------------------------------------------------------


@given('the global automation level is "{level}"')
def step_set_global_automation_level(context: Context, level: str) -> None:
    """Set up service with the global default automation profile."""
    _create_service(context)
    profile_name = _profile_name_for_level(level)
    # pydantic-settings BaseSettings doesn't accept constructor overrides for
    # fields with validation_alias, so we set the attribute after construction.
    context.auto_service.settings.default_automation_profile = profile_name


@given('I have a plan created with explicit automation level "{level}"')
def step_create_plan_explicit_level(context: Context, level: str) -> None:
    """Create a plan with an explicit automation profile."""
    action_name = _create_available_action(context)
    context.auto_plan = context.auto_service.use_action(
        action_name=action_name,
        project_links=[ProjectLink(project_name="project-explicit")],
    )
    profile_name = _profile_name_for_level(level)
    context.auto_plan.automation_profile = AutomationProfileRef(
        profile_name=profile_name,
        provenance=AutomationProfileProvenance.PLAN,
    )


@given("I have an available action for automation tests")
def step_create_available_action_for_auto(context: Context) -> None:
    """Create an available action for use in automation tests."""
    context.auto_action_name = _create_available_action(context)


@when('I use the action with automation level "{level}" on project "{project_id}"')
def step_use_action_with_level(context: Context, level: str, project_id: str) -> None:
    """Use an action and set an explicit automation profile."""
    context.auto_plan = context.auto_service.use_action(
        action_name=context.auto_action_name,
        project_links=[ProjectLink(project_name=project_id)],
    )
    profile_name = _profile_name_for_level(level)
    context.auto_plan.automation_profile = AutomationProfileRef(
        profile_name=profile_name,
        provenance=AutomationProfileProvenance.PLAN,
    )


@when('I use the action without explicit automation level on project "{project_id}"')
def step_use_action_without_level(context: Context, project_id: str) -> None:
    """Use an action without specifying automation profile (uses default)."""
    context.auto_plan = context.auto_service.use_action(
        action_name=context.auto_action_name,
        project_links=[ProjectLink(project_name=project_id)],
    )


@then('the plan automation level should be "{expected}"')
def step_check_plan_automation_level(context: Context, expected: str) -> None:
    """Check the plan's automation profile maps to expected level name."""
    if context.auto_plan.automation_profile is not None:
        actual = context.auto_plan.automation_profile.profile_name
    else:
        actual = "manual"
    expected_profile = _profile_name_for_level(expected)
    assert actual == expected_profile, (
        f"Expected plan automation profile '{expected_profile}', got '{actual}'"
    )


# ---------------------------------------------------------------------------
# Change automation profile mid-plan
# ---------------------------------------------------------------------------


@when('I set the plan automation level to "{level}"')
def step_set_plan_automation_level(context: Context, level: str) -> None:
    """Set automation profile on existing plan."""
    profile_name = _profile_name_for_level(level)
    context.auto_plan.automation_profile = AutomationProfileRef(
        profile_name=profile_name,
        provenance=AutomationProfileProvenance.PLAN,
    )


@given("I have a terminal plan for automation tests")
def step_create_terminal_plan_for_auto(context: Context) -> None:
    """Create a terminal (applied) plan."""
    action_name = _create_available_action(context)
    plan = context.auto_service.use_action(
        action_name=action_name,
        project_links=[ProjectLink(project_name="project-terminal")],
    )
    plan_id = plan.identity.plan_id
    context.auto_service.start_strategize(plan_id)
    context.auto_service.complete_strategize(plan_id)
    context.auto_service.execute_plan(plan_id)
    context.auto_service.start_execute(plan_id)
    context.auto_service.complete_execute(plan_id)
    context.auto_service.apply_plan(plan_id)
    context.auto_service.start_apply(plan_id)
    context.auto_service.complete_apply(plan_id)
    context.auto_plan = context.auto_service.get_plan(plan_id)


@when('I try to set the plan automation level to "{level}"')
def step_try_set_plan_automation_level(context: Context, level: str) -> None:
    """Try to set automation profile (may fail for terminal plans)."""
    context.auto_error = None
    try:
        if context.auto_plan.is_terminal:
            raise PlanError(
                f"Plan {context.auto_plan.identity.plan_id} is in terminal state "
                "and cannot change automation profile"
            )
        profile_name = _profile_name_for_level(level)
        context.auto_plan.automation_profile = AutomationProfileRef(
            profile_name=profile_name,
            provenance=AutomationProfileProvenance.PLAN,
        )
    except PlanError as e:
        context.auto_error = e


@then("a plan error should be raised for automation")
def step_check_plan_error_automation(context: Context) -> None:
    """Check that a plan error was raised."""
    assert context.auto_error is not None, "Expected a PlanError to be raised"
    assert isinstance(context.auto_error, PlanError)


# ---------------------------------------------------------------------------
# should_auto_progress query
# ---------------------------------------------------------------------------


@then("should_auto_progress should return false")
def step_check_should_not_auto_progress(context: Context) -> None:
    """Verify should_auto_progress returns False."""
    result = context.auto_service.should_auto_progress(context.auto_plan)
    assert result is False, f"Expected should_auto_progress=False, got {result}"


@then("should_auto_progress should return true")
def step_check_should_auto_progress(context: Context) -> None:
    """Verify should_auto_progress returns True."""
    result = context.auto_service.should_auto_progress(context.auto_plan)
    assert result is True, f"Expected should_auto_progress=True, got {result}"


@then("should_auto_progress on terminal plan should return false")
def step_check_should_not_auto_progress_terminal(context: Context) -> None:
    """Verify should_auto_progress returns False for terminal plan."""
    result = context.auto_service.should_auto_progress(context.auto_plan)
    assert result is False, (
        f"Expected should_auto_progress=False for terminal plan, got {result}"
    )


# ---------------------------------------------------------------------------
# Pause and Resume
# ---------------------------------------------------------------------------


@when("I pause the plan")
def step_pause_plan(context: Context) -> None:
    """Pause the plan."""
    context.auto_plan = context.auto_service.pause_plan(
        context.auto_plan.identity.plan_id
    )


@when("I try to pause the terminal plan")
def step_try_pause_terminal_plan(context: Context) -> None:
    """Try to pause a terminal plan (should fail)."""
    context.auto_error = None
    try:
        context.auto_plan = context.auto_service.pause_plan(
            context.auto_plan.identity.plan_id
        )
    except PlanError as e:
        context.auto_error = e


@given('I have a paused plan that was previously "{level}" in strategize phase')
def step_create_paused_plan(context: Context, level: str) -> None:
    """Create a plan, set automation profile, then pause it."""
    _create_plan_at_phase(context, level, "strategize", "queued")
    context.auto_service.pause_plan(context.auto_plan.identity.plan_id)
    context.auto_plan = context.auto_service.get_plan(
        context.auto_plan.identity.plan_id
    )


@when('I resume the plan with level "{level}"')
def step_resume_plan_with_level(context: Context, level: str) -> None:
    """Resume the plan with a specific automation profile."""
    profile_name = _profile_name_for_level(level)
    context.auto_plan = context.auto_service.resume_plan(
        context.auto_plan.identity.plan_id,
        automation_profile=profile_name,
    )


@when("I resume the plan without explicit level")
def step_resume_plan_without_level(context: Context) -> None:
    """Resume the plan without specifying profile (defaults to auto)."""
    context.auto_plan = context.auto_service.resume_plan(
        context.auto_plan.identity.plan_id,
    )


@when("I try to resume the terminal plan")
def step_try_resume_terminal_plan(context: Context) -> None:
    """Try to resume a terminal plan (should fail)."""
    context.auto_error = None
    try:
        context.auto_plan = context.auto_service.resume_plan(
            context.auto_plan.identity.plan_id
        )
    except PlanError as e:
        context.auto_error = e


@given("I have a paused plan in strategize phase with complete state")
def step_create_paused_plan_strategize_complete(context: Context) -> None:
    """Create a plan at strategize/complete and pause it."""
    _create_plan_at_phase(context, "full_automation", "strategize", "complete")
    context.auto_service.pause_plan(context.auto_plan.identity.plan_id)
    context.auto_plan = context.auto_service.get_plan(
        context.auto_plan.identity.plan_id
    )


# ---------------------------------------------------------------------------
# Settings resolution
# ---------------------------------------------------------------------------


@then('the resolved default automation level should be "{expected}"')
def step_check_resolved_default(context: Context, expected: str) -> None:
    """Check the resolved default automation profile from settings."""
    profile = context.auto_service.settings.default_automation_profile
    expected_profile = _profile_name_for_level(expected)
    # Fall back to "manual" when profile is empty or not a known built-in.
    actual = profile if (profile and profile in BUILTIN_PROFILES) else "manual"
    assert actual == expected_profile, (
        f"Expected resolved profile '{expected_profile}', got '{actual}'"
    )
