"""Behave steps targeting uncovered lines in yaml_template_engine.py.

Uncovered lines targeted:
  101-105  - fallback parse path after _fix_common_yaml_issues (dict and non-dict)
  134-136  - _postprocess_rendered_yaml value_words branch
  138-142  - dead-code branch (documented; closest reachable path exercised)
  172      - _fix_common_yaml_issues single/zero-colon line passthrough
  236-237  - _sum_filter ValueError for non-summable input
"""

from __future__ import annotations

from typing import Any
from unittest import mock

import yaml
from behave import given, then, when

# Keep a reference to the module for mock.patch.object
import cleveragents.actor.yaml_template_engine as _mod
from cleveragents.actor.yaml_template_engine import YAMLTemplateEngine

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


@given("a fresh YAML template engine instance")
def step_fresh_engine(context: Any) -> None:
    context.engine = YAMLTemplateEngine()
    context.result = None
    context.error = None


# ---------------------------------------------------------------------------
# Lines 101-105 - fallback returns dict
# ---------------------------------------------------------------------------


@given("a YAML template with Jinja2 that renders valid content")
def step_template_for_fallback(context: Any) -> None:
    # Must contain {{ so load_string takes the template path
    context.yaml_template = "result: {{ val }}"


@given("a context that produces parseable YAML on fix retry")
def step_context_for_fallback(context: Any) -> None:
    context.template_ctx = {"val": "hello", "context": {}}


@when("I render and parse with a forced first-parse YAMLError returning a dict")
def step_render_fallback_dict(context: Any) -> None:
    """Mock yaml.safe_load so the first call raises YAMLError and the second
    returns a dict - exercising lines 100-101-105."""
    side_effects = [
        yaml.YAMLError("simulated first-parse failure"),
        {"result": "recovered-value"},
    ]
    with mock.patch.object(_mod.yaml, "safe_load", side_effect=side_effects):
        context.result = context.engine._render_and_parse(
            context.yaml_template, context.template_ctx
        )


@then("the fallback parse should return a dict result")
def step_assert_fallback_dict(context: Any) -> None:
    assert isinstance(context.result, dict), (
        f"Expected dict, got {type(context.result)}"
    )
    assert context.result == {"result": "recovered-value"}


@when("I render and parse with a forced first-parse YAMLError returning a list")
def step_render_fallback_list(context: Any) -> None:
    """Mock yaml.safe_load so the first call raises YAMLError and the second
    returns a list - exercising lines 100-101-102-103-104."""
    side_effects = [
        yaml.YAMLError("simulated first-parse failure"),
        ["not", "a", "dict"],
    ]
    try:
        with mock.patch.object(_mod.yaml, "safe_load", side_effect=side_effects):
            context.result = context.engine._render_and_parse(
                context.yaml_template, context.template_ctx
            )
        context.error = None
    except Exception as exc:
        context.result = None
        context.error = exc


@then("a ValueError should be raised from the fallback path")
def step_assert_fallback_valueerror(context: Any) -> None:
    assert isinstance(context.error, ValueError), (
        f"Expected ValueError, got {type(context.error)}: {context.error}"
    )
    assert "dict" in str(context.error)
    # The ValueError should chain from the original YAMLError
    assert context.error.__cause__ is not None


# ---------------------------------------------------------------------------
# Lines 134-136 - _postprocess_rendered_yaml value_words branch
# ---------------------------------------------------------------------------


@given("rendered YAML with a single-colon line whose value has multiple words")
def step_rendered_single_colon_spaced_value(context: Any) -> None:
    # line.count(":") == 1, value_part has spaces, ":" not in value_part
    # This reaches line 134 (value_words = ...) and line 135-136 (any() → False)
    context.postprocess_input = "config: alpha beta gamma delta"


@when("I postprocess the YAML content")
def step_postprocess_content(context: Any) -> None:
    context.postprocess_output = context.engine._postprocess_rendered_yaml(
        context.postprocess_input
    )


@then("the value_words branch should be entered and evaluated")
def step_assert_value_words_entered(context: Any) -> None:
    # Because value has no colons in subsequent words, the inner condition is
    # False and the line passes through unchanged.
    assert context.postprocess_output.strip() == "config: alpha beta gamma delta"


# ---------------------------------------------------------------------------
# Lines 138-142 - dead code (closest reachable path)
# ---------------------------------------------------------------------------


@given('rendered YAML content "{content}" with spaces in value')
def step_rendered_content_with_spaces(context: Any, content: str) -> None:
    context.postprocess_input = content


@then("the line should pass through unchanged because value words lack colons")
def step_assert_unchanged_no_colon_words(context: Any) -> None:
    # The guard ":" not in value_part on line 133 prevents any word from having
    # a colon, so the inner any() on line 135-136 is always False.
    # The line passes to fixed_lines.append(line) on line 143.
    assert context.postprocess_input.strip() in context.postprocess_output


# ---------------------------------------------------------------------------
# Line 172 - _fix_common_yaml_issues passthrough for <=1 colon lines
# ---------------------------------------------------------------------------


@given('YAML content with a single-colon line "{content}"')
def step_yaml_single_colon_line(context: Any, content: str) -> None:
    context.fix_input = content


@when("I apply fix common YAML issues")
def step_apply_fix_common(context: Any) -> None:
    context.fix_output = context.engine._fix_common_yaml_issues(context.fix_input)


@then("the single-colon line should be preserved verbatim")
def step_assert_single_colon_preserved(context: Any) -> None:
    assert context.fix_output == context.fix_input


@given('YAML content with a zero-colon line "{content}"')
def step_yaml_zero_colon_line(context: Any, content: str) -> None:
    context.fix_input = content


@then("the zero-colon line should be preserved verbatim")
def step_assert_zero_colon_preserved(context: Any) -> None:
    assert context.fix_output == context.fix_input


# ---------------------------------------------------------------------------
# Lines 236-237 - _sum_filter ValueError
# ---------------------------------------------------------------------------


@given("a sequence of non-summable items")
def step_non_summable_sequence(context: Any) -> None:
    # Passing strings that can't be summed with + starting from 0
    context.non_summable = "not-a-sequence"


@when("I call the sum filter on the non-summable sequence")
def step_call_sum_filter(context: Any) -> None:
    try:
        context.result = context.engine._sum_filter(context.non_summable)
        context.error = None
    except Exception as exc:
        context.result = None
        context.error = exc


@then("a ValueError should be raised with a descriptive message")
def step_assert_sum_valueerror(context: Any) -> None:
    assert isinstance(context.error, ValueError), (
        f"Expected ValueError, got {type(context.error)}: {context.error}"
    )
    assert "Cannot sum value" in str(context.error)


# ---------------------------------------------------------------------------
# Integration: end-to-end fallback
# ---------------------------------------------------------------------------


@given('a template "{template}" with context val set to "{val}"')
def step_template_with_val(context: Any, template: str, val: str) -> None:
    context.yaml_template = template
    context.template_ctx = {"val": val, "context": {}}


@when("rendering triggers a YAMLError on first parse but fix produces a dict")
def step_render_e2e_fallback(context: Any) -> None:
    side_effects = [
        yaml.YAMLError("transient parse failure"),
        {"result": "ok"},
    ]
    with mock.patch.object(_mod.yaml, "safe_load", side_effect=side_effects):
        context.result = context.engine._render_and_parse(
            context.yaml_template, context.template_ctx
        )


@then("the final result should be the fixed dict")
def step_assert_e2e_fixed(context: Any) -> None:
    assert context.result == {"result": "ok"}
