"""Step definitions for yaml_engine_direct_coverage.feature."""

from __future__ import annotations

import tempfile
from pathlib import Path
from typing import Any

import yaml
from behave import given, then, when  # type: ignore[import-untyped]
from behave.runner import Context  # type: ignore[import-untyped]

from cleveragents.actor.yaml_template_engine import YAMLTemplateEngine


class _HasScore:
    def __init__(self, score: float) -> None:
        self.score = score


class _NoScore:
    pass


@when("I construct a new YAMLTemplateEngine for direct testing")
@given("I construct a new YAMLTemplateEngine for direct testing")
def step_construct(context: Context) -> None:
    context.direct_engine = YAMLTemplateEngine()


@then("the direct engine should have yaml indent sum selectattr filters")
def step_check_filters(context: Context) -> None:
    for name in ("yaml", "indent", "sum", "selectattr"):
        assert name in context.direct_engine.env.filters, f"Missing filter: {name}"


@then("the direct engine globals should include builtins")
def step_check_globals(context: Context) -> None:
    g = context.direct_engine.env.globals
    for name in ("range", "abs", "round", "len", "min", "max", "sum"):
        assert name in g, f"Missing global: {name}"


@given("I write a temp YAML file with mapping content")
def step_write_temp_mapping(context: Context) -> None:
    with tempfile.NamedTemporaryFile(
        mode="w", suffix=".yaml", delete=False, encoding="utf-8"
    ) as tmp:
        tmp.write("app:\n  name: TestApp\n  version: 1\n")
        tmp.flush()
        context.direct_temp_path = Path(tmp.name)


@when("I call load_file on the direct engine without context")
def step_load_file_no_ctx(context: Context) -> None:
    context.direct_result = context.direct_engine.load_file(context.direct_temp_path)


@then("the direct load_file result should be a dict with app key")
def step_assert_load_file(context: Context) -> None:
    r = context.direct_result
    assert isinstance(r, dict)
    assert "app" in r


@when("I call load_string with plain mapping YAML on direct engine")
def step_load_plain_mapping(context: Context) -> None:
    context.direct_result = context.direct_engine.load_string("server:\n  port: 8080\n")


@then("the direct result should have key server")
def step_assert_server(context: Context) -> None:
    assert "server" in context.direct_result


@when("I call load_string with plain list YAML on direct engine")
def step_load_plain_list(context: Context) -> None:
    context.direct_error = None
    try:
        context.direct_engine.load_string("- a\n- b\n")
    except ValueError as exc:
        context.direct_error = exc


@then("a direct ValueError mentioning dict should be raised")
def step_assert_direct_valueerror(context: Context) -> None:
    assert context.direct_error is not None, "Expected ValueError"
    assert "dict" in str(context.direct_error).lower()


@when("I call load_string with template and context on direct engine")
def step_load_with_context(context: Context) -> None:
    yaml_str = 'greeting: "{{ message }}"\ncount: {{ num }}'
    ctx: dict[str, Any] = {"message": "hello", "num": 42}
    context.direct_result = context.direct_engine.load_string(yaml_str, context=ctx)


@then("the direct rendered result should have substituted values")
def step_assert_substituted(context: Context) -> None:
    assert context.direct_result["greeting"] == "hello"
    assert context.direct_result["count"] == 42


@when("I call load_string with template but no context on direct engine")
def step_load_no_context(context: Context) -> None:
    yaml_str = 'item:\n  name: "{{ name }}"\n'
    context.direct_error = None
    try:
        context.direct_result = context.direct_engine.load_string(yaml_str)
    except (yaml.YAMLError, ValueError) as exc:
        context.direct_error = exc
        context.direct_result = None


@then("the direct deferred result should be a dict or raise YAMLError")
def step_assert_deferred(context: Context) -> None:
    if context.direct_result is not None:
        assert isinstance(context.direct_result, dict)


@when("I call preprocess on content with a for loop")
def step_preprocess(context: Context) -> None:
    content = "items:\n  {% for x in list %}\n  - {{ x }}\n  {% endfor %}\n"
    context.direct_result = context.direct_engine._preprocess_for_rendering(content)


@then("the direct preprocessed result should contain indent hint")
def step_assert_hint(context: Context) -> None:
    assert "{# indent:" in context.direct_result


@when("I call postprocess on content with blank lines before unindented")
def step_postprocess(context: Context) -> None:
    content = "first: value\n\nsecond: value\n"
    context.direct_result = context.direct_engine._postprocess_rendered_yaml(content)


@then("blank lines before unindented content should be gone")
def step_assert_no_blanks(context: Context) -> None:
    lines = context.direct_result.split("\n")
    for i, line in enumerate(lines):
        if line.strip() == "" and i + 1 < len(lines):
            next_line = lines[i + 1]
            if next_line and not next_line.startswith(" "):
                raise AssertionError("Blank line before unindented line not removed")


@when("I call fix_common_yaml_issues on multi-colon content")
def step_fix_colons(context: Context) -> None:
    content = "config: key1: val1 key2: val2"
    context.direct_result = context.direct_engine._fix_common_yaml_issues(content)


@then("the fixed content should have multiple lines")
def step_assert_multiline(context: Context) -> None:
    assert len(context.direct_result.split("\n")) > 1


@when("I call analyze_yaml_structure on mixed content")
def step_analyze(context: Context) -> None:
    content = (
        "# comment\nroot:\n  value: static\n"
        "  {{ inline_var }}\n  {% for x in items %}\n"
        "  - name: {{ x }}\n  {% endfor %}\n"
    )
    context.direct_result = context.direct_engine._analyze_yaml_structure(content)


@then("template_blocks and inline_templates should be populated")
def step_assert_blocks(context: Context) -> None:
    assert len(context.direct_result["template_blocks"]) > 0
    assert len(context.direct_result["inline_templates"]) > 0


@then("hierarchy should have root key")
def step_assert_root(context: Context) -> None:
    keys = [e["key"] for e in context.direct_result["hierarchy"]]
    assert "root" in keys


@when("I call the static yaml_filter with a dict")
def step_yaml_filter(context: Context) -> None:
    context.direct_result = YAMLTemplateEngine._yaml_filter({"key": "value"})


@then("the yaml_filter result should contain key colon")
def step_assert_yaml(context: Context) -> None:
    assert "key:" in context.direct_result


@when("I call the static indent_filter with {n:d} spaces")
def step_indent_filter(context: Context, n: int) -> None:
    context.direct_result = YAMLTemplateEngine._indent_filter("line1\nline2", n)
    context.direct_indent = n


@then("each direct line should start with {n:d} spaces")
def step_assert_indent(context: Context, n: int) -> None:
    for line in context.direct_result.split("\n"):
        assert line.startswith(" " * n), f"Not indented: {line!r}"


@when("I call the static sum_filter on a number list")
def step_sum_filter(context: Context) -> None:
    context.direct_result = YAMLTemplateEngine._sum_filter([1, 2, 3, 4])


@then("the direct sum should be {n:d}")
def step_assert_sum(context: Context, n: int) -> None:
    assert context.direct_result == n


@when("I call the static selectattr_filter for score")
def step_selectattr(context: Context) -> None:
    items = [_HasScore(0.9), _NoScore(), _HasScore(0.5)]
    context.direct_result = YAMLTemplateEngine._selectattr_filter(items, "score", 0)


@then("items with score return their value and others return 0")
def step_assert_selectattr(context: Context) -> None:
    assert context.direct_result[0] == 0.9
    assert context.direct_result[1] == 0
    assert context.direct_result[2] == 0.5


@when("I call the static create_render_context with nested data")
def step_render_context(context: Context) -> None:
    ctx: dict[str, Any] = {
        "value": "explicit",
        "context": {"shared": "nested_val"},
    }
    context.direct_result = YAMLTemplateEngine._create_render_context(ctx)


@then("the direct context should have both nested and explicit keys")
def step_assert_ctx(context: Context) -> None:
    assert context.direct_result["value"] == "explicit"
    assert context.direct_result["context"]["shared"] == "nested_val"


@when("I call render_and_parse with colon-heavy value")
def step_render_colons(context: Context) -> None:
    ctx: dict[str, Any] = {"val": "key1: v1 key2: v2"}
    context.direct_error = None
    context.direct_result = None
    try:
        context.direct_result = context.direct_engine._render_and_parse(
            "config: {{ val }}", ctx
        )
    except (yaml.YAMLError, ValueError) as exc:
        context.direct_error = exc


@then("the direct engine should attempt fix or raise")
def step_assert_fix(context: Context) -> None:
    assert context.direct_result is not None or context.direct_error is not None


@when("I call render_and_parse that produces a list")
def step_render_list(context: Context) -> None:
    ctx: dict[str, Any] = {"items": ["a", "b"]}
    context.direct_error = None
    try:
        context.direct_engine._render_and_parse(
            "{% for item in items %}\n- {{ item }}\n{% endfor %}", ctx
        )
    except ValueError as exc:
        context.direct_error = exc


@when("I call simple_template_extraction with invalid YAML")
def step_simple_invalid(context: Context) -> None:
    context.direct_error = None
    try:
        context.direct_engine._simple_template_extraction("key: {{ bad\n")
    except yaml.YAMLError as exc:
        context.direct_error = exc


@then("a direct YAML error should be raised")
def step_assert_yaml_error(context: Context) -> None:
    assert context.direct_error is not None, "Expected YAMLError"


@when("I call simple_template_extraction with list YAML")
def step_simple_list(context: Context) -> None:
    context.direct_error = None
    try:
        context.direct_engine._simple_template_extraction("- a\n- b\n")
    except ValueError as exc:
        context.direct_error = exc


@given("I write a temp YAML file with template content")
def step_write_template_file(context: Context) -> None:
    with tempfile.NamedTemporaryFile(
        mode="w", suffix=".yaml", delete=False, encoding="utf-8"
    ) as tmp:
        tmp.write('agent:\n  name: "{{ name }}"\n  role: static\n')
        tmp.flush()
        context.direct_template_path = Path(tmp.name)


@when("I call load_file with template context on direct engine")
def step_load_file_with_ctx(context: Context) -> None:
    ctx: dict[str, Any] = {"name": "TestAgent"}
    context.direct_result = context.direct_engine.load_file(
        context.direct_template_path, context=ctx
    )


@then("the direct file result should contain rendered values")
def step_assert_file_rendered(context: Context) -> None:
    assert isinstance(context.direct_result, dict)
    assert context.direct_result["agent"]["name"] == "TestAgent"
