"""Step definitions for tdd_auto_debug_node_contracts.feature.

These tests verify that AutoDebugAgent._generate_fix(), _validate_fix(), and
_finalize() each return a plain dict of state updates (only the changed keys),
not the full state object, satisfying the LangGraph node contract.
"""

from __future__ import annotations

from typing import Any
from unittest.mock import MagicMock

from behave import given, then, when

from cleveragents.agents.graphs.auto_debug import AutoDebugAgent, AutoDebugState


def _make_agent(content: str) -> AutoDebugAgent:
    llm = MagicMock()
    llm.invoke.return_value = MagicMock(content=content)
    return AutoDebugAgent(llm=llm)


def _base_state() -> AutoDebugState:
    return {
        "error_message": "RuntimeError at line 10",
        "code_context": "x = undefined_func()",
        "messages": [],
        "context": {},
        "result": None,
        "error": None,
        "metadata": {},
        "attempted_fixes": [],
        "current_fix": {},
        "fix_validated": False,
    }


@given("an AutoDebugAgent with a mock LLM for the node contract test")
def step_create_agent_mock(context: Any) -> None:
    context.nc_agent = _make_agent("Mock LLM response")


@given("an AutoDebugAgent with a mock LLM returning valid for the node contract test")
def step_create_agent_valid(context: Any) -> None:
    context.nc_agent = _make_agent("Mock LLM response")


@given("an AutoDebugAgent with a mock LLM returning invalid for the node contract test")
def step_create_agent_invalid(context: Any) -> None:
    context.nc_agent = _make_agent('{"is_valid": false}')


@given("a state with an error_analysis message for the node contract test")
def step_state_with_error_analysis(context: Any) -> None:
    state = _base_state()
    state["messages"] = [
        {"role": "assistant", "content": "analysis", "type": "error_analysis"}
    ]
    context.nc_state = state


@given("a state with a current_fix for the node contract test")
def step_state_with_current_fix(context: Any) -> None:
    state = _base_state()
    state["current_fix"] = {
        "description": "Replace undefined_func with func",
        "code": "x = func()",
        "files_to_modify": [],
    }
    context.nc_state = state


@given("a state with fix_validated True and a current_fix for the finalize test")
def step_state_for_finalize(context: Any) -> None:
    state = _base_state()
    state["current_fix"] = {
        "description": "Replace undefined_func with func",
        "code": "x = func()",
        "files_to_modify": [],
    }
    state["fix_validated"] = True
    context.nc_state = state


@when("_generate_fix is called with the state for the node contract test")
def step_call_generate_fix(context: Any) -> None:
    context.nc_result = context.nc_agent._generate_fix(context.nc_state)


@when("_validate_fix is called with the state for the node contract test")
def step_call_validate_fix(context: Any) -> None:
    context.nc_result = context.nc_agent._validate_fix(context.nc_state)


@when("_finalize is called with the state for the node contract test")
def step_call_finalize(context: Any) -> None:
    context.nc_result = context.nc_agent._finalize(context.nc_state)


@then("the generate_fix result should be a plain dict of updates")
def step_generate_fix_is_dict(context: Any) -> None:
    assert isinstance(context.nc_result, dict), f"got {type(context.nc_result)}"


@then("the generate_fix result should contain only the current_fix key")
def step_generate_fix_only_current_fix(context: Any) -> None:
    keys = set(context.nc_result.keys())
    assert keys == {"current_fix"}, f"expected {{'current_fix'}}, got {keys}"


@then("the generate_fix result should not be the same object as the input state")
def step_generate_fix_not_same_object(context: Any) -> None:
    assert context.nc_result is not context.nc_state


@then("the validate_fix result should be a plain dict of updates")
def step_validate_fix_is_dict(context: Any) -> None:
    assert isinstance(context.nc_result, dict), f"got {type(context.nc_result)}"


@then("the validate_fix result should contain fix_validated set to True")
def step_validate_fix_is_true(context: Any) -> None:
    val = context.nc_result.get("fix_validated")
    assert val is True, f"expected True, got {val!r}"


@then("the validate_fix result should not be the same object as the input state")
def step_validate_fix_not_same_object(context: Any) -> None:
    assert context.nc_result is not context.nc_state


@then("the validate_fix result should contain fix_validated set to False")
def step_validate_fix_is_false(context: Any) -> None:
    val = context.nc_result.get("fix_validated")
    assert val is False, f"expected False, got {val!r}"


@then("the validate_fix result should contain the attempted_fixes key")
def step_validate_fix_has_attempted_fixes(context: Any) -> None:
    keys = set(context.nc_result.keys())
    assert "attempted_fixes" in keys, f"'attempted_fixes' not in {keys}"


@then("the finalize result should be a plain dict of updates")
def step_finalize_is_dict(context: Any) -> None:
    assert isinstance(context.nc_result, dict), f"got {type(context.nc_result)}"


@then("the finalize result should contain only the result key")
def step_finalize_only_result(context: Any) -> None:
    keys = set(context.nc_result.keys())
    assert keys == {"result"}, f"expected {{'result'}}, got {keys}"


@then("the finalize result should not be the same object as the input state")
def step_finalize_not_same_object(context: Any) -> None:
    assert context.nc_result is not context.nc_state
