"""Step definitions for llm_file_parsing_regression.feature (#10878 TDD regression).

Demonstrates that LLMExecuteActor._parse_file_blocks correctly extracts all FILE
blocks when their body contains embedded triple-backtick markdown code fences,
proving the new delimiter scheme is not vulnerable to the collision that
caused truncated architecture reviews.

Forgejo: #10878

Also exercises related edge cases: trailing backtick line, sentinel text in
body prose without full markers, and git merge-conflict style markers mixed
with sentinel delimiters.
"""

from __future__ import annotations


from behave import given, then, when  # type: ignore[import-untyped]

from cleveragents.application.services.llm_actors import LLMExecuteActor


# ---------------------------------------------------------------------------
# Helpers - build LLM output strings matching each scenario's description.
# ---------------------------------------------------------------------------


def _llm_with_fences(n_files, fence_in_each=True, extra_prose="") -> str:
    """Return an ``FILE: ... / delimiter`` block string.

    When *fence_in_each* is True the body of every file includes a
    `````python ```` `code fence` - exactly the pattern that caused the original bug.
    """
    output = extra_prose
    for i in range(1, n_files + 1):
        path = f"mod_{i}.py" if i == 1 else f"src/mod_{i}.py"
        fence_block = "\n```python\nprint('hello')\n```\n" if fence_in_each else ""
        output += (
            f"FILE: {path}\n"
            "<<<<<<< CLEVERAGENTS_FILE_START >>>>>>>\n"
            f"# File {i}\n{fence_block}"
            "actual code here\n"
            "<<<<<<< CLEVERAGENTS_FILE_END >>>>>>>\n\n"
        )
    return output


# ---------------------------------------------------------------------------
# Given steps
# ---------------------------------------------------------------------------


@given(
    "an LLM response with two FILE blocks, each having ```python sections in the body"
)
def step_two_embedded_fences(context):  # type: ignore[no-untyped-def]
    context.input_lfm = _llm_with_fences(2, fence_in_each=True)


@given(
    "an LLM response mimicking an architecture review with report prose and three FILE blocks each containing markdown code example triple-backticks in their body",
)
def step_arch_review_full(context):  # type: ignore[no-untyped-def]
    context.input_lfm = _llm_with_fences(
        3, fence_in_each=True, extra_prose="## Module Graph\nSome report prose above.\n"
    )


@given(
    "an LLM response where a single FILE block body ends on a line that is exactly ``` after code content"
)
def step_trailing_backtick(context):  # type: ignore[no-untyped-def]
    path = "module.py"
    context.input_lfm = (
        f"FILE: {path}\n"
        "<<<<<<< CLEVERAGENTS_FILE_START >>>>>>>\n"
        "line one\nline two\n```\n"
        "<<<<<<< CLEVERAGENTS_FILE_END >>>>>>>\n\n"
    )


@given(
    "an LLM response where a FILE block body contains the word 'CLEVERAGENTS_FILE_END' referenced in prose within content and another file follows",
)
def step_body_mentions_sentinel_text(context):  # type: ignore[no-untyped-def]
    context.input_lfm = (
        "FILE: mod_a.py\n"
        "<<<<<<< CLEVERAGENTS_FILE_START >>>>>>>\n"
        "some text with ```python blocks\n"
        "# The body mentions CLEVERAGENTS_FILE_END as prose (no markers)\n"
        "rest of content here\n"
        "<<<<<<< CLEVERAGENTS_FILE_END >>>>>>>\n\n"
        "FILE: mod_b.py\n"
        "<<<<<<< CLEVERAGENTS_FILE_START >>>>>>>\n"
        "# second file block\n"
        "continues after sentinel-text mention\n"
        "<<<<<<< CLEVERAGENTS_FILE_END >>>>>>>\n\n"
    )


@given(
    "an LLM response where FILE block bodies include text like '>>>>>>> some_branch' and '<<<<<<< base' but the actual delimiters are CLEVERAGENTS_FILE_START / CLEVERAGENTS_FILE_END markers",
)
def step_mixed_git_markers(context):  # type: ignore[no-untyped-def]
    context.input_lfm = (
        "Prose with <<<<<< some_branch mentioned here\n"
        "FILE: mod_a.py\n"
        "<<<<<<< CLEVERAGENTS_FILE_START >>>>>>>\n"
        "```python\ndef f1(): pass  # <<< and >>> in content are fine\n```\n"
        "<<<<<<< CLEVERAGENTS_FILE_END >>>>>>>\n\n"
        "FILE: mod_b.py\n"
        "<<<<<<< CLEVERAGENTS_FILE_START >>>>>>>\n"
        ">>>>>>> base branch also mentioned here\n"
        "more code below\n"
        "<<<<<<< CLEVERAGENTS_FILE_END >>>>>>>\n\n"
    )


@given('a mock plan_id "01HQREGTEST"')
def step_mock_plan_id_regtest(context):  # type: ignore[no-untyped-def]
    context.lfm_plan_id = "01HQREGTEST"


@given('a mock plan_id "01HQARCHTEST"')
def step_mock_plan_id_archtest(context):  # type: ignore[no-untyped-def]
    context.lfm_plan_id = "01HQARCHTEST"


@given('a mock plan_id "01HQTRAILT"')
def step_mock_plan_id_trail(context):  # type: ignore[no-untyped-def]
    context.lfm_plan_id = "01HQTRAILT"


@given('a mock plan_id "01HQSENTINEL"')
def step_mock_plan_id_sentinel(context):  # type: ignore[no-untyped-def]
    context.lfm_plan_id = "01HQSENTINEL"


@given('a mock plan_id "01HQGITTEST"')
def step_mock_plan_id_gittest(context):  # type: ignore[no-untyped-def]
    context.lfm_plan_id = "01HQGITTEST"


@given('a mock plan_id "01HQESCTEST"')
def step_mock_plan_id_esctest(context):  # type: ignore[no-untyped-def]
    context.lfm_plan_id = "01HQESCTEST"


@given(
    "an LLM response with a single FILE block using legacy markers where the body"
    " contains a backslash-escaped <<<<<<< CLEVERAGENTS_FILE_START >>>>>>>"
    " sequence that should be preserved as literal text"
)
def step_single_block_with_escaped_marker(context):  # type: ignore[no-untyped-def]
    """FILE block whose body mentions the START marker prefixed with a backslash.

    The backslash-escaped occurrence must NOT be treated as a block boundary
    by the negative-lookbehind regex in _parse_file_blocks.
    """
    context.input_lfm = (
        "FILE: example.py\n"
        "<<<<<<< CLEVERAGENTS_FILE_START >>>>>>>\n"
        "# This file discusses the delimiter format:\n"
        "# \\<<<<<<< CLEVERAGENTS_FILE_START >>>>>>>\n"
        "# The above line is escaped and must not break parsing.\n"
        "<<<<<<< CLEVERAGENTS_FILE_END >>>>>>>\n\n"
    )


# ---------------------------------------------------------------------------
# When steps
# ---------------------------------------------------------------------------


@when(
    "I parse file blocks using the new CLEVERAGENTS_FILE_START / CLEVERAGENTS_FILE_END delimiter pattern"
)
def step_parse_with_new_delimiters(context):  # type: ignore[no-untyped-def]
    """Exercise LLMExecuteActor._parse_file_blocks with real input."""
    plan_id = getattr(context, "lfm_plan_id", "TEST_PLAN")  # type: ignore[attr-defined]
    input_data = context.input_lfm  # type: ignore[attr-defined]
    context.lfm_entries, _ = LLMExecuteActor._parse_file_blocks(input_data, plan_id)


@when("I parse file blocks using the new delimiter pattern")
def step_parse_with_new_delimiters_short(context):  # type: ignore[no-untyped-def]
    """Alias of the CLEVERAGENTS step for shorter scenario descriptions."""
    plan_id = getattr(context, "lfm_plan_id", "TEST_PLAN")  # type: ignore[attr-defined]
    input_data = context.input_lfm  # type: ignore[attr-defined]
    context.lfm_entries, _ = LLMExecuteActor._parse_file_blocks(input_data, plan_id)


# ---------------------------------------------------------------------------
# Then steps
# ---------------------------------------------------------------------------


@then("it should return {count:d} changeset entries")
def step_validate_entry_count(context, count):  # type: ignore[no-untyped-def]
    assert len(context.lfm_entries) == count, (  # type: ignore[attr-defined]
        f"Expected {count} entry/entries, got {len(context.lfm_entries)}: "
        f"{[e.path for e in context.lfm_entries]}"
    )


@then("it should return the full entry count of all embedded blocks")
def step_validate_full_count_in_body(context):  # type: ignore[no-untyped-def]
    entries = context.lfm_entries  # type: ignore[attr-defined]
    assert len(entries) == 3, (
        f"Expected 3 entries for full arch-review output, got {len(entries)}: "
        f"{[e.path for e in entries]}"
    )


@then("it should return {count:d} changeset entries (second entry has content in body)")
def step_validate_entry_count_with_body_note(context, count):  # type: ignore[no-untyped-def]
    assert len(context.lfm_entries) == count, (  # type: ignore[attr-defined]
        f"Expected {count} entries, got {len(context.lfm_entries)}: "
        f"{[e.path for e in context.lfm_entries]}"
    )


@then("changeset entry {idx:d} path should be a file with ```python block inside")
def step_validate_path_has_python_fences(index, context):  # type: ignore[no-untyped-def]
    entries = context.lfm_entries  # type: ignore[attr-defined]
    assert len(entries) > index, (
        f"Expected at least {index + 1} entries, got {len(entries)}"
    )


@then("it should NOT trip on the closing triple backtick")
def step_validate_no_triple_backtick_stop(context):  # type: ignore[no-untyped-def]
    entries = context.lfm_entries  # type: ignore[attr-defined]
    assert len(entries) >= 1, (
        f"Expected at least one entry when body contains ```; got {len(entries)}"
    )
