"""
Tests for insaits/circuit_breaker.py
"""

import time
import pytest
from insa_its.circuit_breaker import (
    AgentCircuitBreaker,
    STATE_CLOSED,
    STATE_OPEN,
    STATE_HALF_OPEN,
)


class TestCircuitBreakerInit:
    def test_default_params(self):
        cb = AgentCircuitBreaker()
        state = cb.get_state("any_agent")
        assert state["state"] == STATE_CLOSED
        assert state["anomaly_rate"] == 0.0
        assert state["window_size"] == 20
        assert state["failure_threshold"] == 0.4

    def test_custom_params(self):
        cb = AgentCircuitBreaker(window_size=10, failure_threshold=0.5, recovery_seconds=30)
        state = cb.get_state("x")
        assert state["window_size"] == 10
        assert state["failure_threshold"] == 0.5

    def test_invalid_window_size(self):
        with pytest.raises(ValueError):
            AgentCircuitBreaker(window_size=0)

    def test_invalid_threshold(self):
        with pytest.raises(ValueError):
            AgentCircuitBreaker(failure_threshold=0.0)
        with pytest.raises(ValueError):
            AgentCircuitBreaker(failure_threshold=1.5)

    def test_invalid_recovery(self):
        with pytest.raises(ValueError):
            AgentCircuitBreaker(recovery_seconds=-1)


class TestCircuitBreakerClosedState:
    def test_starts_closed(self):
        cb = AgentCircuitBreaker()
        assert cb.is_open("agent1") is False

    def test_stays_closed_below_threshold(self):
        cb = AgentCircuitBreaker(window_size=10, failure_threshold=0.5)
        # 4 anomalies out of 10 = 40% < 50% threshold
        for i in range(10):
            cb.record("a1", had_anomaly=(i < 4))
        assert cb.is_open("a1") is False

    def test_anomaly_rate_tracking(self):
        cb = AgentCircuitBreaker(window_size=10)
        for _ in range(5):
            cb.record("a1", had_anomaly=True)
        for _ in range(5):
            cb.record("a1", had_anomaly=False)
        state = cb.get_state("a1")
        assert state["anomaly_rate"] == 0.5


class TestCircuitBreakerTripping:
    def test_trips_when_threshold_exceeded(self):
        cb = AgentCircuitBreaker(window_size=5, failure_threshold=0.4)
        # 3 out of 5 = 60% > 40% threshold
        for i in range(5):
            cb.record("a1", had_anomaly=(i < 3))
        assert cb.is_open("a1") is True

    def test_state_is_open_after_trip(self):
        cb = AgentCircuitBreaker(window_size=5, failure_threshold=0.4)
        for _ in range(5):
            cb.record("a1", had_anomaly=True)
        state = cb.get_state("a1")
        assert state["state"] == STATE_OPEN
        assert "recovery_remaining_seconds" in state

    def test_does_not_trip_before_window_full(self):
        cb = AgentCircuitBreaker(window_size=10, failure_threshold=0.4)
        # Only 5 messages (window not full), even if all anomalies
        for _ in range(5):
            cb.record("a1", had_anomaly=True)
        assert cb.is_open("a1") is False


class TestCircuitBreakerRecovery:
    def test_transitions_to_half_open_after_recovery(self):
        cb = AgentCircuitBreaker(window_size=5, failure_threshold=0.4, recovery_seconds=0.1)
        for _ in range(5):
            cb.record("a1", had_anomaly=True)
        assert cb.is_open("a1") is True

        # Wait for recovery
        time.sleep(0.15)
        assert cb.is_open("a1") is False  # Now HALF_OPEN

    def test_half_open_success_closes(self):
        cb = AgentCircuitBreaker(window_size=5, failure_threshold=0.4, recovery_seconds=0.1)
        for _ in range(5):
            cb.record("a1", had_anomaly=True)

        time.sleep(0.15)
        cb.is_open("a1")  # Trigger transition to HALF_OPEN

        # Clean trial message
        cb.record("a1", had_anomaly=False)
        state = cb.get_state("a1")
        assert state["state"] == STATE_CLOSED

    def test_half_open_failure_reopens(self):
        cb = AgentCircuitBreaker(window_size=5, failure_threshold=0.4, recovery_seconds=0.1)
        for _ in range(5):
            cb.record("a1", had_anomaly=True)

        time.sleep(0.15)
        cb.is_open("a1")  # Trigger HALF_OPEN

        # Anomaly trial message
        cb.record("a1", had_anomaly=True)
        assert cb.is_open("a1") is True
        state = cb.get_state("a1")
        assert state["state"] == STATE_OPEN


class TestCircuitBreakerMultiAgent:
    def test_independent_per_agent(self):
        cb = AgentCircuitBreaker(window_size=5, failure_threshold=0.4)
        for _ in range(5):
            cb.record("bad_agent", had_anomaly=True)
            cb.record("good_agent", had_anomaly=False)

        assert cb.is_open("bad_agent") is True
        assert cb.is_open("good_agent") is False


class TestCircuitBreakerReset:
    def test_manual_reset(self):
        cb = AgentCircuitBreaker(window_size=5, failure_threshold=0.4)
        for _ in range(5):
            cb.record("a1", had_anomaly=True)
        assert cb.is_open("a1") is True

        cb.reset("a1")
        assert cb.is_open("a1") is False
        state = cb.get_state("a1")
        assert state["state"] == STATE_CLOSED

    def test_reset_all(self):
        cb = AgentCircuitBreaker(window_size=5, failure_threshold=0.4)
        for _ in range(5):
            cb.record("a1", had_anomaly=True)
            cb.record("a2", had_anomaly=True)

        cb.reset_all()
        assert cb.is_open("a1") is False
        assert cb.is_open("a2") is False
