"""Step definitions for materializers_coverage_r3.feature.

These steps target uncovered lines in materializers.py:
- Line 344: _BaseBufferStrategy.on_element_closed with final_state=None
- Line 473: _LiveMaterializationStrategy._render_element base class
- Lines 486-487: _LiveMaterializationStrategy.on_element_created with initial_state=None
- Line 495: _LiveMaterializationStrategy.on_element_updated with unknown handle_id
- Line 497: _LiveMaterializationStrategy.on_element_updated for frozen element
- Line 509: _LiveMaterializationStrategy.on_element_closed with unknown handle_id
- Line 550: _LiveMaterializationStrategy._render_frame with empty dirty set
- Line 610: _AccumulateStrategy.on_element_updated returns immediately
"""

from __future__ import annotations

from unittest.mock import patch

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

from cleveragents.cli.output.handles import (
    ElementClosed,
    ElementCreated,
    ElementUpdated,
    Panel,
    StatusMessage,
)
from cleveragents.cli.output.materializers import (
    JsonMaterializer,
    PlainMaterializer,
    _LiveMaterializationStrategy,
)

# ===========================================================================
# Line 344: _BaseBufferStrategy.on_element_closed with final_state=None
# ===========================================================================


@given("matcov3 a PlainMaterializer instance")
def step_matcov3_given_plain(context: Context) -> None:
    context.matcov3_plain = PlainMaterializer()


@given('matcov3 the strategy has a registered handle "{handle_id}" at index {idx:d}')
def step_matcov3_register_handle(context: Context, handle_id: str, idx: int) -> None:
    # Simulate on_element_created so the handle is registered in _index_map
    event = ElementCreated(
        event_type="created",
        handle_id=handle_id,
        element_kind="panel",
        declaration_index=idx,
        initial_state=Panel(title="Placeholder"),
    )
    context.matcov3_plain.on_element_created(event)


@when(
    'matcov3 an ElementClosed event with handle "{handle_id}" and no final state is dispatched'
)
def step_matcov3_close_no_final_state(context: Context, handle_id: str) -> None:
    event = ElementClosed(
        event_type="closed",
        handle_id=handle_id,
        element_kind="panel",
        final_state=None,  # This triggers the early return on line 344
    )
    try:
        context.matcov3_plain.on_element_closed(event)
        context.matcov3_error = None
    except Exception as exc:
        context.matcov3_error = exc


@then("matcov3 the plain strategy output is empty")
def step_matcov3_plain_output_empty(context: Context) -> None:
    output = context.matcov3_plain.get_output()
    assert output == "", f"Expected empty output, got {output!r}"
    assert context.matcov3_error is None, f"Unexpected error: {context.matcov3_error}"


# ===========================================================================
# Line 473: _LiveMaterializationStrategy._render_element base class
# ===========================================================================


@given("matcov3 a raw LiveMaterializationStrategy instance")
def step_matcov3_given_live_strategy(context: Context) -> None:
    context.matcov3_live = _LiveMaterializationStrategy()


@given('matcov3 a StatusMessage element with message "{msg}"')
def step_matcov3_given_status_element(context: Context, msg: str) -> None:
    context.matcov3_status_element = StatusMessage(message=msg, level="info")


@when("matcov3 I call the live base class _render_element")
def step_matcov3_call_live_render(context: Context) -> None:
    try:
        context.matcov3_live_render_result = context.matcov3_live._render_element(
            context.matcov3_status_element
        )
        context.matcov3_error = None
    except Exception as exc:
        context.matcov3_error = exc
        context.matcov3_live_render_result = ""


@then('matcov3 the rendered text contains "{text}"')
def step_matcov3_rendered_contains(context: Context, text: str) -> None:
    assert context.matcov3_error is None, f"Unexpected error: {context.matcov3_error}"
    assert text in context.matcov3_live_render_result, (
        f"Expected {text!r} in {context.matcov3_live_render_result!r}"
    )


# ===========================================================================
# Lines 486-487: _LiveMaterializationStrategy.on_element_created
# with initial_state=None
# ===========================================================================


@when(
    "matcov3 an ElementCreated event with no initial state is dispatched to the live strategy"
)
def step_matcov3_live_created_no_initial(context: Context) -> None:
    context.matcov3_decl_idx = 3
    event = ElementCreated(
        event_type="created",
        handle_id="hdl-no-initial",
        element_kind="panel",
        declaration_index=context.matcov3_decl_idx,
        initial_state=None,  # Triggers lines 486-487
    )
    # Mock time.monotonic so _maybe_render_frame triggers a frame render
    patcher = patch("time.monotonic", return_value=999999.0)
    patcher.start()
    context.add_cleanup(patcher.stop)

    try:
        context.matcov3_live.on_element_created(event)
        context.matcov3_error = None
    except Exception as exc:
        context.matcov3_error = exc


@then("matcov3 the live strategy has an empty rendered entry at the declaration index")
def step_matcov3_live_empty_rendered(context: Context) -> None:
    assert context.matcov3_error is None, f"Unexpected error: {context.matcov3_error}"
    idx = context.matcov3_decl_idx
    assert idx in context.matcov3_live._rendered, (
        f"Declaration index {idx} not in _rendered"
    )
    assert context.matcov3_live._rendered[idx] == "", (
        f"Expected empty string at index {idx}, got {context.matcov3_live._rendered[idx]!r}"
    )


@then("matcov3 the live strategy has height zero at the declaration index")
def step_matcov3_live_zero_height(context: Context) -> None:
    idx = context.matcov3_decl_idx
    assert idx in context.matcov3_live._heights, (
        f"Declaration index {idx} not in _heights"
    )
    assert context.matcov3_live._heights[idx] == 0, (
        f"Expected height 0 at index {idx}, got {context.matcov3_live._heights[idx]}"
    )


# ===========================================================================
# Line 495: _LiveMaterializationStrategy.on_element_updated
# with unknown handle_id
# ===========================================================================


@when(
    'matcov3 an ElementUpdated event for unknown handle "{handle_id}" is dispatched to the live strategy'
)
def step_matcov3_live_update_unknown(context: Context, handle_id: str) -> None:
    event = ElementUpdated(
        event_type="updated",
        handle_id=handle_id,
        element_kind="panel",
        update_type="content",
        element_snapshot=StatusMessage(message="ghost update"),
    )
    try:
        context.matcov3_live.on_element_updated(event)
        context.matcov3_error = None
    except Exception as exc:
        context.matcov3_error = exc


@then("matcov3 the live strategy rendered dict is empty")
def step_matcov3_live_rendered_empty(context: Context) -> None:
    assert context.matcov3_error is None, f"Unexpected error: {context.matcov3_error}"
    assert len(context.matcov3_live._rendered) == 0, (
        f"Expected empty _rendered, got {context.matcov3_live._rendered}"
    )


# ===========================================================================
# Line 497: _LiveMaterializationStrategy.on_element_updated
# for a frozen (closed) element
# ===========================================================================


@given(
    'matcov3 a live element "{handle_id}" is created and then closed at index {idx:d}'
)
def step_matcov3_live_create_and_close(
    context: Context, handle_id: str, idx: int
) -> None:
    context.matcov3_frozen_handle_id = handle_id
    context.matcov3_frozen_idx = idx

    # Mock time to allow frames to render
    patcher = patch("time.monotonic", return_value=999999.0)
    patcher.start()
    context.add_cleanup(patcher.stop)

    # Create the element
    create_event = ElementCreated(
        event_type="created",
        handle_id=handle_id,
        element_kind="status",
        declaration_index=idx,
        initial_state=StatusMessage(message="frozen-element"),
    )
    context.matcov3_live.on_element_created(create_event)

    # Close the element (adds to _closed_indices)
    close_event = ElementClosed(
        event_type="closed",
        handle_id=handle_id,
        element_kind="status",
        final_state=StatusMessage(message="frozen-element-final"),
    )
    context.matcov3_live.on_element_closed(close_event)


@when(
    'matcov3 an ElementUpdated event for handle "{handle_id}" is dispatched to the live strategy'
)
def step_matcov3_live_update_frozen(context: Context, handle_id: str) -> None:
    event = ElementUpdated(
        event_type="updated",
        handle_id=handle_id,
        element_kind="status",
        update_type="content",
        element_snapshot=StatusMessage(message="should-be-ignored"),
    )
    try:
        context.matcov3_live.on_element_updated(event)
        context.matcov3_error = None
    except Exception as exc:
        context.matcov3_error = exc


@then("matcov3 no error is raised for the frozen update")
def step_matcov3_no_error_frozen(context: Context) -> None:
    assert context.matcov3_error is None, f"Unexpected error: {context.matcov3_error}"
    # Verify the update was ignored - rendered content should still be the final state
    idx = context.matcov3_frozen_idx
    rendered = context.matcov3_live._rendered.get(idx, "")
    assert "should-be-ignored" not in rendered, (
        f"Frozen element should not have been updated, but got: {rendered!r}"
    )


# ===========================================================================
# Line 509: _LiveMaterializationStrategy.on_element_closed
# with unknown handle_id
# ===========================================================================


@when(
    'matcov3 an ElementClosed event for unknown handle "{handle_id}" is dispatched to the live strategy'
)
def step_matcov3_live_close_unknown(context: Context, handle_id: str) -> None:
    event = ElementClosed(
        event_type="closed",
        handle_id=handle_id,
        element_kind="panel",
        final_state=Panel(title="Ghost Close"),
    )
    try:
        context.matcov3_live.on_element_closed(event)
        context.matcov3_error = None
    except Exception as exc:
        context.matcov3_error = exc


@then("matcov3 the live strategy closed indices set is empty")
def step_matcov3_live_closed_indices_empty(context: Context) -> None:
    assert context.matcov3_error is None, f"Unexpected error: {context.matcov3_error}"
    assert len(context.matcov3_live._closed_indices) == 0, (
        f"Expected empty _closed_indices, got {context.matcov3_live._closed_indices}"
    )


# ===========================================================================
# Line 550: _LiveMaterializationStrategy._render_frame with empty dirty set
# ===========================================================================


@when("matcov3 I call _render_frame on the live strategy with an empty dirty set")
def step_matcov3_live_render_frame_empty_dirty(context: Context) -> None:
    # Ensure dirty set is empty (it is by default on a fresh instance)
    assert len(context.matcov3_live._dirty) == 0, (
        f"Expected empty dirty set, got {context.matcov3_live._dirty}"
    )
    try:
        # _render_frame expects caller to hold the lock
        with context.matcov3_live._lock:
            context.matcov3_live._render_frame()
        context.matcov3_error = None
    except Exception as exc:
        context.matcov3_error = exc


@then("matcov3 the live strategy stream is still empty")
def step_matcov3_live_stream_empty(context: Context) -> None:
    assert context.matcov3_error is None, f"Unexpected error: {context.matcov3_error}"
    output = context.matcov3_live.get_output()
    assert output == "", f"Expected empty stream output, got {output!r}"


# ===========================================================================
# Line 610: _AccumulateStrategy.on_element_updated returns immediately
# ===========================================================================


@given("matcov3 a JsonMaterializer instance")
def step_matcov3_given_json_materializer(context: Context) -> None:
    context.matcov3_json = JsonMaterializer()


@when("matcov3 an ElementUpdated event is dispatched to the accumulate strategy")
def step_matcov3_accum_update(context: Context) -> None:
    event = ElementUpdated(
        event_type="updated",
        handle_id="hdl-accum-test",
        element_kind="status",
        update_type="content",
        element_snapshot=StatusMessage(message="accum update ignored"),
    )
    try:
        result = context.matcov3_json.on_element_updated(event)
        context.matcov3_accum_result = result
        context.matcov3_error = None
    except Exception as exc:
        context.matcov3_error = exc


@then("matcov3 no error is raised for the accumulate update")
def step_matcov3_no_error_accum(context: Context) -> None:
    assert context.matcov3_error is None, f"Unexpected error: {context.matcov3_error}"
