"""
Unit Tests for Circuit Breaker

Tests the circuit breaker fault tolerance pattern.
"""

import pytest
import time
from altrus.core.fault.circuit_breaker import (
    CircuitBreaker,
    CircuitState,
    CircuitBreakerConfig
)


class TestCircuitBreaker:
    """Test suite for CircuitBreaker"""

    def test_initial_state(self):
        """Test that circuit breaker starts in CLOSED state"""
        cb = CircuitBreaker("test_module")
        assert cb.state == CircuitState.CLOSED

    def test_can_execute_when_closed(self):
        """Test that execution is allowed when circuit is CLOSED"""
        cb = CircuitBreaker("test_module")
        assert cb.can_execute() is True

    def test_open_after_failures(self):
        """Test that circuit opens after threshold failures"""
        config = CircuitBreakerConfig(failure_threshold=3)
        cb = CircuitBreaker("test_module", config)

        # Record failures
        cb.record_failure()
        assert cb.state == CircuitState.CLOSED

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

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

    def test_cannot_execute_when_open(self):
        """Test that execution is blocked when circuit is OPEN"""
        config = CircuitBreakerConfig(failure_threshold=1)
        cb = CircuitBreaker("test_module", config)

        cb.record_failure()
        assert cb.state == CircuitState.OPEN
        assert cb.can_execute() is False

    def test_half_open_after_timeout(self):
        """Test transition to HALF_OPEN after timeout"""
        config = CircuitBreakerConfig(
            failure_threshold=1,
            timeout=0.1  # 100ms timeout
        )
        cb = CircuitBreaker("test_module", config)

        # Open the circuit
        cb.record_failure()
        assert cb.state == CircuitState.OPEN

        # Wait for timeout
        time.sleep(0.15)

        # Next can_execute should transition to HALF_OPEN
        can_exec = cb.can_execute()
        assert cb.state == CircuitState.HALF_OPEN
        assert can_exec is True

    def test_close_after_successes_in_half_open(self):
        """Test that circuit closes after success threshold in HALF_OPEN"""
        config = CircuitBreakerConfig(
            failure_threshold=1,
            success_threshold=2,
            timeout=0.1
        )
        cb = CircuitBreaker("test_module", config)

        # Open the circuit
        cb.record_failure()
        time.sleep(0.15)

        # Transition to HALF_OPEN
        cb.can_execute()
        assert cb.state == CircuitState.HALF_OPEN

        # Record successes
        cb.record_success()
        assert cb.state == CircuitState.HALF_OPEN

        cb.record_success()
        assert cb.state == CircuitState.CLOSED

    def test_reopen_on_failure_in_half_open(self):
        """Test that circuit reopens on failure in HALF_OPEN"""
        config = CircuitBreakerConfig(
            failure_threshold=1,
            timeout=0.1
        )
        cb = CircuitBreaker("test_module", config)

        # Open the circuit
        cb.record_failure()
        time.sleep(0.15)

        # Transition to HALF_OPEN
        cb.can_execute()
        assert cb.state == CircuitState.HALF_OPEN

        # Record failure
        cb.record_failure()
        assert cb.state == CircuitState.OPEN

    def test_success_resets_failure_count(self):
        """Test that success resets failure count in CLOSED state"""
        config = CircuitBreakerConfig(failure_threshold=3)
        cb = CircuitBreaker("test_module", config)

        # Record some failures
        cb.record_failure()
        cb.record_failure()

        # Record success
        cb.record_success()

        # Should still be CLOSED
        assert cb.state == CircuitState.CLOSED

        # Now need 3 more failures to open
        cb.record_failure()
        cb.record_failure()
        assert cb.state == CircuitState.CLOSED

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

    def test_state_change_callback(self):
        """Test that state change callbacks are invoked"""
        cb = CircuitBreaker("test_module")

        callback_invoked = []

        def callback(old_state, new_state):
            callback_invoked.append((old_state, new_state))

        cb.register_state_change_callback(callback)

        # Force open
        cb.force_open()

        assert len(callback_invoked) == 1
        assert callback_invoked[0] == (CircuitState.CLOSED, CircuitState.OPEN)

    def test_metrics(self):
        """Test circuit breaker metrics"""
        config = CircuitBreakerConfig(failure_threshold=5)
        cb = CircuitBreaker("test_module", config)

        cb.record_failure()
        cb.record_failure()

        metrics = cb.get_metrics()

        assert metrics["module_id"] == "test_module"
        assert metrics["state"] == CircuitState.CLOSED.value
        assert metrics["failure_count"] == 2
        assert metrics["config"]["failure_threshold"] == 5

    def test_force_open(self):
        """Test manual circuit opening"""
        cb = CircuitBreaker("test_module")

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

    def test_force_close(self):
        """Test manual circuit closing"""
        cb = CircuitBreaker("test_module")

        cb.force_open()
        cb.force_close()
        assert cb.state == CircuitState.CLOSED


if __name__ == "__main__":
    pytest.main([__file__, "-v"])
