"""Step definitions for circuit breaker behavior tests."""

from __future__ import annotations

import contextlib
from typing import Any

from behave import given, then, when

from cleveragents.core.retry_patterns import CircuitBreakerOpen, CircuitBreakerState

# ---------------------------------------------------------------------------
# Circuit breaker wiring steps
# ---------------------------------------------------------------------------


@given('the circuit breaker for "{name}" is open')
def step_set_cb_open(context: Any, name: str) -> None:
    cb = context.wiring.get_circuit_breaker(name)
    assert cb is not None, f"No circuit breaker for {name}"
    cb.state = CircuitBreakerState.OPEN
    cb.failure_count = cb.failure_threshold
    import time

    cb.last_failure_time = time.monotonic()


@then("a CircuitBreakerOpen exception should be raised")
def step_check_cb_open_error(context: Any) -> None:
    assert isinstance(context.exec_error, CircuitBreakerOpen)


@when('I reset the circuit breaker for "{name}"')
def step_reset_cb(context: Any, name: str) -> None:
    context.wiring.reset_circuit(name)


@then('the circuit breaker for "{name}" should be closed')
def step_check_cb_closed(context: Any, name: str) -> None:
    cb = context.wiring.get_circuit_breaker(name)
    assert cb is not None
    assert cb.state == "closed"


@when('I check the circuit status for "{name}"')
def step_check_circuit_status(context: Any, name: str) -> None:
    context.circuit_open = context.wiring.is_circuit_open(name)


@then("the circuit should not be open")
def step_check_circuit_not_open(context: Any) -> None:
    assert context.circuit_open is False


@given('the circuit breaker for "{name}" is in half-open state')
def step_set_cb_half_open(context: Any, name: str) -> None:
    cb = context.wiring.get_circuit_breaker(name)
    assert cb is not None, f"No circuit breaker for {name}"
    cb.state = CircuitBreakerState.HALF_OPEN
    cb.success_count_half_open = 0
    cb.failure_count = 0
    cb._half_open_permits = cb.half_open_max_successes


@when('I execute two successful calls through the wiring for "{name}"')
def step_execute_two_successes(context: Any, name: str) -> None:
    # Execute directly through the circuit breaker to avoid retry decorator
    cb = context.wiring.get_circuit_breaker(name)
    if cb is not None:
        cb.call(lambda: "ok")
        cb.call(lambda: "ok")


# ---------------------------------------------------------------------------
# Circuit breaker config wiring (C2, C3)
# ---------------------------------------------------------------------------


@then('the circuit breaker for "{name}" should have cooldown_seconds {value:g}')
def step_check_cb_cooldown_wired(context: Any, name: str, value: float) -> None:
    cb = context.wiring.get_circuit_breaker(name)
    assert cb is not None
    assert cb.cooldown_seconds == value


@then('the circuit breaker for "{name}" should have half_open_max_successes {value:d}')
def step_check_cb_half_open_max_wired(context: Any, name: str, value: int) -> None:
    cb = context.wiring.get_circuit_breaker(name)
    assert cb is not None
    assert cb.half_open_max_successes == value


# ---------------------------------------------------------------------------
# Circuit breaker last_failure_time cleared
# ---------------------------------------------------------------------------


@then('the circuit breaker for "{name}" last_failure_time should be None')
def step_check_cb_last_failure_cleared(context: Any, name: str) -> None:
    cb = context.wiring.get_circuit_breaker(name)
    assert cb is not None
    assert cb.last_failure_time is None


# ---------------------------------------------------------------------------
# B1: _on_success does not close open circuit
# ---------------------------------------------------------------------------


@given("I have a standalone CircuitBreaker instance")
def step_create_standalone_cb(context: Any) -> None:
    from cleveragents.core.retry_patterns import CircuitBreaker

    context.standalone_cb = CircuitBreaker(failure_threshold=3)


@given('the standalone circuit breaker state is set to "{state}"')
def step_set_standalone_cb_state(context: Any, state: str) -> None:
    context.standalone_cb.state = CircuitBreakerState(state)
    context.standalone_cb.failure_count = context.standalone_cb.failure_threshold


@when("I call _on_success on the standalone circuit breaker")
def step_call_on_success_standalone(context: Any) -> None:
    with context.standalone_cb._lock:
        context.standalone_cb._on_success()


@then('the standalone circuit breaker state should remain "{state}"')
def step_check_standalone_cb_state_remain(context: Any, state: str) -> None:
    assert context.standalone_cb.state == state


# ---------------------------------------------------------------------------
# B2: Half-open probe limit
# ---------------------------------------------------------------------------


@given("I have a standalone CircuitBreaker instance in half-open with 0 permits")
def step_create_cb_half_open_no_permits(context: Any) -> None:
    from cleveragents.core.retry_patterns import CircuitBreaker

    context.standalone_cb = CircuitBreaker(failure_threshold=3)
    context.standalone_cb.state = CircuitBreakerState.HALF_OPEN
    context.standalone_cb._half_open_permits = 0


@when("I call the standalone circuit breaker with a test function")
def step_call_standalone_cb(context: Any) -> None:
    from cleveragents.core.retry_patterns import CircuitBreakerOpen

    try:
        context.standalone_cb.call(lambda: "ok")
        context.standalone_cb_error = None
    except CircuitBreakerOpen as exc:
        context.standalone_cb_error = exc


@then("a CircuitBreakerOpen exception should be raised for probe limit")
def step_check_probe_limit_error(context: Any) -> None:
    assert context.standalone_cb_error is not None
    assert "probe limit" in str(context.standalone_cb_error)


# ---------------------------------------------------------------------------
# B4: Non-expected exceptions tracked by circuit breaker
# ---------------------------------------------------------------------------


@given("I have a standalone CircuitBreaker with expected_exception IOError")
def step_create_cb_with_ioerror(context: Any) -> None:
    from cleveragents.core.retry_patterns import CircuitBreaker

    context.standalone_cb = CircuitBreaker(
        failure_threshold=5, expected_exception=IOError
    )


@when("I call it with a function that raises RuntimeError")
def step_call_cb_with_runtime_error(context: Any) -> None:
    def raise_runtime() -> str:
        raise RuntimeError("boom")

    with contextlib.suppress(RuntimeError):
        context.standalone_cb.call(raise_runtime)


@when("I call it with a function that raises IOError")
def step_call_cb_with_ioerror(context: Any) -> None:
    def raise_io() -> str:
        raise OSError("boom")

    with contextlib.suppress(OSError):
        context.standalone_cb.call(raise_io)


@then("the standalone circuit breaker failure_count should be {count:d}")
def step_check_standalone_cb_failure_count(context: Any, count: int) -> None:
    assert context.standalone_cb.failure_count == count


# ---------------------------------------------------------------------------
# T-A2: Half-open failure re-opens circuit
# ---------------------------------------------------------------------------


@given("I have a standalone CircuitBreaker instance in half-open state with permits")
def step_create_cb_half_open_with_permits(context: Any) -> None:
    from cleveragents.core.retry_patterns import CircuitBreaker

    context.standalone_cb = CircuitBreaker(failure_threshold=3)
    context.standalone_cb.state = CircuitBreakerState.HALF_OPEN
    context.standalone_cb._half_open_permits = 2
    context.standalone_cb.success_count_half_open = 0


@when("I call it with a function that raises an expected exception")
def step_call_cb_with_expected_exception(context: Any) -> None:
    def failing_func() -> str:
        raise RuntimeError("half-open probe failed")

    with contextlib.suppress(RuntimeError):
        context.standalone_cb.call(failing_func)


@then('the standalone circuit breaker state should be "{state}"')
def step_check_standalone_state(context: Any, state: str) -> None:
    assert context.standalone_cb.state == state


# ---------------------------------------------------------------------------
# T-A3: Cooldown prevents premature reset
# ---------------------------------------------------------------------------


@given("I have a standalone CircuitBreaker with a recent half-open attempt")
def step_create_cb_with_recent_half_open(context: Any) -> None:
    import time

    from cleveragents.core.retry_patterns import CircuitBreaker

    context.standalone_cb = CircuitBreaker(
        failure_threshold=3,
        recovery_timeout=1.0,
        cooldown_seconds=9999.0,  # Very long cooldown
    )
    context.standalone_cb.state = CircuitBreakerState.OPEN
    context.standalone_cb.failure_count = 3
    context.standalone_cb.last_failure_time = time.monotonic() - 10.0  # Past recovery
    context.standalone_cb._last_half_open_time = time.monotonic()  # Just attempted


@when("I check _should_attempt_reset on the standalone circuit breaker")
def step_check_should_attempt_reset(context: Any) -> None:
    context.reset_allowed = context.standalone_cb._should_attempt_reset()


@then("the reset should be denied due to cooldown")
def step_check_reset_denied(context: Any) -> None:
    assert context.reset_allowed is False


# ---------------------------------------------------------------------------
# M6: failure_count reset on half-open entry
# ---------------------------------------------------------------------------


@given("I have a standalone CircuitBreaker that transitions to half-open")
def step_create_cb_for_half_open_transition(context: Any) -> None:
    import time as _time

    from cleveragents.core.retry_patterns import CircuitBreaker

    context.standalone_cb = CircuitBreaker(
        failure_threshold=3,
        recovery_timeout=0.01,
        cooldown_seconds=0.0,
    )
    # Simulate an open circuit with failures
    # S5: circuit breaker now uses time.monotonic() internally
    context.standalone_cb.state = CircuitBreakerState.OPEN
    context.standalone_cb.failure_count = 5
    context.standalone_cb.last_failure_time = _time.monotonic() - 1.0


@when("the circuit breaker transitions from open to half-open")
def step_transition_to_half_open(context: Any) -> None:
    # Calling a successful function should trigger open -> half-open transition
    result = context.standalone_cb.call(lambda: "probe_ok")
    context.half_open_transition_result = result


@then("the circuit breaker failure_count should be {count:d}")
def step_check_cb_failure_count_after_transition(context: Any, count: int) -> None:
    assert context.standalone_cb.failure_count == count


# ---------------------------------------------------------------------------
# Constructor validation: half_open_max_successes
# ---------------------------------------------------------------------------


@when("I create a CircuitBreaker with half_open_max_successes {value:d}")
def step_create_cb_invalid_half_open(context: Any, value: int) -> None:
    from cleveragents.core.retry_patterns import CircuitBreaker

    try:
        CircuitBreaker(failure_threshold=3, half_open_max_successes=value)
        context.cb_ctor_error = None
    except ValueError as exc:
        context.cb_ctor_error = exc


@then("a ValueError should be raised for invalid half_open_max_successes")
def step_check_cb_ctor_value_error(context: Any) -> None:
    assert isinstance(context.cb_ctor_error, ValueError)
    assert "half_open_max_successes" in str(context.cb_ctor_error)


# ---------------------------------------------------------------------------
# _should_attempt_reset with None last_failure_time
# ---------------------------------------------------------------------------


@given("I have a fresh circuit breaker with no failures")
def step_create_fresh_cb(context: Any) -> None:
    from cleveragents.core.retry_patterns import CircuitBreaker

    context.fresh_cb = CircuitBreaker(failure_threshold=3)
    # Ensure no failures have been recorded
    assert context.fresh_cb.last_failure_time is None


@when("I check _should_attempt_reset on the fresh circuit breaker")
def step_check_fresh_should_attempt_reset(context: Any) -> None:
    context.fresh_reset_result = context.fresh_cb._should_attempt_reset()


@then("_should_attempt_reset should return False")
def step_verify_fresh_reset_false(context: Any) -> None:
    assert context.fresh_reset_result is False


# ---------------------------------------------------------------------------
# Async half-open probe limit
# ---------------------------------------------------------------------------


@given("I have an async circuit breaker in half-open with 0 permits")
def step_create_async_cb_half_open_no_permits(context: Any) -> None:
    from cleveragents.core.retry_patterns import CircuitBreaker

    context.async_cb = CircuitBreaker(failure_threshold=3)
    context.async_cb.state = CircuitBreakerState.HALF_OPEN
    context.async_cb._half_open_permits = 0


@when("I make an async call on the half-open circuit breaker")
def step_async_call_half_open_no_permits(context: Any) -> None:
    import asyncio as _asyncio

    from cleveragents.core.retry_patterns import CircuitBreakerOpen

    async def _do() -> None:
        await context.async_cb.async_call(lambda: "ok")

    loop = _asyncio.new_event_loop()
    try:
        loop.run_until_complete(_do())
        context.async_probe_error = None
    except CircuitBreakerOpen as exc:
        context.async_probe_error = exc
    finally:
        loop.close()


@then("a CircuitBreakerOpen should be raised for async probe limit")
def step_check_async_probe_limit(context: Any) -> None:
    from cleveragents.core.retry_patterns import CircuitBreakerOpen

    assert isinstance(context.async_probe_error, CircuitBreakerOpen)
    assert "probe limit" in str(context.async_probe_error)


# ---------------------------------------------------------------------------
# Async CancelledError restores permits
# ---------------------------------------------------------------------------


@given("I have an async circuit breaker in half-open with 1 permit")
def step_create_async_cb_half_open_one_permit(context: Any) -> None:
    from cleveragents.core.retry_patterns import CircuitBreaker

    context.async_cb = CircuitBreaker(failure_threshold=3)
    context.async_cb.state = CircuitBreakerState.HALF_OPEN
    context.async_cb._half_open_permits = 1


@when("the async call is cancelled mid-execution")
def step_async_call_cancelled(context: Any) -> None:
    import asyncio as _asyncio

    async def cancelling_func() -> str:
        raise _asyncio.CancelledError()

    loop = _asyncio.new_event_loop()
    try:
        loop.run_until_complete(context.async_cb.async_call(cancelling_func))
        context.cancel_error = None
    except _asyncio.CancelledError:
        context.cancel_error = "cancelled"
    finally:
        loop.close()


@then("the half-open permit should be restored")
def step_check_permit_restored(context: Any) -> None:
    assert context.cancel_error == "cancelled"
    # The permit was decremented on entry and restored on CancelledError
    assert context.async_cb._half_open_permits == 1
