#!/usr/bin/env python3
"""
ULTIMATE AI-ML HYBRID LOTTO GENERATOR V2.2 - ERWEITERT
Erweiterte Version mit verbessertem Feature Engineering

NEUE FEATURES V2.2:
- Erweiterte Feature Engineering (4 → 18+ Features)
- Gap-Analyse (Ziehungen seit letztem Auftreten)
- Momentum & Trend-Features
- Beziehungs-Features (Summen, Parität, Dekaden)
- Nachbarschafts-Features (Cluster-Erkennung)
- Zyklus-Features (Monat, Woche, Saison)

OPTIMIERUNGEN (V2.1):
- Progress Bar für Training
- Reduzierte Iterationen (150 -> 50)
- Model Caching mit intelligenter Update-Erkennung
- Paralleles Training (wenn möglich)
"""

import pandas as pd
import numpy as np
import random
from collections import Counter, defaultdict, deque
import datetime
import pickle
import os
import json
import sys

# ML Imports
try:
    from sklearn.ensemble import RandomForestRegressor, GradientBoostingRegressor
    from sklearn.neural_network import MLPRegressor
    from sklearn.preprocessing import StandardScaler
    from sklearn.model_selection import train_test_split, cross_val_score
    from sklearn.metrics import r2_score, mean_absolute_error, mean_squared_error
    import joblib
    ML_AVAILABLE = True
except ImportError:
    ML_AVAILABLE = False

# Deep Learning (optional) - PyTorch or TensorFlow
DEEP_LEARNING_AVAILABLE = False
try:
    import torch
    DEEP_LEARNING_AVAILABLE = True
except ImportError:
    try:
        import tensorflow as tf
        from tensorflow.keras.models import Sequential
        from tensorflow.keras.layers import LSTM, Dense, Dropout
        from tensorflow.keras.optimizers import Adam
        DEEP_LEARNING_AVAILABLE = True
    except ImportError:
        DEEP_LEARNING_AVAILABLE = False

# Import Deep Learning Engine (PyTorch-based for Python 3.14+ compatibility)
try:
    sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'utils'))
    from deep_learning_engine_pytorch import DeepLearningEngine, HybridDeepLearningPredictor
    DL_ENGINE_AVAILABLE = True
except ImportError:
    try:
        from deep_learning_engine import DeepLearningEngine, HybridDeepLearningPredictor
        DL_ENGINE_AVAILABLE = True
    except ImportError:
        DL_ENGINE_AVAILABLE = False
        DeepLearningEngine = None
        HybridDeepLearningPredictor = None


class UltimateAIMLHybridGenerator:
    """
    Ultimate Generator - OPTIMIZED VERSION
    """
    
    def __init__(self, data_path, fast_mode=True):
        self.data_path = data_path
        self.df = None
        self.fast_mode = fast_mode  # Schnellerer Modus
        
        # Configuration
        self.model_cache_path = os.path.join(os.path.dirname(data_path), "ultimate_ml_models")

        # Core Systems
        self.ai_ml_engine = AIMLEngine(fast_mode=fast_mode)
        self.pattern_engine = PatternEngine()
        self.hybrid_optimizer = HybridOptimizer(fast_mode=fast_mode)
        self.feature_engineer = FeatureEngineer()
        self.real_time_learner = RealTimeLearner(cache_path=self.model_cache_path)  # Mit Persistenz
        self.performance_tracker = PerformanceTracker()
        
        # Strategy Management (optimiert basierend auf Performance-Tests)
        self.strategy_weights = {
            'pure_ai': 0.25,           # Reduziert: 30% → 25%
            'pure_pattern': 0.15,      # Reduziert: 25% → 15% (schwächste Strategie)
            'hybrid_optimized': 0.40,  # Erhöht: 30% → 40% (beste Strategie)
            'ensemble_best': 0.20      # Erhöht: 15% → 20%
        }

        self.is_trained = False
        
        print("🚀 ULTIMATE AI-ML HYBRID GENERATOR V2.2 - ERWEITERT")
        print("=" * 70)
        print("🧠 AI/ML + 🎨 Pattern + ⚡ Hybrid + 📊 Real-Time Learning")
        print("✨ Neue Features: Gap-Analyse, Momentum, Trends, Beziehungen")
        if fast_mode:
            print("⚡ FAST MODE: Aktiviert (reduzierte Iterationen)")
        print("=" * 70)
        
        # Initialize
        self.initialize_system()
    
    def initialize_system(self):
        """Initialisiert alle Systeme."""
        print("\n🔧 SYSTEM INITIALIZATION...")
        print("-" * 70)
        
        # 1. Load Data
        print("📊 Loading data...", end=" ", flush=True)
        if not self._load_data():
            return
        print("✅")
        
        # 2. Feature Engineering
        print("🔍 Feature Engineering...", end=" ", flush=True)
        self.features_df = self.feature_engineer.create_features(self.df)
        print(f"✅ ({len(self.features_df.columns)} features)")
        
        # 3. Initialize Pattern Engine
        print("🎨 Analyzing patterns...", end=" ", flush=True)
        self.pattern_engine.initialize(self.df)
        print(f"✅ ({len(self.pattern_engine.pattern_frequencies)} patterns)")
        
        # 4. Initialize AI/ML Engine
        print("🧠 Initializing AI/ML Engine...")
        self.ai_ml_engine.data_file = self.data_path  # Setze data_file für Retraining-Check
        self.ai_ml_engine.initialize(self.df, self.features_df, self.model_cache_path)
        
        # 5. Initialize Hybrid Optimizer
        print("⚡ Initializing Hybrid Optimizer...", end=" ", flush=True)
        self.hybrid_optimizer.initialize(self.ai_ml_engine, self.pattern_engine)
        print("✅")
        
        # 6. Initialize Real-Time Learning
        print("📚 Activating Real-Time Learning...", end=" ", flush=True)
        self.real_time_learner.initialize(self.features_df)
        print("✅")
        
        self.is_trained = self.ai_ml_engine.is_trained
        
        print("\n✅ ALL SYSTEMS READY!")
        self._print_system_status()
    
    def _load_data(self):
        """Lädt und validiert Daten."""
        try:
            self.df = pd.read_csv(self.data_path, sep=';')
            
            if 'datum' in self.df.columns:
                self.df['datum'] = pd.to_datetime(self.df['datum'], format='%Y-%m-%d', errors='coerce')
                self.df = self.df.sort_values('datum')
            
            # Validate
            required_cols = ['Z1', 'Z2', 'Z3', 'Z4', 'Z5', 'Z6']
            if not all(col in self.df.columns for col in required_cols):
                print("❌ Fehlende Spalten")
                return False
            
            return True
            
        except Exception as e:
            print(f"❌ Error: {e}")
            return False
    
    def _print_system_status(self):
        """Zeigt System-Status."""
        print("\n📊 SYSTEM STATUS:")
        print(f"   🧠 ML Available: {'✅' if ML_AVAILABLE else '❌ (pip install scikit-learn)'}")
        print(f"   🚀 Deep Learning: {'✅ ACTIVE' if self.ai_ml_engine.use_deep_learning else '⚠️ Optional (pip install tensorflow)'}")
        print(f"   🎯 Models Trained: {'✅' if self.is_trained else '⚠️ Using frequency-based fallback'}")
        print(f"   📁 Data Size: {len(self.df):,} drawings")
        print(f"   🎨 Patterns: {len(self.pattern_engine.pattern_frequencies)}")
        print(f"   ⚡ Fast Mode: {'ON' if self.fast_mode else 'OFF'}")
        if self.ai_ml_engine.use_deep_learning:
            print(f"   🔮 LSTM: Hybrid Mode (RF 40% + DL 60%)")
    
    def generate_ultimate_tips(self, num_tips=10):
        """
        Generiert Ultimate Tips mit allen 4 Strategien.
        """
        print(f"\n🎯 GENERATING {num_tips} ULTIMATE TIPS")
        print("=" * 95)
        
        if len(self.df) == 0:
            print("❌ Keine Daten verfügbar")
            return []
        
        # Get AI Predictions
        print("🧠 Computing AI predictions...", end=" ", flush=True)
        ai_predictions = self.ai_ml_engine.predict_all_numbers(self.features_df)
        ai_predictions = self.real_time_learner.adjust_predictions(ai_predictions)
        print("✅")
        
        # Determine Strategy Distribution
        distribution = self._calculate_strategy_distribution(num_tips)
        
        print(f"\n📊 STRATEGY DISTRIBUTION:")
        for strategy, count in distribution.items():
            weight = self.strategy_weights[strategy]
            print(f"   {strategy.upper()}: {count} tips (Weight: {weight:.1%})")
        
        # Show Top AI Predictions
        self._show_top_ai_predictions(ai_predictions)
        
        # Generate Tips
        print(f"\n🎲 GENERATING TIPS...")
        all_tips = []
        tip_counter = 1
        
        # Generate by strategy with progress
        for strategy, count in distribution.items():
            strategy_name = strategy.upper()
            print(f"   {strategy_name}...", end=" ", flush=True)
            tips = self._generate_tips_by_strategy(
                strategy, count, tip_counter, ai_predictions
            )
            all_tips.extend(tips)
            tip_counter += len(tips)
            print(f"✅ ({count} tips)")
        
        # Output all tips
        print(f"\n📋 RESULTS:")
        print("=" * 95)
        print("Nr  6 Numbers              SZ  Strategy           AI-Score  Pattern-W  Confidence  Quality")
        print("-" * 95)
        
        for tip in all_tips:
            self._print_tip_line(tip)
        
        # Analysis
        self._analyze_portfolio(all_tips)
        
        # Update adaptive weights
        self._update_strategy_weights(all_tips)
        
        # Track performance
        self.performance_tracker.log_generated_tips(all_tips)
        
        return all_tips
    
    def _calculate_strategy_distribution(self, num_tips):
        """Berechnet Tip-Verteilung über Strategien."""
        distribution = {}
        remaining = num_tips
        
        # Calculate based on weights
        for strategy, weight in sorted(self.strategy_weights.items(), key=lambda x: x[1], reverse=True):
            count = max(1, int(num_tips * weight))
            count = min(count, remaining)
            distribution[strategy] = count
            remaining -= count
        
        # Distribute remaining
        if remaining > 0:
            best_strategy = max(self.strategy_weights.items(), key=lambda x: x[1])[0]
            distribution[best_strategy] += remaining
        
        return distribution
    
    def _show_top_ai_predictions(self, ai_predictions):
        """Zeigt Top AI Predictions."""
        print(f"\n🧠 TOP 10 AI PREDICTIONS:")
        sorted_preds = sorted(ai_predictions.items(), key=lambda x: x[1], reverse=True)[:10]
        
        for i, (num, score) in enumerate(sorted_preds, 1):
            status = "🔥" if score > 0.7 else "🌡️" if score > 0.5 else "💧"
            print(f"   {i:2}. Zahl {num:2}: {score:.4f} {status}")
    
    def _generate_tips_by_strategy(self, strategy, count, start_number, ai_predictions):
        """Generiert Tips für spezifische Strategie."""
        tips = []
        
        for i in range(count):
            tip_number = start_number + i
            
            if strategy == 'pure_ai':
                tip = self._generate_pure_ai_tip(tip_number, ai_predictions)
            elif strategy == 'pure_pattern':
                tip = self._generate_pure_pattern_tip(tip_number, ai_predictions)
            elif strategy == 'hybrid_optimized':
                tip = self._generate_hybrid_tip(tip_number, ai_predictions)
            else:  # ensemble_best
                tip = self._generate_ensemble_tip(tip_number, ai_predictions)
            
            tips.append(tip)
        
        return tips
    
    def _generate_pure_ai_tip(self, tip_number, ai_predictions):
        """Pure AI-ML Strategie."""
        random.seed(42 + tip_number * 13)
        
        sorted_preds = sorted(ai_predictions.items(), key=lambda x: x[1], reverse=True)
        top_candidates = [num for num, score in sorted_preds[:30]]
        
        selected = []
        
        for position in range(6):
            candidates = [n for n in top_candidates if n not in selected]
            
            if not candidates:
                candidates = [n for n in range(1, 50) if n not in selected]
            
            if candidates:
                weights = [ai_predictions.get(c, 0.1) + random.random() * 0.15 for c in candidates]
                if selected:
                    for i, c in enumerate(candidates):
                        diversity = self._calculate_diversity_score(c, selected)
                        weights[i] *= (1 + diversity * 0.3)
                
                choice = random.choices(candidates, weights=weights)[0]
                selected.append(choice)
        
        selected = sorted(selected)
        superzahl = self._get_smart_superzahl(tip_number)
        
        ai_score = np.mean([ai_predictions.get(n, 0.1) for n in selected])
        pattern_weight = self.pattern_engine.calculate_pattern_weight(selected)
        confidence = ai_score * 0.8 + pattern_weight * 0.2
        quality = self._calculate_quality_score(selected, ai_predictions, pattern_weight)
        
        return {
            'tip_number': tip_number,
            'numbers': selected,
            'superzahl': superzahl,
            'strategy': 'PURE-AI',
            'ai_score': ai_score,
            'pattern_weight': pattern_weight,
            'confidence': confidence,
            'quality': quality
        }
    
    def _generate_pure_pattern_tip(self, tip_number, ai_predictions):
        """Pure Pattern Strategie."""
        random.seed(42 + tip_number * 17)
        
        top_patterns = self.pattern_engine.get_top_patterns(10)
        target_pattern = top_patterns[tip_number % len(top_patterns)] if top_patterns else 'NNMMHH'
        
        selected = self.pattern_engine.generate_for_pattern(target_pattern, tip_number)
        superzahl = self._get_smart_superzahl(tip_number)
        
        pattern_weight = self.pattern_engine.calculate_pattern_weight(selected)
        ai_score = np.mean([ai_predictions.get(n, 0.1) for n in selected])
        confidence = pattern_weight * 0.7 + ai_score * 0.3
        quality = self._calculate_quality_score(selected, ai_predictions, pattern_weight)
        
        return {
            'tip_number': tip_number,
            'numbers': selected,
            'superzahl': superzahl,
            'strategy': 'PURE-PATTERN',
            'ai_score': ai_score,
            'pattern_weight': pattern_weight,
            'confidence': confidence,
            'quality': quality,
            'target_pattern': target_pattern
        }
    
    def _generate_hybrid_tip(self, tip_number, ai_predictions):
        """Hybrid Strategie."""
        result = self.hybrid_optimizer.optimize(tip_number, ai_predictions)
        
        superzahl = self._get_smart_superzahl(tip_number)
        quality = self._calculate_quality_score(
            result['numbers'], ai_predictions, result['pattern_weight']
        )
        
        return {
            'tip_number': tip_number,
            'numbers': result['numbers'],
            'superzahl': superzahl,
            'strategy': 'HYBRID-OPT',
            'ai_score': result['ai_score'],
            'pattern_weight': result['pattern_weight'],
            'confidence': result['confidence'],
            'quality': quality
        }
    
    def _generate_ensemble_tip(self, tip_number, ai_predictions):
        """Ensemble Strategie."""
        random.seed(42 + tip_number * 23)
        
        sorted_preds = sorted(ai_predictions.items(), key=lambda x: x[1], reverse=True)
        ai_candidates = [num for num, score in sorted_preds[:20]]
        
        top_patterns = self.pattern_engine.get_top_patterns(3)
        pattern_candidates = []
        for pattern in top_patterns[:2]:
            pcands = self.pattern_engine.generate_for_pattern(pattern, tip_number)
            pattern_candidates.extend(pcands)
        
        all_candidates = list(set(ai_candidates + pattern_candidates))
        
        selected = []
        for position in range(6):
            best_candidate = None
            best_score = -1
            
            for candidate in all_candidates:
                if candidate not in selected:
                    ai_s = ai_predictions.get(candidate, 0.1)
                    pattern_s = self.pattern_engine.get_number_pattern_score(candidate)
                    diversity_s = self._calculate_diversity_score(candidate, selected) if selected else 0.5
                    
                    ensemble_score = (ai_s * 0.4 + pattern_s * 0.3 + diversity_s * 0.3)
                    
                    if ensemble_score > best_score:
                        best_score = ensemble_score
                        best_candidate = candidate
            
            if best_candidate:
                selected.append(best_candidate)
            else:
                available = [n for n in range(1, 50) if n not in selected]
                if available:
                    selected.append(random.choice(available))
        
        selected = sorted(selected[:6])
        superzahl = self._get_smart_superzahl(tip_number)
        
        ai_score = np.mean([ai_predictions.get(n, 0.1) for n in selected])
        pattern_weight = self.pattern_engine.calculate_pattern_weight(selected)
        confidence = (ai_score + pattern_weight) / 2
        quality = self._calculate_quality_score(selected, ai_predictions, pattern_weight)
        
        return {
            'tip_number': tip_number,
            'numbers': selected,
            'superzahl': superzahl,
            'strategy': 'ENSEMBLE',
            'ai_score': ai_score,
            'pattern_weight': pattern_weight,
            'confidence': confidence,
            'quality': quality
        }
    
    def _calculate_diversity_score(self, candidate, selected):
        """Berechnet Diversitäts-Score."""
        if not selected:
            return 0.5
        
        distances = [abs(candidate - s) for s in selected]
        avg_distance = np.mean(distances)
        distance_score = min(avg_distance / 10.0, 1.0)
        
        def get_range(n):
            if n <= 16: return 0
            elif n <= 32: return 1
            else: return 2
        
        candidate_range = get_range(candidate)
        selected_ranges = [get_range(s) for s in selected]
        range_counts = Counter(selected_ranges)
        
        if range_counts[candidate_range] < 2:
            range_score = 0.8
        elif range_counts[candidate_range] == 2:
            range_score = 0.4
        else:
            range_score = 0.1
        
        even_count = sum(1 for s in selected if s % 2 == 0)
        candidate_even = candidate % 2 == 0
        
        if len(selected) < 3:
            balance_score = 0.5
        elif (even_count < 3 and candidate_even) or (even_count >= 3 and not candidate_even):
            balance_score = 0.7
        else:
            balance_score = 0.3
        
        return (distance_score * 0.4 + range_score * 0.4 + balance_score * 0.2)
    
    def _get_smart_superzahl(self, tip_number):
        """Intelligente Superzahl."""
        base_sz = [7, 6, 3, 2, 0, 1, 4, 5, 8, 9]
        
        try:
            if 'SZ' in self.df.columns and len(self.df) > 10:
                recent_sz = self.df['SZ'].tail(30).dropna()
                if len(recent_sz) > 0:
                    sz_freq = Counter(recent_sz)
                    top_sz = [int(sz) for sz, _ in sz_freq.most_common(7) if 0 <= sz <= 9]
                    if top_sz:
                        base_sz = top_sz + [7, 6, 3]
        except:
            pass
        
        if tip_number <= 3:
            sz = base_sz[0]
        else:
            sz = base_sz[tip_number % len(base_sz)]
        
        return int(sz) if 0 <= int(sz) <= 9 else 7
    
    def _calculate_quality_score(self, numbers, ai_predictions, pattern_weight):
        """Berechnet Qualitäts-Score."""
        ai_scores = [ai_predictions.get(n, 0.1) for n in numbers]
        ai_quality = np.mean(ai_scores) * (1 + np.std(ai_scores))
        
        pattern_quality = pattern_weight
        
        distances = []
        for i, n1 in enumerate(numbers):
            for n2 in numbers[i+1:]:
                distances.append(abs(n1 - n2))
        diversity_quality = min(np.mean(distances) / 8.0, 1.0) if distances else 0.5
        
        quality = (ai_quality * 0.4 + pattern_quality * 0.3 + diversity_quality * 0.3)
        return min(quality, 1.0)
    
    def _print_tip_line(self, tip):
        """Druckt Tip-Zeile."""
        zahlen_str = '-'.join([f"{n:2d}" for n in tip['numbers']])
        
        quality_emoji = "⭐" if tip['quality'] > 0.7 else "🌟" if tip['quality'] > 0.5 else "💫"
        
        print(f"{tip['tip_number']:2d}  {zahlen_str}  {tip['superzahl']:2d}  "
              f"{tip['strategy']:<14}  {tip['ai_score']:.4f}    {tip['pattern_weight']:.4f}     "
              f"{tip['confidence']:.4f}    {quality_emoji} {tip['quality']:.3f}")
    
    def _analyze_portfolio(self, tips):
        """Analysiert Tip-Portfolio."""
        print(f"\n📊 PORTFOLIO ANALYSIS:")
        print("=" * 70)
        
        strategy_stats = defaultdict(lambda: {'count': 0, 'avg_conf': [], 'avg_quality': []})
        
        for tip in tips:
            strategy = tip['strategy']
            strategy_stats[strategy]['count'] += 1
            strategy_stats[strategy]['avg_conf'].append(tip['confidence'])
            strategy_stats[strategy]['avg_quality'].append(tip['quality'])
        
        for strategy, stats in strategy_stats.items():
            avg_conf = np.mean(stats['avg_conf'])
            avg_qual = np.mean(stats['avg_quality'])
            print(f"{strategy}:")
            print(f"   Count: {stats['count']}, Confidence: {avg_conf:.4f}, Quality: {avg_qual:.4f}")
        
        best_by_confidence = max(tips, key=lambda x: x['confidence'])
        best_by_quality = max(tips, key=lambda x: x['quality'])
        
        print(f"\n⭐ TOP RECOMMENDATIONS:")
        print(f"\n🎯 Highest Confidence:")
        zahlen = '-'.join([f"{n:2d}" for n in best_by_confidence['numbers']])
        print(f"   Tip #{best_by_confidence['tip_number']}: {zahlen} + SZ {best_by_confidence['superzahl']}")
        print(f"   Strategy: {best_by_confidence['strategy']}, Confidence: {best_by_confidence['confidence']:.4f}")
        
        print(f"\n💎 Highest Quality:")
        zahlen = '-'.join([f"{n:2d}" for n in best_by_quality['numbers']])
        print(f"   Tip #{best_by_quality['tip_number']}: {zahlen} + SZ {best_by_quality['superzahl']}")
        print(f"   Strategy: {best_by_quality['strategy']}, Quality: {best_by_quality['quality']:.4f}")
    
    def _update_strategy_weights(self, tips):
        """Updated Strategy-Gewichte."""
        strategy_performance = defaultdict(list)
        
        for tip in tips:
            perf_score = (tip['confidence'] * 0.6 + tip['quality'] * 0.4)
            strategy_performance[tip['strategy']].append(perf_score)
        
        strategy_avg = {}
        total = 0
        
        for strategy in ['pure_ai', 'pure_pattern', 'hybrid_optimized', 'ensemble_best']:
            strategy_key = {
                'pure_ai': 'PURE-AI',
                'pure_pattern': 'PURE-PATTERN',
                'hybrid_optimized': 'HYBRID-OPT',
                'ensemble_best': 'ENSEMBLE'
            }[strategy]
            
            if strategy_key in strategy_performance:
                avg = np.mean(strategy_performance[strategy_key])
                strategy_avg[strategy] = avg
                total += avg
        
        if total > 0:
            for strategy in self.strategy_weights:
                if strategy in strategy_avg:
                    new_weight = strategy_avg[strategy] / total
                    self.strategy_weights[strategy] = (
                        self.strategy_weights[strategy] * 0.7 + new_weight * 0.3
                    )
        
        print(f"\n🔄 UPDATED STRATEGY WEIGHTS:")
        for strategy, weight in sorted(self.strategy_weights.items(), key=lambda x: x[1], reverse=True):
            print(f"   {strategy.upper()}: {weight:.1%}")


# ============================================================================
# OPTIMIZED SUPPORT CLASSES
# ============================================================================

class AIMLEngine:
    """AI/ML Engine - OPTIMIZED mit Deep Learning Support."""

    def __init__(self, fast_mode=True):
        self.models = {}
        self.trained_models = {}
        self.scaler = StandardScaler()
        self.is_trained = False
        self.fast_mode = fast_mode

        # Deep Learning
        self.dl_engine = None
        self.hybrid_predictor = None
        self.use_deep_learning = DL_ENGINE_AVAILABLE and DEEP_LEARNING_AVAILABLE
    
    def initialize(self, df, features_df, cache_path):
        """Initialisiert AI/ML System mit intelligentem Retraining."""
        self.cache_path = cache_path
        self.data_file = None  # Wird vom Generator gesetzt
        self.df = df  # Store for DL
        self.features_df = features_df  # Store for DL

        if not ML_AVAILABLE:
            print("   ⚠️ ML not available - using frequency-based fallback")
            return

        # Setup models
        n_estimators = 100 if self.fast_mode else 200
        self.models = {
            'random_forest': RandomForestRegressor(
                n_estimators=n_estimators, max_depth=8, random_state=42, n_jobs=-1
            ),
            'gradient_boost': GradientBoostingRegressor(
                n_estimators=n_estimators, learning_rate=0.1, max_depth=5, random_state=42
            )
        }

        # Intelligentes Retraining
        cache_file = os.path.join(cache_path, "trained_models.pkl")
        needs_retrain = self._needs_retraining(cache_file)

        if os.path.exists(cache_file) and not needs_retrain:
            print("   📂 Loading cached models...", end=" ", flush=True)
            self._load_models()
            print("✅")
        elif len(df) >= 50:
            if needs_retrain:
                print("   🔄 Data updated - retraining models...")
            self._train_models(df, features_df)
        else:
            print("   ⚠️ Insufficient data - using fallback")

        # Initialize Deep Learning
        if self.use_deep_learning:
            print("   🧠 Initializing Deep Learning (LSTM)...", end=" ", flush=True)
            try:
                self.dl_engine = DeepLearningEngine(
                    num_numbers=49,
                    sequence_length=20,
                    cache_dir=os.path.join(cache_path, 'deep_learning'),
                    fast_mode=self.fast_mode
                )

                # Train or load LSTM
                self.dl_engine.train(df, features_df, force_retrain=needs_retrain)

                # Create hybrid predictor
                self.hybrid_predictor = HybridDeepLearningPredictor(
                    dl_engine=self.dl_engine,
                    rf_weight=0.4,
                    dl_weight=0.6
                )
                print("✅")
            except Exception as e:
                print(f"⚠️  DL init failed: {e}")
                self.use_deep_learning = False
        else:
            if not DEEP_LEARNING_AVAILABLE:
                print("   ℹ️  Deep Learning disabled (TensorFlow not installed)")
            elif not DL_ENGINE_AVAILABLE:
                print("   ℹ️  Deep Learning Engine not available")

    def _needs_retraining(self, cache_file):
        """Prüft ob Retraining nötig ist."""
        if not os.path.exists(cache_file):
            return True

        # Prüfe ob CSV neuer als Cache
        data_file = self.data_file or os.path.join(
            os.path.dirname(self.cache_path),
            "AlleLottozahlen.csv"
        )

        if os.path.exists(data_file):
            cache_mtime = os.path.getmtime(cache_file)
            data_mtime = os.path.getmtime(data_file)

            if data_mtime > cache_mtime:
                print("   🔍 Detected updated data (CSV newer than cache)")
                return True

        return False
    
    def _train_models(self, df, features_df):
        """Trainiert ML-Modelle mit Progress."""
        print("   🎯 Training AI models...")
        
        training_results = {}
        
        # Train only for most frequently drawn numbers (optimization)
        number_freq = Counter()
        for _, row in df.iterrows():
            for col in ['Z1', 'Z2', 'Z3', 'Z4', 'Z5', 'Z6']:
                number_freq[int(row[col])] += 1
        
        # Train for top 40 numbers (saves time)
        top_numbers = [num for num, _ in number_freq.most_common(40)]
        
        total = len(top_numbers)
        for idx, number in enumerate(top_numbers, 1):
            # Progress indicator
            progress = idx / total
            bar_length = 30
            filled = int(bar_length * progress)
            bar = '█' * filled + '░' * (bar_length - filled)
            print(f"\r   [{bar}] {progress:.0%} - Training number {number}", end='', flush=True)
            
            X, y = self._prepare_training_data(df, features_df, number)
            
            if len(X) < 20:
                continue
            
            X_train, X_test, y_train, y_test = train_test_split(
                X, y, test_size=0.2, random_state=42
            )
            
            scaler = StandardScaler()
            X_train_scaled = scaler.fit_transform(X_train)
            X_test_scaled = scaler.transform(X_test)
            
            number_models = {}
            
            for model_name, model in self.models.items():
                try:
                    model.fit(X_train_scaled, y_train)
                    y_pred = model.predict(X_test_scaled)
                    score = r2_score(y_test, y_pred)
                    
                    number_models[model_name] = {
                        'model': model,
                        'scaler': scaler,
                        'score': max(score, 0.1)
                    }
                except:
                    pass
            
            if number_models:
                training_results[number] = number_models
        
        print()  # New line after progress bar
        
        self.trained_models = training_results
        self._save_models()
        self.is_trained = True
        print(f"   ✅ AI models trained ({len(training_results)} numbers)")
    
    def _prepare_training_data(self, df, features_df, number):
        """Bereitet Trainingsdaten vor - SIMPLIFIED."""
        X = []
        y = []
        
        window_size = 3  # Reduced from 5
        
        for i in range(window_size, min(len(features_df), 100)):  # Max 100 samples
            features = []
            for j in range(i - window_size, i):
                row_features = features_df.iloc[j].values.tolist()
                features.extend(row_features[:4])  # Only first 4 features
            
            X.append(features)
            
            current_numbers = [df.iloc[i][f'Z{k}'] for k in range(1, 7)]
            y.append(1 if number in current_numbers else 0)
        
        return np.array(X), np.array(y)
    
    def predict_all_numbers(self, features_df):
        """Vorhersage für alle Zahlen - mit Deep Learning Hybrid."""
        predictions = {}

        # Get RandomForest predictions
        if not self.is_trained or not self.trained_models:
            # Fallback
            for num in range(1, 50):
                predictions[num] = 0.2 + random.random() * 0.3
        else:
            current_features = self._get_current_features(features_df)

            for number in range(1, 50):
                if number not in self.trained_models:
                    predictions[number] = 0.15 + (number % 10) * 0.03
                    continue

                number_models = self.trained_models[number]
                ensemble_pred = 0
                total_weight = 0

                for model_name, model_data in number_models.items():
                    try:
                        model = model_data['model']
                        scaler = model_data['scaler']
                        score = model_data['score']

                        features_scaled = scaler.transform([current_features])
                        pred = model.predict(features_scaled)[0]

                        weight = score
                        ensemble_pred += pred * weight
                        total_weight += weight
                    except:
                        pass

                if total_weight > 0:
                    predictions[number] = np.clip(ensemble_pred / total_weight, 0, 1)
                else:
                    predictions[number] = 0.2 + random.random() * 0.2

        # Use Deep Learning Hybrid if available
        if self.use_deep_learning and self.hybrid_predictor:
            try:
                predictions = self.hybrid_predictor.predict(
                    predictions,
                    self.df,
                    self.features_df
                )
            except Exception as e:
                print(f"⚠️  DL prediction failed: {e}, using RF only")

        return predictions
    
    def _get_current_features(self, features_df):
        """Holt aktuelle Features - SIMPLIFIED."""
        if len(features_df) == 0:
            return [0.0] * 12
        
        features = []
        for i in range(max(0, len(features_df)-3), len(features_df)):
            row_features = features_df.iloc[i].values.tolist()
            features.extend(row_features[:4])
        
        while len(features) < 12:
            features.append(0.0)
        
        return features[:12]
    
    def _save_models(self):
        """Speichert Modelle."""
        os.makedirs(self.cache_path, exist_ok=True)
        filepath = os.path.join(self.cache_path, "trained_models.pkl")
        
        with open(filepath, 'wb') as f:
            pickle.dump(self.trained_models, f)
    
    def _load_models(self):
        """Lädt Modelle."""
        filepath = os.path.join(self.cache_path, "trained_models.pkl")
        
        try:
            with open(filepath, 'rb') as f:
                self.trained_models = pickle.load(f)
            self.is_trained = True
        except:
            pass


class PatternEngine:
    """Pattern Analysis Engine."""
    
    def __init__(self):
        self.pattern_frequencies = Counter()
        self.pattern_weights = {}
        self.number_patterns = defaultdict(list)
    
    def initialize(self, df):
        """Initialisiert Pattern Engine."""
        self._analyze_patterns(df)
        self._analyze_number_patterns(df)
    
    def _analyze_patterns(self, df):
        """Analysiert Patterns."""
        for _, row in df.iterrows():
            numbers = sorted([row['Z1'], row['Z2'], row['Z3'], 
                            row['Z4'], row['Z5'], row['Z6']])
            pattern = self._get_pattern(numbers)
            self.pattern_frequencies[pattern] += 1
        
        total = sum(self.pattern_frequencies.values())
        for pattern, count in self.pattern_frequencies.items():
            self.pattern_weights[pattern] = count / total if total > 0 else 0
    
    def _analyze_number_patterns(self, df):
        """Analysiert Zahlen-Muster."""
        for _, row in df.tail(100).iterrows():  # Only last 100 for speed
            numbers = [row['Z1'], row['Z2'], row['Z3'], 
                      row['Z4'], row['Z5'], row['Z6']]
            for num in numbers:
                self.number_patterns[num].append(numbers)
    
    def _get_pattern(self, numbers):
        """Bestimmt Pattern."""
        pattern = ""
        for num in numbers:
            if num <= 16:
                pattern += "N"
            elif num <= 32:
                pattern += "M"
            else:
                pattern += "H"
        return pattern
    
    def calculate_pattern_weight(self, numbers):
        """Berechnet Pattern-Gewicht."""
        pattern = self._get_pattern(sorted(numbers))
        return self.pattern_weights.get(pattern, 0.01)
    
    def get_top_patterns(self, count):
        """Liefert Top Patterns."""
        return [p for p, _ in self.pattern_frequencies.most_common(count)]
    
    def generate_for_pattern(self, target_pattern, seed):
        """Generiert für Pattern."""
        random.seed(42 + seed * 19)
        
        ranges = {
            'N': list(range(1, 17)),
            'M': list(range(17, 33)),
            'H': list(range(33, 50))
        }
        
        pattern_counts = Counter(target_pattern)
        selected = []
        
        for char, count in pattern_counts.items():
            if char in ranges:
                available = [n for n in ranges[char] if n not in selected]
                if len(available) >= count:
                    selected.extend(random.sample(available, count))
        
        while len(selected) < 6:
            available = [n for n in range(1, 50) if n not in selected]
            if available:
                selected.append(random.choice(available))
        
        return sorted(selected[:6])
    
    def get_number_pattern_score(self, number):
        """Pattern-Score für Zahl."""
        if number not in self.number_patterns:
            return 0.1
        
        frequency = len(self.number_patterns[number])
        max_freq = max(len(patterns) for patterns in self.number_patterns.values()) if self.number_patterns else 1
        
        return min(frequency / max_freq, 1.0) if max_freq > 0 else 0.1
    
    def update_with_drawing(self, drawing):
        """Update mit Ziehung."""
        numbers = sorted([drawing['Z1'], drawing['Z2'], drawing['Z3'],
                         drawing['Z4'], drawing['Z5'], drawing['Z6']])
        pattern = self._get_pattern(numbers)
        self.pattern_frequencies[pattern] += 1
        
        total = sum(self.pattern_frequencies.values())
        for p, count in self.pattern_frequencies.items():
            self.pattern_weights[p] = count / total


class HybridOptimizer:
    """Hybrid Optimizer - OPTIMIZED."""
    
    def __init__(self, fast_mode=True):
        self.ai_engine = None
        self.pattern_engine = None
        self.fast_mode = fast_mode
        self.iterations = 50 if fast_mode else 150
    
    def initialize(self, ai_engine, pattern_engine):
        """Initialisiert Optimizer."""
        self.ai_engine = ai_engine
        self.pattern_engine = pattern_engine
    
    def optimize(self, seed, ai_predictions):
        """Multi-Objective Optimization - FASTER."""
        random.seed(42 + seed * 29)
        
        best_score = -1
        best_combination = None
        best_ai_score = 0
        best_pattern_weight = 0
        
        for attempt in range(self.iterations):
            candidate = self._generate_candidate(ai_predictions, attempt, seed)
            
            ai_score = np.mean([ai_predictions.get(n, 0.1) for n in candidate])
            pattern_weight = self.pattern_engine.calculate_pattern_weight(candidate)
            diversity = self._calculate_diversity(candidate)
            
            combined_score = (
                ai_score * 0.45 +
                pattern_weight * 0.35 +
                diversity * 0.20
            )
            
            if combined_score > best_score:
                best_score = combined_score
                best_combination = candidate
                best_ai_score = ai_score
                best_pattern_weight = pattern_weight
        
        return {
            'numbers': best_combination if best_combination else sorted(random.sample(range(1, 50), 6)),
            'ai_score': best_ai_score,
            'pattern_weight': best_pattern_weight,
            'confidence': best_score
        }
    
    def _generate_candidate(self, ai_predictions, attempt, seed):
        """Generiert Kandidaten."""
        if attempt < self.iterations // 3:
            sorted_preds = sorted(ai_predictions.items(), key=lambda x: x[1], reverse=True)
            candidates = [num for num, _ in sorted_preds[:25]]
            return sorted(random.sample(candidates, min(6, len(candidates))))
        
        elif attempt < 2 * self.iterations // 3:
            top_patterns = self.pattern_engine.get_top_patterns(5)
            if top_patterns:
                pattern = random.choice(top_patterns)
                return self.pattern_engine.generate_for_pattern(pattern, seed + attempt)
        
        sorted_preds = sorted(ai_predictions.items(), key=lambda x: x[1], reverse=True)
        ai_nums = [num for num, _ in sorted_preds[:15]]
        
        selected = random.sample(ai_nums, 3)
        remaining = [n for n in range(1, 50) if n not in selected]
        selected.extend(random.sample(remaining, 3))
        
        return sorted(selected)
    
    def _calculate_diversity(self, numbers):
        """Berechnet Diversität."""
        if len(numbers) < 2:
            return 0.5
        
        distances = []
        for i, n1 in enumerate(numbers):
            for n2 in numbers[i+1:]:
                distances.append(abs(n1 - n2))
        
        avg_distance = np.mean(distances)
        return min(avg_distance / 10.0, 1.0)


class FeatureEngineer:
    """Feature Engineering - ERWEITERT V2.2."""

    def create_features(self, df):
        """Erstellt Features."""
        features_list = []

        # Calculate number-specific features once
        self.number_gaps = self._calculate_all_gaps(df)
        self.number_frequencies = self._calculate_all_frequencies(df)

        for i in range(len(df)):
            features = self._extract_features(df, i)
            features_list.append(features)

        return pd.DataFrame(features_list)

    def _calculate_all_gaps(self, df):
        """Berechnet Gap-Statistiken für alle Zahlen."""
        gaps = {num: [] for num in range(1, 50)}
        last_seen = {num: -1 for num in range(1, 50)}

        for idx, row in df.iterrows():
            numbers = [row['Z1'], row['Z2'], row['Z3'], row['Z4'], row['Z5'], row['Z6']]
            for num in range(1, 50):
                if num in numbers:
                    if last_seen[num] >= 0:
                        gaps[num].append(idx - last_seen[num])
                    last_seen[num] = idx

        return gaps

    def _calculate_all_frequencies(self, df):
        """Berechnet Häufigkeiten für alle Zahlen."""
        freq = {num: 0 for num in range(1, 50)}

        for _, row in df.iterrows():
            numbers = [row['Z1'], row['Z2'], row['Z3'], row['Z4'], row['Z5'], row['Z6']]
            for num in numbers:
                freq[num] += 1

        return freq

    def _extract_features(self, df, idx):
        """Extrahiert Features - ERWEITERT."""
        features = {}

        # 1. BASIC FREQUENCY FEATURES (original)
        for window in [5, 10]:
            start = max(0, idx - window)
            hist = df.iloc[start:idx]

            if len(hist) > 0:
                all_nums = []
                for _, row in hist.iterrows():
                    all_nums.extend([row['Z1'], row['Z2'], row['Z3'],
                                   row['Z4'], row['Z5'], row['Z6']])
                features[f'freq_{window}'] = len(set(all_nums)) / (window * 6) if window > 0 else 0
            else:
                features[f'freq_{window}'] = 0

        # 2. GAP ANALYSIS FEATURES (neu)
        for window in [20, 50]:
            start = max(0, idx - window)
            hist = df.iloc[start:idx]

            if len(hist) > 0:
                gap_features = self._extract_gap_features(hist)
                features[f'avg_gap_{window}'] = gap_features['avg_gap']
                features[f'gap_variance_{window}'] = gap_features['gap_variance']
            else:
                features[f'avg_gap_{window}'] = 0
                features[f'gap_variance_{window}'] = 0

        # 3. MOMENTUM FEATURES (neu)
        features.update(self._extract_momentum_features(df, idx))

        # 4. RELATIONSHIP FEATURES (neu)
        if idx > 0:
            prev_row = df.iloc[idx-1]
            prev_nums = [prev_row['Z1'], prev_row['Z2'], prev_row['Z3'],
                        prev_row['Z4'], prev_row['Z5'], prev_row['Z6']]

            # Sum tendency - normalized from min (6) to max (264)
            prev_sum_raw = sum(prev_nums)
            features['prev_sum'] = (prev_sum_raw - 6) / (264 - 6)  # Normalize to [0,1]
            features['prev_sum_norm'] = prev_sum_raw / (49 * 6)  # Alternative normalization

            # Parity balance
            even_count = sum(1 for n in prev_nums if n % 2 == 0)
            features['prev_parity'] = even_count / 6

            # Decade distribution
            decades = [0] * 5  # 1-10, 11-20, 21-30, 31-40, 41-49
            for num in prev_nums:
                decade_idx = min((num - 1) // 10, 4)
                decades[decade_idx] += 1
            features['prev_decade_spread'] = len([d for d in decades if d > 0]) / 5
        else:
            features['prev_sum'] = 0
            features['prev_sum_norm'] = 0
            features['prev_parity'] = 0.5
            features['prev_decade_spread'] = 0

        # 5. TEMPORAL FEATURES (original + erweitert)
        if 'datum' in df.columns and pd.notna(df.iloc[idx].get('datum')):
            date = df.iloc[idx]['datum']
            features['day_of_week'] = date.dayofweek / 6  # Normalize 0-6 to [0,1]
            # Normalize sin/cos from [-1,1] to [0,1]
            features['seasonal'] = (np.sin(2 * np.pi * date.dayofyear / 365) + 1) / 2

            # Cycle features (neu) - normalized
            features['month'] = (date.month - 1) / 11  # Normalize 1-12 to [0,1]
            features['week_of_year'] = (date.isocalendar()[1] - 1) / 52  # Normalize 1-53 to [0,1]
            features['seasonal_cos'] = (np.cos(2 * np.pi * date.dayofyear / 365) + 1) / 2  # [-1,1] to [0,1]
        else:
            features['day_of_week'] = 0
            features['seasonal'] = 0.5
            features['month'] = 0
            features['week_of_year'] = 0
            features['seasonal_cos'] = 0.5

        # 6. NEIGHBORHOOD FEATURES (neu)
        if idx > 0:
            prev_row = df.iloc[idx-1]
            prev_nums = set([prev_row['Z1'], prev_row['Z2'], prev_row['Z3'],
                           prev_row['Z4'], prev_row['Z5'], prev_row['Z6']])

            # Count numbers that are ±1 or ±2 from previous
            neighbor_1 = sum(1 for n in prev_nums if any(abs(n-m) == 1 for m in prev_nums if m != n))
            neighbor_2 = sum(1 for n in prev_nums if any(abs(n-m) == 2 for m in prev_nums if m != n))

            features['prev_neighbor_1'] = neighbor_1 / 6
            features['prev_neighbor_2'] = neighbor_2 / 6
        else:
            features['prev_neighbor_1'] = 0
            features['prev_neighbor_2'] = 0

        return features

    def _extract_gap_features(self, hist):
        """Extrahiert Gap-Features aus Historie."""
        if len(hist) == 0:
            return {'avg_gap': 0, 'gap_variance': 0}

        # Track when each number was last seen
        last_seen = {num: -1 for num in range(1, 50)}
        gaps = {num: [] for num in range(1, 50)}

        for local_idx, (_, row) in enumerate(hist.iterrows()):
            numbers = [row['Z1'], row['Z2'], row['Z3'], row['Z4'], row['Z5'], row['Z6']]

            for num in range(1, 50):
                if num in numbers:
                    if last_seen[num] >= 0:
                        gaps[num].append(local_idx - last_seen[num])
                    last_seen[num] = local_idx

        # Calculate average gap across all numbers
        all_gaps = [g for gap_list in gaps.values() for g in gap_list if gap_list]

        if all_gaps:
            avg_gap = np.mean(all_gaps)
            gap_variance = np.var(all_gaps) if len(all_gaps) > 1 else 0
        else:
            avg_gap = 0
            gap_variance = 0

        return {
            'avg_gap': min(avg_gap / 10.0, 1.0),  # Normalized
            'gap_variance': min(gap_variance / 50.0, 1.0)  # Normalized
        }

    def _extract_momentum_features(self, df, idx):
        """Extrahiert Momentum-Features."""
        features = {}

        if idx < 10:
            # Not enough data for momentum
            features['trend_5'] = 0
            features['trend_10'] = 0
            features['acceleration'] = 0
            return features

        # Count unique numbers in windows
        recent_5 = set()
        recent_10 = set()
        old_10 = set()

        for i in range(max(0, idx-5), idx):
            row = df.iloc[i]
            nums = [row['Z1'], row['Z2'], row['Z3'], row['Z4'], row['Z5'], row['Z6']]
            recent_5.update(nums)

        for i in range(max(0, idx-10), idx):
            row = df.iloc[i]
            nums = [row['Z1'], row['Z2'], row['Z3'], row['Z4'], row['Z5'], row['Z6']]
            recent_10.update(nums)

        for i in range(max(0, idx-20), max(0, idx-10)):
            row = df.iloc[i]
            nums = [row['Z1'], row['Z2'], row['Z3'], row['Z4'], row['Z5'], row['Z6']]
            old_10.update(nums)

        # Trend: Increasing or decreasing diversity
        div_5 = len(recent_5) / 30.0  # Max 30 numbers in 5 draws
        div_10 = len(recent_10) / 49.0  # Normalized
        div_old = len(old_10) / 49.0 if len(old_10) > 0 else div_10

        features['trend_5'] = div_5
        features['trend_10'] = div_10
        # Normalize acceleration from [-1, 1] to [0, 1]
        accel_raw = max(min((div_10 - div_old), 1.0), -1.0)
        features['acceleration'] = (accel_raw + 1.0) / 2.0  # Map [-1,1] to [0,1]

        return features


class RealTimeLearner:
    """Real-Time Learning mit Persistenz - ERWEITERT V2.2."""

    def __init__(self, cache_path=None):
        self.learning_rate = 0.1
        self.adjustments = {}
        self.stats = defaultdict(int)
        self.cache_path = cache_path
        self.learning_state_file = os.path.join(cache_path, 'learning_state.json') if cache_path else None

        # Lade gespeicherten State
        if self.learning_state_file and os.path.exists(self.learning_state_file):
            self._load_learning_state()

    def initialize(self, features_df):
        """Initialisiert Learning."""
        self.features_df = features_df

    def adjust_predictions(self, predictions):
        """Passt Predictions an."""
        adjusted = {}

        for number, pred in predictions.items():
            adjustment = self.adjustments.get(number, 0)
            adjusted[number] = np.clip(pred + adjustment * self.learning_rate, 0, 1)

        return adjusted

    def learn_from_result(self, drawing):
        """Lernt aus Ziehung."""
        actual = [drawing[f'Z{i}'] for i in range(1, 7)]

        for number in range(1, 50):
            if number not in self.adjustments:
                self.adjustments[number] = 0

            if number in actual:
                self.adjustments[number] += 0.01
                self.stats['correct'] += 1
            else:
                self.adjustments[number] -= 0.005
                self.stats['incorrect'] += 1

            # Langfristiger Decay (wöchentlich statt pro Zyklus)
            self.adjustments[number] *= 0.995  # Langsamer Decay

        self.stats['cycles'] += 1
        self.stats['last_update'] = datetime.datetime.now().isoformat()

        # Speichere Learning State
        self._save_learning_state()

    def _save_learning_state(self):
        """Speichert Learning State persistent."""
        if not self.learning_state_file:
            return

        try:
            # Erstelle Verzeichnis falls nötig
            os.makedirs(os.path.dirname(self.learning_state_file), exist_ok=True)

            state = {
                'adjustments': {str(k): float(v) for k, v in self.adjustments.items()},
                'stats': dict(self.stats),
                'learning_rate': self.learning_rate,
                'last_saved': datetime.datetime.now().isoformat()
            }

            with open(self.learning_state_file, 'w') as f:
                json.dump(state, f, indent=2)

        except Exception as e:
            print(f"⚠️  Warning: Konnte Learning State nicht speichern: {e}")

    def _load_learning_state(self):
        """Lädt gespeicherten Learning State."""
        try:
            with open(self.learning_state_file, 'r') as f:
                state = json.load(f)

            self.adjustments = {int(k): float(v) for k, v in state.get('adjustments', {}).items()}
            self.stats = defaultdict(int, state.get('stats', {}))
            self.learning_rate = state.get('learning_rate', 0.1)

            last_saved = state.get('last_saved', 'Unbekannt')
            cycles = self.stats.get('cycles', 0)
            print(f"📚 Learning State geladen: {cycles} Zyklen (Stand: {last_saved[:10]})")

        except Exception as e:
            print(f"⚠️  Warning: Konnte Learning State nicht laden: {e}")
            self.adjustments = {}
            self.stats = defaultdict(int)

    def get_learning_summary(self):
        """Gibt Learning-Zusammenfassung zurück."""
        if not self.adjustments:
            return "Noch kein Learning durchgeführt"

        # Top Adjustments
        top_positive = sorted(self.adjustments.items(), key=lambda x: x[1], reverse=True)[:5]
        top_negative = sorted(self.adjustments.items(), key=lambda x: x[1])[:5]

        avg_adjustment = np.mean(list(self.adjustments.values()))

        summary = {
            'total_cycles': self.stats.get('cycles', 0),
            'correct_predictions': self.stats.get('correct', 0),
            'incorrect_predictions': self.stats.get('incorrect', 0),
            'avg_adjustment': avg_adjustment,
            'top_positive': top_positive,
            'top_negative': top_negative,
            'last_update': self.stats.get('last_update', 'Nie')
        }

        return summary
    
    def get_stats(self):
        """Stats."""
        return dict(self.stats)


class PerformanceTracker:
    """Performance Tracking."""
    
    def __init__(self):
        self.generated_tips = []
        self.evaluations = []
    
    def log_generated_tips(self, tips):
        """Loggt Tips."""
        self.generated_tips.extend(tips)
    
    def evaluate_predictions(self, drawing):
        """Evaluiert."""
        actual = [drawing[f'Z{i}'] for i in range(1, 7)]
        
        if self.generated_tips:
            recent = self.generated_tips[-10:]
            
            for tip in recent:
                matches = len(set(tip['numbers']) & set(actual))
                self.evaluations.append({
                    'matches': matches,
                    'strategy': tip['strategy'],
                    'timestamp': datetime.datetime.now()
                })
    
    def get_statistics(self):
        """Stats."""
        return {
            'total_tips': len(self.generated_tips),
            'total_evaluations': len(self.evaluations)
        }


# ============================================================================
# MAIN
# ============================================================================

def main():
    """Main Function - OPTIMIZED."""
    print("🚀 ULTIMATE AI-ML HYBRID LOTTO GENERATOR V2.1")
    print("⚡ OPTIMIZED VERSION with Progress Indicators")
    print("=" * 70)
    
    data_path = "/Users/sebastianfrohlich/Library/Mobile Documents/com~apple~CloudDocs/Jupyter Notebooks/AlleLottozahlen.csv"
    
    try:
        # Initialize with fast mode
        generator = UltimateAIMLHybridGenerator(data_path, fast_mode=True)
        
        # Generate Tips
        tips = generator.generate_ultimate_tips(10)
        
        if tips:
            print("\n🏆 GENERATION COMPLETED!")
            print("=" * 70)
            print(f"✅ {len(tips)} Ultimate Tips generated")
            print(f"🧠 AI/ML: {'✅' if ML_AVAILABLE else '⚠️'}")
            print(f"🎨 Patterns: ✅")
            print(f"⚡ Optimization: ✅")
            print(f"📚 Learning: ✅")
            
            print("\n💡 ADVANTAGES:")
            print("   🔬 4 Strategien: Pure-AI, Pure-Pattern, Hybrid, Ensemble")
            print("   🧠 AI/ML Ensemble: RandomForest + GradientBoosting")
            print("   🎨 Pattern Analysis: Historische Verteilungen")
            print("   ⚡ Multi-Objective: AI + Pattern + Diversity")
            print("   📚 Real-Time Learning: Kontinuierliche Anpassung")
            print("   ⚡ Fast Mode: Optimierte Performance")
    
    except Exception as e:
        print(f"❌ Error: {e}")
        import traceback
        traceback.print_exc()


if __name__ == "__main__":
    random.seed(42)
    np.random.seed(42)
    main()
