from __future__ import annotations

import asyncio

import pytest

from sentinely.circuit_breaker import (
    CircuitBreakerConfig,
    CircuitBreakerState,
    CircuitDecision,
    decide,
)
from sentinely.intent import IntentScore


# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------

def _score(risk: int = 50, confidence: float = 0.9, attack_type: str = "none") -> IntentScore:
    return IntentScore(
        risk_score=risk,
        reason="test reason",
        attack_type=attack_type,
        confidence=confidence,
    )


# ---------------------------------------------------------------------------
# Tests
# ---------------------------------------------------------------------------

def test_allow_low_risk():
    decision = decide(_score(risk=25), CircuitBreakerConfig())
    assert decision.action == "allow"
    assert decision.should_notify is False
    assert decision.requires_human_approval is False


def test_flag_medium_risk():
    decision = decide(_score(risk=65), CircuitBreakerConfig())
    assert decision.action == "flag"
    assert decision.should_notify is True
    assert decision.requires_human_approval is False


def test_block_high_risk():
    decision = decide(_score(risk=85), CircuitBreakerConfig())
    assert decision.action == "block"
    assert decision.requires_human_approval is True
    assert decision.should_notify is True


def test_monitor_policy_never_blocks():
    config = CircuitBreakerConfig(policy="monitor")
    decision = decide(_score(risk=99), config)
    assert decision.action == "allow"
    assert decision.should_log is True


def test_permissive_policy_raises_threshold():
    config = CircuitBreakerConfig(policy="permissive")
    decision = decide(_score(risk=85), config)
    # 85 is below the permissive flag_threshold of 90, so it's a flag not a block
    assert decision.action == "flag"


def test_low_confidence_flags():
    config = CircuitBreakerConfig(block_on_unknown=True)
    decision = decide(_score(risk=30, confidence=0.2), config)
    assert decision.action == "flag"
    assert decision.should_notify is True


@pytest.mark.asyncio
async def test_consecutive_flags_auto_block():
    state = CircuitBreakerState()
    config = CircuitBreakerConfig(max_consecutive_flags=3)

    flag_decision = CircuitDecision(
        action="flag",
        risk_score=65,
        reason="test",
        attack_type="none",
        should_notify=True,
        should_log=True,
        requires_human_approval=False,
    )

    for _ in range(3):
        await state.record_decision("sess-1", flag_decision)

    assert await state.get_consecutive_flags("sess-1") == 3
    assert await state.should_auto_block("sess-1", config) is True

    # A different session should not be affected.
    assert await state.should_auto_block("sess-other", config) is False
