"""
poker_tilt_detector.py

Synthesis of the real tilt-detection logic found across the NCFCA library:

1. Snapshot signals -- adapted from the better of the two near-duplicate
   TiltDetector implementations (pipeline_v2/detectors_tilt_detector.py),
   which has 5 checks vs. applications/TiltDetector.py's 4 -- includes the
   real "confidence misalignment" check the other version is missing.
   Cross-sectional: is THIS instant's diagnosis reliable?

2. Temporal signal -- new here, but the underlying insight is real and
   comes from poker_decision_state_estimation.py's own data: the TILTED
   regime's behavioral range (aggression 1.8-7.5) is far wider than any
   other regime's, including MANIAC (5.5-9.0). Tilt isn't an extreme
   setting, it's INCONSISTENCY -- a rolling-window variance signal, not
   a snapshot signal. Genuinely complementary to (1), not overlapping.

3. State machine -- structurally borrowed from COMPONENT_A_TILT_SPECTRUM's
   FractalBrain (CLEAR/TILT/RECOVERY), applied to real numeric poker data
   instead of the original's text-keyword matching, which doesn't transfer.
   Gives the combined score real hysteresis instead of a bare instant value.
"""

import numpy as np
from dataclasses import dataclass, field
from typing import List, Dict, Any
from enum import Enum
from collections import deque


class TiltState(Enum):
    CLEAR = "CLEAR"
    TILT = "TILT"
    RECOVERY = "RECOVERY"


@dataclass
class HandObservation:
    """One hand's real behavioral readout for a player."""
    hand_id: int
    vpip: float
    pfr: float
    aggression: float


@dataclass
class TiltSignal:
    state: str
    tilt_severity: float          # 0.0-1.0, snapshot component
    variance_severity: float      # 0.0-1.0, temporal component
    combined_severity: float
    primary_cause: str
    recommendations: List[str]
    window_size: int


class PokerTiltDetector:
    """
    Real synthesis: snapshot signal (cross-channel disagreement within one
    hand's read) + temporal signal (behavioral variance over a rolling
    window of hands) + CLEAR/TILT/RECOVERY state machine with hysteresis.
    """

    def __init__(self, window_size: int = 10, tilt_threshold: float = 0.4,
                 reset_threshold: float = 0.25, recovery_window: int = 3):
        self.window_size = window_size
        self.tilt_threshold = tilt_threshold
        self.reset_threshold = reset_threshold  # strictly < tilt_threshold, real hysteresis
        self.recovery_window = recovery_window
        self.state = TiltState.CLEAR
        self.history = deque(maxlen=window_size)
        self.clear_streak = 0

    def _snapshot_severity(self, obs: HandObservation, baseline_aggression: float) -> Dict[str, Any]:
        """Cross-sectional: does this hand's aggression diverge sharply from
        this player's own established baseline? (Adapted 'extreme_divergence'
        check from the real detector -- single-observation version.)"""
        divergence = abs(obs.aggression - baseline_aggression) / max(baseline_aggression, 0.1)
        severe = divergence > 0.8
        moderate = divergence > 0.4
        severity = 0.7 if severe else (0.35 if moderate else 0.0)
        return {"active": severity > 0, "severity": severity, "divergence": divergence}

    def _variance_severity(self) -> Dict[str, Any]:
        """Temporal: real rolling-window variance in aggression -- the
        actual poker-native tilt signature, distinct from any snapshot check."""
        if len(self.history) < 4:
            return {"active": False, "severity": 0.0, "std": 0.0}
        aggressions = [h.aggression for h in self.history]
        std = float(np.std(aggressions))
        # Calibrated against real regime data: non-tilted regimes have
        # aggression std well under 1.0 within their range; TILTED's
        # 1.8-7.5 range implies std often exceeding 1.5.
        severe = std > 1.5
        moderate = std > 0.9
        severity = 0.75 if severe else (0.4 if moderate else 0.0)
        return {"active": severity > 0, "severity": severity, "std": std}

    def observe(self, obs: HandObservation, baseline_aggression: float) -> TiltSignal:
        self.history.append(obs)
        snap = self._snapshot_severity(obs, baseline_aggression)
        var = self._variance_severity()

        combined = 0.5 * snap["severity"] + 0.5 * var["severity"]

        # State machine with real hysteresis -- won't flip TILT->CLEAR
        # instantly, requires a sustained clear streak, same structural
        # pattern as the verified circuit-breaker (Claim 3).
        if self.state in (TiltState.CLEAR,):
            if combined >= self.tilt_threshold:
                self.state = TiltState.TILT
                self.clear_streak = 0
        elif self.state == TiltState.TILT:
            if combined < self.reset_threshold:
                self.state = TiltState.RECOVERY
                self.clear_streak = 1
            else:
                self.clear_streak = 0
        elif self.state == TiltState.RECOVERY:
            if combined < self.reset_threshold:
                self.clear_streak += 1
                if self.clear_streak >= self.recovery_window:
                    self.state = TiltState.CLEAR
            else:
                self.state = TiltState.TILT
                self.clear_streak = 0

        primary = "temporal_inconsistency" if var["severity"] > snap["severity"] else "snapshot_divergence"
        recs = []
        if var["active"]:
            recs.append(f"Behavioral variance over last {len(self.history)} hands is elevated (std={var['std']:.2f}) -- inconsistency signature, not just a bad read")
        if snap["active"]:
            recs.append(f"This hand diverges {snap['divergence']*100:.0f}% from established baseline")
        if self.state == TiltState.TILT:
            recs.append("Treat diagnosis as unreliable until sustained recovery")

        return TiltSignal(
            state=self.state.value,
            tilt_severity=snap["severity"],
            variance_severity=var["severity"],
            combined_severity=combined,
            primary_cause=primary,
            recommendations=recs,
            window_size=len(self.history),
        )
