import numpy as np
from collections import deque

try:
    from sklearn.ensemble import IsolationForest
    HAS_IFOREST = True
except ImportError:
    HAS_IFOREST = False

class RNGAnomalyDetector:
    """
    Monitors the stream of RNG values to detect statistically bizarre 
    sequences that indicate the Casino might have altered the seed 
    or logic server-side.
    """
    def __init__(self, window_size=50, contamination=0.05):
        self.window_size = window_size
        self.contamination = contamination
        self.history = deque(maxlen=2000)
        self.model = None
        self.is_fitted = False
        
        if HAS_IFOREST:
            self.model = IsolationForest(
                n_estimators=100, 
                contamination=self.contamination,
                random_state=42
            )
            
    def _extract_window_features(self, sequence):
        """Extracts spatial/temporal properties of a sequence window."""
        seq = np.array(sequence)
        return [
            np.mean(seq),
            np.std(seq),
            np.max(seq) - np.min(seq),
            np.mean(np.abs(np.diff(seq))), # Average jump size
            np.sum(seq > 50) / len(seq),   # Ratio over 50
            np.median(seq)
        ]

    def add(self, roll):
        self.history.append(roll)
        
        # Fit the anomaly baseline when we have enough data
        if HAS_IFOREST and not self.is_fitted and len(self.history) >= 500:
            self.fit_baseline()

    def fit_baseline(self):
        """Trains what a 'Normal' RNG sequence looks like for this seed."""
        if not HAS_IFOREST or len(self.history) < self.window_size * 2:
            return
            
        data = list(self.history)
        features = []
        for i in range(len(data) - self.window_size):
            window = data[i:i+self.window_size]
            features.append(self._extract_window_features(window))
            
        if len(features) > 100:
            self.model.fit(features)
            self.is_fitted = True

    def check_anomaly(self):
        """
        Checks if the most recent window is anomalous compared to the baseline.
        Returns: ExceptionScore (-1 for Anomaly, 1 for Normal), and Raw Distance.
        """
        if not self.is_fitted or not HAS_IFOREST:
            return 1, 0.0
            
        if len(self.history) < self.window_size:
            return 1, 0.0
            
        recent_window = list(self.history)[-self.window_size:]
        feat = np.array([self._extract_window_features(recent_window)])
        
        prediction = self.model.predict(feat)[0]
        score = self.model.decision_function(feat)[0]
        
        return prediction, score
