"""
Tests for the provider circuit breaker.

Validates:
- State transitions: CLOSED -> OPEN -> HALF_OPEN -> CLOSED
- Threshold-based tripping
- Cooldown recovery
- Stale cache serving when open
"""

import time

import pytest

from proxy.circuit_breaker import CircuitState, ProviderCircuitBreaker
from proxy.config import ProxyConfig


@pytest.fixture
def config():
    return ProxyConfig(
        circuit_breaker_threshold_ms=1000.0,
        circuit_breaker_cooldown_ms=100.0,  # Short for testing
        circuit_breaker_failure_threshold=2,
        circuit_breaker_recovery_threshold=2,
    )


@pytest.fixture
def cb(config):
    return ProviderCircuitBreaker(config)


class TestClosedState:
    def test_starts_closed(self, cb):
        assert cb.state == CircuitState.CLOSED
        assert not cb.is_open

    def test_allows_requests_when_closed(self, cb):
        assert cb.should_allow_request() is True

    def test_stays_closed_on_fast_success(self, cb):
        cb.record_success(latency_ms=100.0)
        assert cb.state == CircuitState.CLOSED

    def test_single_failure_doesnt_trip(self, cb):
        """Needs consecutive failures >= threshold to trip."""
        cb.record_failure()
        assert cb.state == CircuitState.CLOSED
        assert cb.should_allow_request() is True


class TestTripping:
    def test_trips_after_consecutive_failures(self, cb):
        cb.record_failure()
        cb.record_failure()  # 2nd failure hits threshold=2
        assert cb.state == CircuitState.OPEN
        assert cb.is_open

    def test_slow_success_counts_as_failure(self, cb):
        cb.record_success(latency_ms=5000.0)  # > 1000ms threshold
        cb.record_success(latency_ms=5000.0)
        assert cb.state == CircuitState.OPEN

    def test_blocks_requests_when_open(self, cb):
        cb.record_failure()
        cb.record_failure()
        assert cb.should_allow_request() is False

    def test_trip_stats(self, cb):
        cb.record_failure()
        cb.record_failure()
        stats = cb.get_stats()
        assert stats.trips == 1
        assert stats.current_state == CircuitState.OPEN


class TestRecovery:
    def test_enters_half_open_after_cooldown(self, cb):
        cb.record_failure()
        cb.record_failure()
        assert cb.state == CircuitState.OPEN

        # Wait for cooldown (100ms)
        time.sleep(0.15)
        assert cb.should_allow_request() is True  # Transitions to HALF_OPEN
        assert cb.state == CircuitState.HALF_OPEN

    def test_recovers_after_consecutive_successes(self, cb):
        cb.record_failure()
        cb.record_failure()

        time.sleep(0.15)
        cb.should_allow_request()  # -> HALF_OPEN

        cb.record_success(100.0)
        cb.record_success(100.0)  # 2nd success hits recovery_threshold=2
        assert cb.state == CircuitState.CLOSED

    def test_re_trips_on_failure_during_half_open(self, cb):
        cb.record_failure()
        cb.record_failure()

        time.sleep(0.15)
        cb.should_allow_request()  # -> HALF_OPEN

        cb.record_failure()
        assert cb.state == CircuitState.OPEN


class TestForceControls:
    def test_force_open(self, cb):
        cb.force_open()
        assert cb.state == CircuitState.OPEN

    def test_force_close(self, cb):
        cb.force_open()
        cb.force_close()
        assert cb.state == CircuitState.CLOSED
