"""
Nonce search optimization and prediction
"""
from typing import Dict, Any, Optional, Tuple
import numpy as np
from dataclasses import dataclass
import time

@dataclass
class SearchConfig:
    batch_size: int = 1024 * 1024
    search_strategy: str = 'parallel_spiral'
    prediction_enabled: bool = True
    historical_window: int = 1000
    
class NoncePredictor:
    def __init__(self, window_size: int = 1000):
        self.window_size = window_size
        self.historical_nonces = []
        self.pattern_cache = {}
        
    def predict_next_range(self, current_nonce: int) -> Tuple[int, int]:
        """Predict next promising nonce range"""
        if len(self.historical_nonces) < self.window_size:
            # Not enough data, return default range
            return (current_nonce, current_nonce + 1000000)
            
        # Analyze patterns
        pattern = self._analyze_patterns()
        
        # Generate prediction
        predicted_start = self._predict_start_nonce(pattern)
        predicted_range = 1000000  # Default range size
        
        return (predicted_start, predicted_start + predicted_range)
        
    def _analyze_patterns(self) -> Dict[str, Any]:
        """Analyze historical nonce patterns"""
        patterns = {
            'gaps': [],
            'clusters': [],
            'trends': []
        }
        
        # Calculate gaps between successful nonces
        for i in range(1, len(self.historical_nonces)):
            gap = self.historical_nonces[i] - self.historical_nonces[i-1]
            patterns['gaps'].append(gap)
            
        # Find clusters
        clusters = self._find_clusters(self.historical_nonces)
        patterns['clusters'] = clusters
        
        # Analyze trends
        patterns['trends'] = self._analyze_trends(self.historical_nonces)
        
        return patterns
        
    def _find_clusters(self, nonces: List[int]) -> List[Dict]:
        """Find clusters of successful nonces"""
        clusters = []
        current_cluster = []
        
        for i in range(1, len(nonces)):
            if nonces[i] - nonces[i-1] < 1000:  # Close nonces
                current_cluster.append(nonces[i])
            else:
                if current_cluster:
                    clusters.append({
                        'start': current_cluster[0],
                        'end': current_cluster[-1],
                        'size': len(current_cluster)
                    })
                current_cluster = [nonces[i]]
                
        return clusters
        
    def _analyze_trends(self, nonces: List[int]) -> Dict[str, Any]:
        """Analyze trends in successful nonces"""
        if len(nonces) < 2:
            return {}
            
        diffs = np.diff(nonces)
        return {
            'mean_gap': float(np.mean(diffs)),
            'std_gap': float(np.std(diffs)),
            'increasing': bool(np.mean(np.diff(diffs)) > 0)
        }
        
    def _predict_start_nonce(self, pattern: Dict[str, Any]) -> int:
        """Predict promising start nonce based on pattern"""
        if not pattern['trends']:
            return 0
            
        if pattern['trends']['increasing']:
            # Trend is increasing, look further ahead
            last_nonce = self.historical_nonces[-1]
            return last_nonce + int(pattern['trends']['mean_gap'])
        else:
            # Look in recent successful ranges
            if pattern['clusters']:
                latest_cluster = pattern['clusters'][-1]
                return latest_cluster['start']
                
        return 0
        
class NonceSearchOptimizer:
    def __init__(self):
        self.config = SearchConfig()
        self.predictor = NoncePredictor()
        self.search_space = None
        
    def optimize_search(self) -> Dict[str, Any]:
        """Get optimized search configuration"""
        return {
            'search_strategy': self.config.search_strategy,
            'batch_size': self.config.batch_size,
            'prediction_model': self._get_prediction_model(),
            'historical_data': self._get_historical_patterns()
        }
        
    def _get_prediction_model(self) -> Dict[str, Any]:
        """Get current prediction model state"""
        if not self.config.prediction_enabled:
            return {}
            
        return {
            'window_size': self.predictor.window_size,
            'patterns': self.predictor.pattern_cache,
            'accuracy': self._calculate_prediction_accuracy()
        }
        
    def _get_historical_patterns(self) -> Dict[str, List]:
        """Get historical nonce patterns"""
        return {
            'successful_nonces': self.predictor.historical_nonces[-1000:],
            'clusters': self.predictor._find_clusters(
                self.predictor.historical_nonces
            ),
            'trends': self.predictor._analyze_trends(
                self.predictor.historical_nonces
            )
        }
        
    def _calculate_prediction_accuracy(self) -> float:
        """Calculate prediction model accuracy"""
        if not self.predictor.historical_nonces:
            return 0.0
            
        # Compare predicted vs actual
        correct_predictions = 0
        total_predictions = len(self.predictor.historical_nonces) - 1
        
        if total_predictions == 0:
            return 0.0
            
        return correct_predictions / total_predictions
        
    def update_search_space(self, start: int, end: int):
        """Update the current search space"""
        self.search_space = (start, end)
        
    def get_next_batch(self) -> Optional[Tuple[int, int]]:
        """Get next batch of nonces to search"""
        if not self.search_space:
            return None
            
        if self.config.prediction_enabled:
            return self.predictor.predict_next_range(
                self.search_space[0]
            )
        else:
            # Simple sequential batch
            start = self.search_space[0]
            end = min(start + self.config.batch_size, self.search_space[1])
            self.search_space = (end, self.search_space[1])
            return (start, end) if start < end else None
