"""
Behave step definitions to cover remaining branches in langgraph/state.py.
"""

import json
import tempfile
from pathlib import Path

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

from cleveragents.langgraph.state import GraphState, StateManager, StateUpdateMode


def _ensure(context: Context) -> None:
    context.state_instances = getattr(context, "state_instances", {}) or {}
    context.state_managers = getattr(context, "state_managers", {}) or {}
    context.test_data = getattr(context, "test_data", {}) or {}
    context.results = getattr(context, "results", {}) or {}


@given('I have a GraphState with metadata {metadata_json} and current node "{node}"')
def step_graphstate_replace(context: Context, metadata_json: str, node: str):
    _ensure(context)
    metadata = json.loads(metadata_json)
    context.state_instances["replace_state"] = GraphState(
        metadata=metadata, current_node=node
    )


@when(
    'I replace the state with updates containing metadata {metadata_json} and current node "{node}"'
)
def step_replace_updates(context: Context, metadata_json: str, node: str):
    _ensure(context)
    state = context.state_instances["replace_state"]
    updates = {"metadata": json.loads(metadata_json), "current_node": node}
    state.update(updates, StateUpdateMode.REPLACE)


@then("the state's metadata should equal {metadata_json}")
def step_verify_metadata_equals(context: Context, metadata_json: str):
    state = context.state_instances.get("replace_state")
    assert state is not None
    expected = json.loads(metadata_json)
    assert state.metadata == expected


@then('the state\'s current node should equal "{node}"')
def step_verify_current_node_equals(context: Context, node: str):
    state = context.state_instances.get("replace_state")
    assert state is not None
    assert state.current_node == node


@given("I have a GraphState with metadata {metadata_json}")
def step_graphstate_metadata(context: Context, metadata_json: str):
    _ensure(context)
    context.state_instances["merge_meta"] = GraphState(
        metadata=json.loads(metadata_json)
    )


@given("I have merge metadata updates {metadata_json}")
def step_merge_metadata_updates(context: Context, metadata_json: str):
    _ensure(context)
    context.test_data["merge_metadata"] = json.loads(metadata_json)


@when("I merge metadata updates into the state")
def step_merge_metadata(context: Context):
    _ensure(context)
    state = context.state_instances["merge_meta"]
    state.update(
        {"metadata": context.test_data["merge_metadata"]}, StateUpdateMode.MERGE
    )


@then('the state\'s metadata should contain keys "{key1}" and "{key2}"')
def step_verify_metadata_keys(context: Context, key1: str, key2: str):
    state = context.state_instances.get("merge_meta")
    assert state is not None
    assert key1 in state.metadata
    assert key2 in state.metadata


@given("I have a GraphState with 1 message")
def step_graphstate_one_message(context: Context):
    _ensure(context)
    context.state_instances["append_single"] = GraphState(messages=[{"id": 0}])


@when("I append a single message with id {message_id:d} into the state")
def step_append_single_message(context: Context, message_id: int):
    _ensure(context)
    state = context.state_instances["append_single"]
    state.update({"messages": {"id": message_id}}, StateUpdateMode.APPEND)


@then("the state's messages should end with id {message_id:d}")
def step_verify_messages_end(context: Context, message_id: int):
    state = context.state_instances.get("append_single")
    assert state is not None
    assert state.messages[-1]["id"] == message_id


@then("the message list length should be 2")
def step_verify_message_length_two(context: Context):
    state = context.state_instances.get("append_single")
    assert state is not None
    assert len(state.messages) == 2


@given("I have a StateManager without checkpointing")
def step_manager_without_checkpoint(context: Context):
    _ensure(context)
    context.state_managers["no_checkpoint"] = StateManager()


@when("I invoke checkpoint saving manually")
def step_invoke_save_checkpoint(context: Context):
    manager = context.state_managers["no_checkpoint"]
    manager._save_checkpoint()


@then("the update count should remain at 0")
def step_verify_update_count_zero(context: Context):
    manager = context.state_managers["no_checkpoint"]
    assert manager.update_count == 0


@given(
    "I have a checkpoint file with execution count {count:d} and metadata {metadata_json}"
)
def step_prepare_checkpoint_file(context: Context, count: int, metadata_json: str):
    _ensure(context)
    checkpoint_dir = Path(tempfile.mkdtemp())
    checkpoint_file = checkpoint_dir / "checkpoint_test.json"
    data = {
        "state": GraphState(metadata=json.loads(metadata_json)).to_dict(),
        "timestamp": "test",
        "update_count": count,
    }
    checkpoint_file.write_text(json.dumps(data), encoding="utf-8")
    context.test_data["checkpoint_file"] = checkpoint_file


@when("I load the checkpoint into a StateManager")
def step_load_checkpoint(context: Context):
    manager = StateManager()
    checkpoint_file = context.test_data["checkpoint_file"]
    manager.load_checkpoint(checkpoint_file)
    context.results["loaded_manager"] = manager


@then("the manager state should include metadata {metadata_json}")
def step_verify_loaded_metadata(context: Context, metadata_json: str):
    manager: StateManager = context.results["loaded_manager"]
    expected = json.loads(metadata_json)
    assert manager.state.metadata == expected


@then("the manager update count should be {count:d}")
def step_verify_loaded_update_count(context: Context, count: int):
    manager: StateManager = context.results["loaded_manager"]
    assert manager.update_count == count


@when("I request the latest checkpoint")
def step_request_latest_checkpoint(context: Context):
    manager = context.state_managers.get("no_checkpoint") or context.results.get(
        "latest_manager"
    )
    context.results["latest_checkpoint"] = (
        manager.get_latest_checkpoint() if manager else None
    )


@then("no checkpoint path should be returned")
def step_verify_latest_none(context: Context):
    assert context.results["latest_checkpoint"] is None


@given("I have a StateManager with two checkpoints written at different times")
def step_manager_with_two_checkpoints(context: Context):
    _ensure(context)
    checkpoint_dir = Path(tempfile.mkdtemp())
    manager = StateManager(checkpoint_dir=checkpoint_dir)

    # Write first checkpoint
    manager.state.metadata = {"first": True}
    manager.update_count = 1
    manager._save_checkpoint()

    # Write second checkpoint with newer mtime
    manager.state.metadata = {"second": True}
    manager.update_count = 2
    manager._save_checkpoint()

    context.results["latest_manager"] = manager


@then("the newest checkpoint path should be returned")
def step_verify_latest_checkpoint(context: Context):
    manager: StateManager = context.results["latest_manager"]
    latest = manager.get_latest_checkpoint()
    assert latest is not None
    checkpoint_files = list(manager.checkpoint_dir.glob("checkpoint_*.json"))
    assert latest == max(checkpoint_files, key=lambda p: p.stat().st_mtime)


@given("I have a StateManager with time travel disabled")
def step_manager_time_travel_disabled(context: Context):
    _ensure(context)
    context.state_managers["tt_disabled"] = StateManager(enable_time_travel=False)


@when("I request time travel")
def step_request_time_travel(context: Context):
    manager = context.state_managers["tt_disabled"]
    context.results["tt_result"] = manager.time_travel()


@then("the returned time travel state should be None")
def step_verify_time_travel_none(context: Context):
    assert context.results["tt_result"] is None


@given("I have a StateManager with time travel history entries")
def step_manager_with_history(context: Context):
    _ensure(context)
    manager = StateManager(enable_time_travel=True)
    manager.update_state({"execution_count": 1, "metadata": {"keep": "me"}})
    manager.update_state({"execution_count": 2, "metadata": {"keep": "me2"}})
    context.state_managers["history_reset"] = manager


@when("I clear the history and then reset with metadata {metadata_json}")
def step_clear_and_reset(context: Context, metadata_json: str):
    manager = context.state_managers["history_reset"]
    manager.clear_history()
    manager.reset(GraphState(metadata=json.loads(metadata_json)))
    context.results["reset_manager"] = manager


@then("the history should be empty after reset")
def step_verify_history_empty(context: Context):
    manager: StateManager = context.results["reset_manager"]
    assert len(manager.history) == 0


@then("the execution count should be zero after reset")
def step_verify_execution_zero(context: Context):
    manager: StateManager = context.results["reset_manager"]
    assert manager.update_count == 0


@then("the state metadata should include {metadata_json}")
def step_verify_state_metadata_after_reset(context: Context, metadata_json: str):
    manager: StateManager = context.results["reset_manager"]
    expected = json.loads(metadata_json)
    assert manager.state.metadata == expected
