"""Step definitions for Resume model coverage boost tests.

Targets uncovered lines in resume.py:
- Line 150: ValueError in record_checkpoint for negative step_index
- Lines 219-233: ResumeSummary.as_cli_dict method
"""

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

from cleveragents.domain.models.core.resume import (
    ResumeCheckpoint,
    ResumeMetadata,
    ResumeSummary,
)

# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------

_VALID_ULID = str(ULID())
_PLAN_ULID = str(ULID())


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


@given("I have a ResumeMetadata instance with {total:d} total steps")
def step_create_resume_metadata(context: Context, total: int) -> None:
    """Create a ResumeMetadata for checkpoint recording tests."""
    context.resume_metadata = ResumeMetadata(total_steps=total)
    context.error = None


@given("I have a ResumeSummary with no optional fields")
def step_summary_no_optionals(context: Context) -> None:
    """Create a ResumeSummary with all optional fields as None."""
    context.summary = ResumeSummary(
        plan_id=_VALID_ULID,
        phase="execute",
        processing_state="processing",
        last_completed_step=0,
        next_step_index=1,
        total_steps=5,
        decision_id=None,
        last_checkpoint_id=None,
        sandbox_ref=None,
    )
    context.cli_dict = None


@given('I have a ResumeSummary with decision_id "{decision_id}"')
def step_summary_with_decision(context: Context, decision_id: str) -> None:
    """Create a ResumeSummary with only decision_id set."""
    context.summary = ResumeSummary(
        plan_id=_VALID_ULID,
        phase="execute",
        processing_state="processing",
        last_completed_step=0,
        next_step_index=1,
        total_steps=5,
        decision_id=decision_id,
    )
    context.cli_dict = None


@given('I have a ResumeSummary with last_checkpoint_id "{chk_id}"')
def step_summary_with_checkpoint(context: Context, chk_id: str) -> None:
    """Create a ResumeSummary with only last_checkpoint_id set."""
    context.summary = ResumeSummary(
        plan_id=_VALID_ULID,
        phase="execute",
        processing_state="processing",
        last_completed_step=1,
        next_step_index=2,
        total_steps=5,
        last_checkpoint_id=chk_id,
    )
    context.cli_dict = None


@given('I have a ResumeSummary with sandbox_ref "{sandbox_ref}"')
def step_summary_with_sandbox(context: Context, sandbox_ref: str) -> None:
    """Create a ResumeSummary with only sandbox_ref set."""
    context.summary = ResumeSummary(
        plan_id=_VALID_ULID,
        phase="execute",
        processing_state="processing",
        last_completed_step=1,
        next_step_index=2,
        total_steps=5,
        sandbox_ref=sandbox_ref,
    )
    context.cli_dict = None


@given("I have a ResumeSummary with all optional fields set")
def step_summary_all_optionals(context: Context) -> None:
    """Create a ResumeSummary with every optional field populated."""
    context.summary = ResumeSummary(
        plan_id=_VALID_ULID,
        phase="execute",
        processing_state="processing",
        last_completed_step=2,
        next_step_index=3,
        total_steps=5,
        decision_id="DEC_ALL",
        last_checkpoint_id=str(ULID()),
        sandbox_ref="/tmp/sandbox-all",
    )
    context.cli_dict = None


@given(
    'I have a ResumeSummary with plan_id "{plan_id}" and phase "{phase}" '
    'and state "{state}" and last_step {last:d} and next_step {nxt:d} '
    "and total {total:d}"
)
def step_summary_with_specific_values(
    context: Context,
    plan_id: str,
    phase: str,
    state: str,
    last: int,
    nxt: int,
    total: int,
) -> None:
    """Create a ResumeSummary with explicit base field values."""
    context.summary = ResumeSummary(
        plan_id=plan_id,
        phase=phase,
        processing_state=state,
        last_completed_step=last,
        next_step_index=nxt,
        total_steps=total,
    )
    context.cli_dict = None


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


@when("I record a checkpoint with step_index {index:d}")
def step_record_bad_checkpoint(context: Context, index: int) -> None:
    """Attempt to record a checkpoint (may raise ValueError for negative index)."""
    try:
        checkpoint = ResumeCheckpoint(
            checkpoint_id=str(ULID()),
            plan_id=_PLAN_ULID,
            decision_id="DEC_NEG",
            step_index=max(index, 0),  # Pydantic ge=0 would reject negative
            step_text="Negative step",
        )
        # Manually set to negative to bypass Pydantic field validation
        # and hit the domain guard inside record_checkpoint
        object.__setattr__(checkpoint, "step_index", index)
        context.resume_metadata.record_checkpoint(checkpoint)
    except ValueError as exc:
        context.error = exc


@when("I call as_cli_dict on the resume summary")
def step_call_as_cli_dict(context: Context) -> None:
    """Call as_cli_dict on the stored ResumeSummary."""
    context.cli_dict = context.summary.as_cli_dict()


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


@then('a ValueError should be raised with message "{msg}"')
def step_assert_value_error(context: Context, msg: str) -> None:
    """Assert a ValueError was raised with the expected message."""
    assert context.error is not None, "Expected a ValueError but none was raised"
    assert isinstance(context.error, ValueError), (
        f"Expected ValueError, got {type(context.error).__name__}"
    )
    assert msg in str(context.error), f"Expected message '{msg}' in '{context.error}'"


@then('the cli dict should have key "{key}"')
def step_assert_dict_has_key(context: Context, key: str) -> None:
    """Assert the cli dict contains the given key."""
    assert context.cli_dict is not None, "cli_dict is None; was as_cli_dict called?"
    assert key in context.cli_dict, (
        f"Key '{key}' not found in cli_dict: {list(context.cli_dict.keys())}"
    )


@then('the cli dict should lack key "{key}"')
def step_assert_dict_missing_key(context: Context, key: str) -> None:
    """Assert the cli dict does NOT contain the given key."""
    assert context.cli_dict is not None, "cli_dict is None; was as_cli_dict called?"
    assert key not in context.cli_dict, (
        f"Key '{key}' should not be in cli_dict but was found with value "
        f"'{context.cli_dict[key]}'"
    )


@then('the cli dict value for "{key}" should be "{value}"')
def step_assert_dict_key_value(context: Context, key: str, value: str) -> None:
    """Assert the cli dict contains the key with the expected string value."""
    assert context.cli_dict is not None
    assert key in context.cli_dict, f"Key '{key}' not in cli_dict"
    assert str(context.cli_dict[key]) == value, (
        f"Expected '{value}', got '{context.cli_dict[key]}'"
    )


@then("the cli dict should have exactly {count:d} keys")
def step_assert_dict_key_count(context: Context, count: int) -> None:
    """Assert the cli dict has exactly N keys."""
    assert context.cli_dict is not None
    actual = len(context.cli_dict)
    assert actual == count, (
        f"Expected {count} keys, got {actual}: {list(context.cli_dict.keys())}"
    )


@then('the cli dict integer for "{key}" should be {value:d}')
def step_assert_dict_key_equals_int(context: Context, key: str, value: int) -> None:
    """Assert a cli dict key equals an integer value."""
    assert context.cli_dict is not None
    assert key in context.cli_dict, f"Key '{key}' not in cli_dict"
    assert context.cli_dict[key] == value, (
        f"Expected {value}, got {context.cli_dict[key]}"
    )
