"""Step definitions for the ``agents plan prompt`` CLI command."""

from __future__ import annotations

import json
from unittest.mock import MagicMock, patch

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

from cleveragents.a2a.facade import A2aLocalFacade
from cleveragents.a2a.models import A2aRequest
from cleveragents.cli.commands.plan import app as plan_app
from cleveragents.core.exceptions import PlanError


@when("I run plan command help")
def step_run_plan_help(context) -> None:
    runner = CliRunner()
    context.prompt_result = runner.invoke(plan_app, ["--help"])


@then("help output should include plan prompt command")
def step_help_includes_prompt(context) -> None:
    assert context.prompt_result.exit_code == 0, context.prompt_result.output
    assert "prompt" in context.prompt_result.output


@given("a mocked lifecycle service prompt response")
def step_mock_lifecycle_prompt_response(context) -> None:
    context.prompt_service_mock = MagicMock()
    context.prompt_service_mock.prompt_plan.return_value = {
        "guidance_added": {
            "plan": "01HXM8C2ZK4Q7C2B3F2R4VYV6J",
            "guidance": "Use mocks for database tests",
            "scope": "next execution step",
            "phase": "execute",
            "state_transition": "errored -> processing",
        },
        "decision_created": {
            "type": "user_intervention",
            "id": "01HXM9C5G7R2X8S3K4Z5Q8R6Y3",
            "parent": "01HXM9A1C2Q7W3R5G8Z0P4Q1X9",
        },
        "queue": {"pending": 1, "applied": 0},
    }


@given("lifecycle service rejects prompt for inactive plan phase")
def step_mock_lifecycle_prompt_reject(context) -> None:
    context.prompt_service_mock = MagicMock()
    context.prompt_service_mock.prompt_plan.side_effect = PlanError(
        "Plan is not in active execute phase"
    )


@when(
    'I run plan prompt with plan id "{plan_id}" and guidance "{guidance}" in format "{fmt}"'
)
def step_run_plan_prompt(context, plan_id: str, guidance: str, fmt: str) -> None:
    runner = CliRunner()
    with patch(
        "cleveragents.cli.commands.plan._get_lifecycle_service",
        return_value=context.prompt_service_mock,
    ):
        context.prompt_result = runner.invoke(
            plan_app,
            ["prompt", plan_id, guidance, "--format", fmt],
        )


@then("the prompt command should succeed")
def step_prompt_success(context) -> None:
    assert context.prompt_result.exit_code == 0, context.prompt_result.output


@then("the prompt command should abort")
def step_prompt_abort(context) -> None:
    assert context.prompt_result.exit_code != 0


@then(
    'lifecycle prompt should be called with plan id "{plan_id}" and guidance "{guidance}"'
)
def step_prompt_called(context, plan_id: str, guidance: str) -> None:
    context.prompt_service_mock.prompt_plan.assert_called_once_with(plan_id, guidance)


def _json_output(result_output: str) -> dict[str, object]:
    return json.loads(result_output.strip())


@then('prompt output envelope should contain command "{command}"')
def step_prompt_envelope_command(context, command: str) -> None:
    payload = _json_output(context.prompt_result.output)
    assert payload.get("command") == command


@then('prompt output envelope should contain status "{status}"')
def step_prompt_envelope_status(context, status: str) -> None:
    payload = _json_output(context.prompt_result.output)
    assert payload.get("status") == status


@then("prompt output data should include queued guidance")
def step_prompt_envelope_data(context) -> None:
    payload = _json_output(context.prompt_result.output)
    data = payload.get("data")
    assert isinstance(data, dict)
    guidance_added = data.get("guidance_added")
    assert isinstance(guidance_added, dict)
    assert guidance_added.get("guidance") == "Use mocks for database tests"
    queue = data.get("queue")
    assert isinstance(queue, dict)
    assert queue.get("pending") == 1


@then('prompt output should include guidance text "{guidance}"')
def step_prompt_output_contains_guidance(context, guidance: str) -> None:
    normalized_output = " ".join(context.prompt_result.output.split())
    normalized_guidance = " ".join(guidance.split())
    assert normalized_guidance in normalized_output


@then("prompt output should mention inactive execute phase")
def step_prompt_output_inactive_phase(context) -> None:
    assert "active execute phase" in context.prompt_result.output.lower()


@given("an A2A facade with a lifecycle prompt service")
def step_facade_with_prompt_service(context) -> None:
    class _PromptLifecycleService:
        def prompt_plan(self, plan_id: str, guidance: str) -> dict[str, object]:
            return {
                "guidance_added": {
                    "plan": plan_id,
                    "guidance": guidance,
                    "scope": "next execution step",
                    "phase": "execute",
                    "state_transition": "errored -> processing",
                },
                "decision_created": {
                    "type": "user_intervention",
                    "id": "01HXM9C5G7R2X8S3K4Z5Q8R6Y3",
                    "parent": "01HXM9A1C2Q7W3R5G8Z0P4Q1X9",
                },
                "queue": {"pending": 1, "applied": 0},
            }

    context.prompt_facade = A2aLocalFacade(
        {"plan_lifecycle_service": _PromptLifecycleService()}
    )


@when(
    'I dispatch _cleveragents plan prompt for plan "{plan_id}" and guidance "{guidance}"'
)
def step_dispatch_facade_prompt(context, plan_id: str, guidance: str) -> None:
    request = A2aRequest(
        method="_cleveragents/plan/prompt",
        params={"plan_id": plan_id, "guidance": guidance},
    )
    context.facade_prompt_response = context.prompt_facade.dispatch(request)


@then("facade prompt response should not be a stub")
def step_facade_prompt_not_stub(context) -> None:
    data = context.facade_prompt_response.result or {}
    assert data.get("stub") is not True


@then('facade prompt response should contain plan id "{plan_id}"')
def step_facade_prompt_plan(context, plan_id: str) -> None:
    data = context.facade_prompt_response.result or {}
    assert data.get("plan_id") == plan_id


@then('facade prompt response should contain guidance "{guidance}"')
def step_facade_prompt_guidance(context, guidance: str) -> None:
    data = context.facade_prompt_response.result or {}
    assert data.get("guidance") == guidance
