"""Step definitions for validation apply gate tests."""

from __future__ import annotations

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

from cleveragents.application.services.validation_apply import (
    ApplyValidationGate,
    ApplyValidationResult,
    ApplyValidationSummary,
    AttachmentScope,
    DefaultValidationRunner,
    ValidationAttachment,
)
from cleveragents.domain.models.core.tool import ValidationMode

__all__: list[str] = []


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


@given("a validation apply test environment")
def step_given_val_apply_env(context: Context) -> None:
    context.val_attachments = []  # list[ValidationAttachment]
    context.val_run_context = {}  # dict[str, str]
    context.val_result = None  # ApplyValidationResult | None
    context.val_summary = None  # ApplyValidationSummary | None
    context.val_gate_summary = None  # ApplyValidationSummary | None


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


@given('a validation attachment "{name}" for resource "{res}" with mode "{mode}"')
def step_given_attachment(context: Context, name: str, res: str, mode: str) -> None:
    idx = len(context.val_attachments)
    att = ValidationAttachment(
        attachment_id=f"01ATT{idx:021d}",
        validation_name=name,
        resource_id=res,
        mode=ValidationMode(mode),
    )
    context.val_attachments.append(att)


@given('a project-scoped attachment "{name}" for resource "{res}" targeting "{target}"')
def step_given_project_attachment(
    context: Context, name: str, res: str, target: str
) -> None:
    idx = len(context.val_attachments)
    att = ValidationAttachment(
        attachment_id=f"01ATT{idx:021d}",
        validation_name=name,
        resource_id=res,
        mode=ValidationMode.REQUIRED,
        scope=AttachmentScope.PROJECT,
        scope_target=target,
    )
    context.val_attachments.append(att)


@given('a plan-scoped attachment "{name}" for resource "{res}" targeting "{target}"')
def step_given_plan_attachment(
    context: Context, name: str, res: str, target: str
) -> None:
    idx = len(context.val_attachments)
    att = ValidationAttachment(
        attachment_id=f"01ATT{idx:021d}",
        validation_name=name,
        resource_id=res,
        mode=ValidationMode.REQUIRED,
        scope=AttachmentScope.PLAN,
        scope_target=target,
    )
    context.val_attachments.append(att)


@given('a validation run context with key "{key}" and value "{value}"')
def step_given_val_context(context: Context, key: str, value: str) -> None:
    context.val_run_context[key] = value


@given('the attachment has argument "{key}" with value "{value}"')
def step_given_attachment_arg(context: Context, key: str, value: str) -> None:
    context.val_attachments[-1].arguments[key] = value


@given("an apply validation summary with {p} required passed and {f} required failed")
def step_given_apply_summary(context: Context, p: str, f: str) -> None:
    results: list[ApplyValidationResult] = []
    for i in range(int(p)):
        results.append(
            ApplyValidationResult(
                attachment_id=f"01ATT{i:021d}",
                validation_name=f"pass-val-{i}",
                resource_id=f"res-{i}",
                mode=ValidationMode.REQUIRED,
                passed=True,
                message="ok",
            )
        )
    for i in range(int(f)):
        results.append(
            ApplyValidationResult(
                attachment_id=f"01ATT{int(p) + i:021d}",
                validation_name=f"fail-val-{i}",
                resource_id=f"res-{int(p) + i}",
                mode=ValidationMode.REQUIRED,
                passed=False,
                message="failed check",
            )
        )
    context.val_summary = ApplyValidationSummary(
        plan_id="01VALTEST0000000000001",
        results=results,
    )


@given("the summary has {ip} informational passed and {iff} informational failed")
def step_given_informational_results(context: Context, ip: str, iff: str) -> None:
    base = len(context.val_summary.results)
    for i in range(int(ip)):
        context.val_summary.results.append(
            ApplyValidationResult(
                attachment_id=f"01ATT{base + i:021d}",
                validation_name=f"info-pass-{i}",
                resource_id=f"res-info-{i}",
                mode=ValidationMode.INFORMATIONAL,
                passed=True,
                message="info ok",
            )
        )
    base2 = len(context.val_summary.results)
    for i in range(int(iff)):
        context.val_summary.results.append(
            ApplyValidationResult(
                attachment_id=f"01ATT{base2 + i:021d}",
                validation_name=f"info-fail-{i}",
                resource_id=f"res-info-fail-{i}",
                mode=ValidationMode.INFORMATIONAL,
                passed=False,
                message="info failed",
            )
        )


@given("a validation run context that raises an error during iteration")
def step_given_error_context(context: Context) -> None:
    """Set up a context dict-like object whose items() raises."""

    class _BadContext(dict):  # type: ignore[type-arg]
        def items(self) -> None:  # type: ignore[override]
            msg = "boom"
            raise RuntimeError(msg)

    context.val_run_context = _BadContext()


@given("an apply validation summary with a bare failure result")
def step_given_summary_bare_failure(context: Context) -> None:
    context.val_summary = ApplyValidationSummary(
        plan_id="01VALTEST0000000000001",
        results=[
            ApplyValidationResult(
                attachment_id="01ATT000000000000000000000",
                validation_name="bare-val",
                resource_id="res-bare",
                mode=ValidationMode.REQUIRED,
                passed=False,
                message="",
                error=None,
            ),
        ],
    )
    context.val_gate_summary = context.val_summary


@given("an apply validation summary with an error result")
def step_given_summary_with_error(context: Context) -> None:
    context.val_summary = ApplyValidationSummary(
        plan_id="01VALTEST0000000000001",
        results=[
            ApplyValidationResult(
                attachment_id="01ATT000000000000000000000",
                validation_name="broken-val",
                resource_id="res-broken",
                mode=ValidationMode.REQUIRED,
                passed=False,
                message="",
                error="execution timed out",
            ),
        ],
    )
    context.val_gate_summary = context.val_summary


@given("an empty apply validation summary")
def step_given_empty_apply_summary(context: Context) -> None:
    context.val_summary = ApplyValidationSummary(
        plan_id="01VALTEST0000000000001",
        results=[],
    )


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


@when("I execute the validation with DefaultValidationRunner")
def step_when_execute_default_runner(context: Context) -> None:
    runner = DefaultValidationRunner()
    context.val_result = runner.run_validation(
        context.val_attachments[-1],
        context.val_run_context,
    )


@when('I run the apply validation gate for plan "{plan_id}"')
def step_when_run_gate(context: Context, plan_id: str) -> None:
    runner = DefaultValidationRunner()
    gate = ApplyValidationGate(runner=runner)
    context.val_gate_summary = gate.run(
        plan_id=plan_id,
        attachments=context.val_attachments,
        context=context.val_run_context,
    )
    # Also store as val_summary for summary assertions
    context.val_summary = context.val_gate_summary


@when('I run the apply validation gate for plan "{plan_id}" with no attachments')
def step_when_run_gate_empty(context: Context, plan_id: str) -> None:
    runner = DefaultValidationRunner()
    gate = ApplyValidationGate(runner=runner)
    context.val_gate_summary = gate.run(
        plan_id=plan_id,
        attachments=[],
        context=context.val_run_context,
    )
    context.val_summary = context.val_gate_summary


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


@then('the attachment mode should be "{mode}"')
def step_then_attachment_mode(context: Context, mode: str) -> None:
    att = context.val_attachments[-1]
    assert att.mode == ValidationMode(mode), (
        f"Expected mode '{mode}', got '{att.mode.value}'"
    )


@then('the attachment scope should be "{scope}"')
def step_then_attachment_scope(context: Context, scope: str) -> None:
    att = context.val_attachments[-1]
    assert att.scope == AttachmentScope(scope), (
        f"Expected scope '{scope}', got '{att.scope.value}'"
    )


@then('the attachment scope target should be "{target}"')
def step_then_scope_target(context: Context, target: str) -> None:
    att = context.val_attachments[-1]
    assert att.scope_target == target


@then("the validation result should be passed")
def step_then_val_result_passed(context: Context) -> None:
    assert context.val_result is not None
    assert context.val_result.passed, (
        f"Expected passed, got failed: {context.val_result.message}"
    )


@then("the validation result should be failed")
def step_then_val_result_failed(context: Context) -> None:
    assert context.val_result is not None
    assert not context.val_result.passed


@then("the validation result duration_ms should be >= {n}")
def step_then_val_result_duration(context: Context, n: str) -> None:
    assert context.val_result is not None
    assert context.val_result.duration_ms >= int(n), (
        f"Expected duration_ms >= {n}, got {context.val_result.duration_ms}"
    )


@then("the validation result should have an error message")
def step_then_val_result_has_error(context: Context) -> None:
    assert context.val_result is not None
    assert context.val_result.error is not None and context.val_result.error != "", (
        f"Expected error message, got: {context.val_result.error!r}"
    )


@then("the apply summary should report all required passed")
def step_then_all_required_passed(context: Context) -> None:
    assert context.val_summary is not None
    assert context.val_summary.all_required_passed


@then("the apply summary should not report all required passed")
def step_then_not_all_required_passed(context: Context) -> None:
    assert context.val_summary is not None
    assert not context.val_summary.all_required_passed


@then("the apply summary total should be {n}")
def step_then_apply_summary_total(context: Context, n: str) -> None:
    assert context.val_summary is not None
    assert context.val_summary.total == int(n), (
        f"Expected total {n}, got {context.val_summary.total}"
    )


@then("the apply summary required failed count should be {n}")
def step_then_required_failed(context: Context, n: str) -> None:
    assert context.val_summary is not None
    assert context.val_summary.required_failed == int(n)


@then("the apply summary informational failed count should be {n}")
def step_then_informational_failed(context: Context, n: str) -> None:
    assert context.val_summary is not None
    assert context.val_summary.informational_failed == int(n)


@then("the apply summary should be empty")
def step_then_apply_summary_empty(context: Context) -> None:
    assert context.val_summary is not None
    assert context.val_summary.is_empty


@then('the plan metadata should have key "{key}"')
def step_then_plan_metadata_key(context: Context, key: str) -> None:
    assert context.val_summary is not None
    md = context.val_summary.to_plan_metadata()
    assert key in md, f"Key '{key}' not in {list(md.keys())}"


@then('the validation gate CLI output should contain "{text}"')
def step_then_val_gate_cli_output(context: Context, text: str) -> None:
    assert context.val_summary is not None
    output = context.val_summary.format_cli_output()
    assert text in output, f"Expected '{text}' in:\n{output}"


@then("the gate should allow apply")
def step_then_gate_allows(context: Context) -> None:
    assert context.val_gate_summary is not None
    gate = ApplyValidationGate(runner=DefaultValidationRunner())
    assert not gate.should_block_apply(context.val_gate_summary), (
        "Expected gate to allow apply but it blocked"
    )


@then("the gate should block apply")
def step_then_gate_blocks(context: Context) -> None:
    assert context.val_gate_summary is not None
    gate = ApplyValidationGate(runner=DefaultValidationRunner())
    assert gate.should_block_apply(context.val_gate_summary), (
        "Expected gate to block apply but it allowed"
    )


@then('the gate failure reasons should mention "{text}"')
def step_then_failure_reasons(context: Context, text: str) -> None:
    assert context.val_gate_summary is not None
    gate = ApplyValidationGate(runner=DefaultValidationRunner())
    reasons = gate.get_failure_reasons(context.val_gate_summary)
    combined = " ".join(reasons)
    assert text in combined, f"Expected '{text}' in failure reasons: {reasons}"
