"""Pure unit tests for _CircuitBreaker (no Redis/Postgres needed)."""

from unittest.mock import patch

from loom.graph.cache import _CircuitBreaker


def test_starts_closed_and_allows_requests():
    cb = _CircuitBreaker()
    assert cb.state == "closed"
    assert cb.should_allow_request is True


def test_failures_below_threshold_keep_closed():
    cb = _CircuitBreaker(failure_threshold=5)
    for _ in range(4):
        cb.record_failure()
    assert cb.state == "closed"
    assert cb.should_allow_request is True


def test_failures_at_threshold_open_circuit():
    cb = _CircuitBreaker(failure_threshold=5)
    for _ in range(5):
        cb.record_failure()
    assert cb.state == "open"


def test_open_circuit_blocks_requests():
    cb = _CircuitBreaker(failure_threshold=3)
    for _ in range(3):
        cb.record_failure()
    assert cb.state == "open"
    assert cb.should_allow_request is False


def test_cooldown_transitions_to_half_open():
    cb = _CircuitBreaker(failure_threshold=2, cooldown_seconds=10.0)
    with patch("loom.graph.cache.time.monotonic") as mock_mono:
        mock_mono.return_value = 100.0
        cb.record_failure()
        cb.record_failure()
        assert cb.state == "open"

        # Before cooldown — still open
        mock_mono.return_value = 109.0
        assert cb.state == "open"
        assert cb.should_allow_request is False

        # After cooldown — half-open
        mock_mono.return_value = 110.0
        assert cb.state == "half-open"
        assert cb.should_allow_request is True


def test_success_in_half_open_closes_circuit():
    cb = _CircuitBreaker(failure_threshold=2, cooldown_seconds=5.0)
    with patch("loom.graph.cache.time.monotonic") as mock_mono:
        mock_mono.return_value = 100.0
        cb.record_failure()
        cb.record_failure()
        assert cb.state == "open"

        # Advance past cooldown to reach half-open
        mock_mono.return_value = 106.0
        assert cb.state == "half-open"

        cb.record_success()
        assert cb.state == "closed"
        assert cb.should_allow_request is True


def test_failure_in_half_open_reopens_circuit():
    cb = _CircuitBreaker(failure_threshold=2, cooldown_seconds=5.0)
    with patch("loom.graph.cache.time.monotonic") as mock_mono:
        mock_mono.return_value = 100.0
        cb.record_failure()
        cb.record_failure()
        assert cb.state == "open"

        # Advance past cooldown to reach half-open
        mock_mono.return_value = 106.0
        assert cb.state == "half-open"

        mock_mono.return_value = 107.0
        cb.record_failure()
        assert cb.state == "open"
        assert cb.should_allow_request is False


def test_record_success_resets_failure_count():
    cb = _CircuitBreaker(failure_threshold=5)
    for _ in range(4):
        cb.record_failure()
    cb.record_success()
    assert cb.state == "closed"
    assert cb._consecutive_failures == 0

    # After reset, need full threshold again to open
    for _ in range(4):
        cb.record_failure()
    assert cb.state == "closed"


def test_custom_threshold_and_cooldown():
    cb = _CircuitBreaker(failure_threshold=2, cooldown_seconds=60.0)
    assert cb._failure_threshold == 2
    assert cb._cooldown_seconds == 60.0

    cb.record_failure()
    cb.record_failure()
    assert cb.state == "open"

    cb2 = _CircuitBreaker(failure_threshold=10, cooldown_seconds=1.0)
    for _ in range(9):
        cb2.record_failure()
    assert cb2.state == "closed"
    cb2.record_failure()
    assert cb2.state == "open"
