from __future__ import annotations

import asyncio
from collections import defaultdict

from pydantic import BaseModel

from sentinely.intent import IntentScore


# ---------------------------------------------------------------------------
# Data models
# ---------------------------------------------------------------------------

class CircuitBreakerConfig(BaseModel):
    allow_threshold: int = 50       # below this: auto-allow
    flag_threshold: int = 80        # above this: hard block
    # between allow_threshold and flag_threshold: allow but flag async

    block_on_unknown: bool = True   # if scoring fails, block or allow?
    max_consecutive_flags: int = 3  # auto-block if this many flags in a row
    policy: str = "strict"          # "strict" | "permissive" | "monitor"


class CircuitDecision(BaseModel):
    action: str        # "allow" | "flag" | "block"
    risk_score: int
    reason: str
    attack_type: str
    should_notify: bool
    should_log: bool
    requires_human_approval: bool


# ---------------------------------------------------------------------------
# Pure decision function
# ---------------------------------------------------------------------------

def decide(score: IntentScore, config: CircuitBreakerConfig) -> CircuitDecision:
    """Evaluate an *IntentScore* against *config* and return a decision.

    This is a pure function with no side effects.
    """
    risk = score.risk_score
    reason = score.reason
    attack_type = score.attack_type

    # Resolve effective thresholds based on policy.
    allow_threshold = config.allow_threshold
    flag_threshold = config.flag_threshold

    if config.policy == "permissive":
        flag_threshold = 90

    # Low-confidence override: if we can't trust the score, flag it so a
    # human (or a later system) can decide.
    if score.confidence < 0.3 and config.block_on_unknown:
        return CircuitDecision(
            action="flag",
            risk_score=risk,
            reason=reason,
            attack_type=attack_type,
            should_notify=True,
            should_log=True,
            requires_human_approval=False,
        )

    # Monitor policy: never block, always allow, always log.
    if config.policy == "monitor":
        return CircuitDecision(
            action="allow",
            risk_score=risk,
            reason=reason,
            attack_type=attack_type,
            should_notify=False,
            should_log=True,
            requires_human_approval=False,
        )

    # 3-tier evaluation against (possibly adjusted) thresholds.
    if risk >= flag_threshold:
        return CircuitDecision(
            action="block",
            risk_score=risk,
            reason=reason,
            attack_type=attack_type,
            should_notify=True,
            should_log=True,
            requires_human_approval=True,
        )

    if risk >= allow_threshold:
        return CircuitDecision(
            action="flag",
            risk_score=risk,
            reason=reason,
            attack_type=attack_type,
            should_notify=True,
            should_log=True,
            requires_human_approval=False,
        )

    return CircuitDecision(
        action="allow",
        risk_score=risk,
        reason=reason,
        attack_type=attack_type,
        should_notify=False,
        should_log=True,
        requires_human_approval=False,
    )


# ---------------------------------------------------------------------------
# Stateful tracker (per-session consecutive flags)
# ---------------------------------------------------------------------------

class CircuitBreakerState:
    """Track consecutive flag decisions per session.

    Uses a plain dict internally.  Thread-safe via ``asyncio.Lock``.
    """

    def __init__(self) -> None:
        self._consecutive_flags: dict[str, int] = defaultdict(int)
        self._lock = asyncio.Lock()

    async def record_decision(self, session_id: str, decision: CircuitDecision) -> None:
        async with self._lock:
            if decision.action == "flag":
                self._consecutive_flags[session_id] += 1
            else:
                self._consecutive_flags[session_id] = 0

    async def get_consecutive_flags(self, session_id: str) -> int:
        async with self._lock:
            return self._consecutive_flags[session_id]

    async def should_auto_block(self, session_id: str, config: CircuitBreakerConfig) -> bool:
        async with self._lock:
            return self._consecutive_flags[session_id] >= config.max_consecutive_flags
