"""
MetaProcessor - Tier 4: META Features (52 columns)

Long-term patterns and meta-features:
- Stability (8 columns): volatility, recent form, win/loss rating
- Side Preference (14 columns): CT vs T ratings, balance scores
- Opponent Adaptation (12 columns): vs different ELO tiers
- Map Specialization (10 columns): best/worst maps, versatility
- Session Pattern (8 columns): daily/weekly patterns, streaks
"""

import sqlite3
from typing import Dict, Any, List
from .base_processor import BaseFeatureProcessor, SafeAggregator


class MetaProcessor(BaseFeatureProcessor):
    """Tier 4 META processor - Cross-match patterns and meta-analysis"""
    
    MIN_MATCHES_REQUIRED = 15  # Need sufficient history for meta patterns
    
    @staticmethod
    def calculate(steam_id: str, conn_l2: sqlite3.Connection) -> Dict[str, Any]:
        """
        Calculate all Tier 4 META features (52 columns)
        
        Returns dict with keys starting with 'meta_'
        """
        features = {}
        
        # Check minimum matches
        if not BaseFeatureProcessor.check_min_matches(steam_id, conn_l2, 
                                                       MetaProcessor.MIN_MATCHES_REQUIRED):
            return _get_default_meta_features()
        
        # Calculate each meta dimension
        features.update(MetaProcessor._calculate_stability(steam_id, conn_l2))
        features.update(MetaProcessor._calculate_side_preference(steam_id, conn_l2))
        features.update(MetaProcessor._calculate_opponent_adaptation(steam_id, conn_l2))
        features.update(MetaProcessor._calculate_map_specialization(steam_id, conn_l2))
        features.update(MetaProcessor._calculate_session_pattern(steam_id, conn_l2))
        
        return features
    
    @staticmethod
    def _calculate_stability(steam_id: str, conn_l2: sqlite3.Connection) -> Dict[str, Any]:
        """
        Calculate Stability (8 columns)
        
        Columns:
        - meta_rating_volatility (STDDEV of last 20 matches)
        - meta_recent_form_rating (AVG of last 10 matches)
        - meta_win_rating, meta_loss_rating
        - meta_rating_consistency
        - meta_time_rating_correlation
        - meta_map_stability
        - meta_elo_tier_stability
        """
        cursor = conn_l2.cursor()
        
        # Get recent matches for volatility
        cursor.execute("""
            SELECT rating
            FROM fact_match_players
            WHERE steam_id_64 = ?
            ORDER BY match_id DESC
            LIMIT 20
        """, (steam_id,))
        
        recent_ratings = [row[0] for row in cursor.fetchall() if row[0] is not None]
        
        rating_volatility = SafeAggregator.safe_stddev(recent_ratings, 0.0)
        
        # Recent form (last 10 matches)
        recent_form = SafeAggregator.safe_avg(recent_ratings[:10], 0.0) if len(recent_ratings) >= 10 else 0.0
        
        # Win/loss ratings
        cursor.execute("""
            SELECT 
                AVG(CASE WHEN is_win = 1 THEN rating END) as win_rating,
                AVG(CASE WHEN is_win = 0 THEN rating END) as loss_rating
            FROM fact_match_players
            WHERE steam_id_64 = ?
        """, (steam_id,))
        
        row = cursor.fetchone()
        win_rating = row[0] if row[0] else 0.0
        loss_rating = row[1] if row[1] else 0.0
        
        # Rating consistency (inverse of volatility, normalized)
        rating_consistency = max(0, 100 - (rating_volatility * 100))
        
        # Time-rating correlation: calculate Pearson correlation between match time and rating
        cursor.execute("""
            SELECT 
                p.rating,
                m.start_time
            FROM fact_match_players p
            JOIN fact_matches m ON p.match_id = m.match_id
            WHERE p.steam_id_64 = ?
            AND p.rating IS NOT NULL
            AND m.start_time IS NOT NULL
            ORDER BY m.start_time
        """, (steam_id,))
        
        time_rating_data = cursor.fetchall()
        
        if len(time_rating_data) >= 2:
            ratings = [row[0] for row in time_rating_data]
            times = [row[1] for row in time_rating_data]
            
            # Normalize timestamps to match indices
            time_indices = list(range(len(times)))
            
            # Calculate Pearson correlation
            n = len(ratings)
            sum_x = sum(time_indices)
            sum_y = sum(ratings)
            sum_xy = sum(x * y for x, y in zip(time_indices, ratings))
            sum_x2 = sum(x * x for x in time_indices)
            sum_y2 = sum(y * y for y in ratings)
            
            numerator = n * sum_xy - sum_x * sum_y
            denominator = ((n * sum_x2 - sum_x ** 2) * (n * sum_y2 - sum_y ** 2)) ** 0.5
            
            time_rating_corr = SafeAggregator.safe_divide(numerator, denominator) if denominator > 0 else 0.0
        else:
            time_rating_corr = 0.0
        
        # Map stability (STDDEV across maps)
        cursor.execute("""
            SELECT 
                m.map_name,
                AVG(p.rating) as avg_rating
            FROM fact_match_players p
            JOIN fact_matches m ON p.match_id = m.match_id
            WHERE p.steam_id_64 = ?
            GROUP BY m.map_name
        """, (steam_id,))
        
        map_ratings = [row[1] for row in cursor.fetchall() if row[1] is not None]
        map_stability = SafeAggregator.safe_stddev(map_ratings, 0.0)
        
        # ELO tier stability (placeholder)
        elo_tier_stability = rating_volatility  # Simplified
        
        return {
            'meta_rating_volatility': round(rating_volatility, 3),
            'meta_recent_form_rating': round(recent_form, 3),
            'meta_win_rating': round(win_rating, 3),
            'meta_loss_rating': round(loss_rating, 3),
            'meta_rating_consistency': round(rating_consistency, 2),
            'meta_time_rating_correlation': round(time_rating_corr, 3),
            'meta_map_stability': round(map_stability, 3),
            'meta_elo_tier_stability': round(elo_tier_stability, 3),
        }
    
    @staticmethod
    def _calculate_side_preference(steam_id: str, conn_l2: sqlite3.Connection) -> Dict[str, Any]:
        """
        Calculate Side Preference (14 columns)
        
        Columns:
        - meta_side_ct_rating, meta_side_t_rating
        - meta_side_ct_kd, meta_side_t_kd
        - meta_side_ct_win_rate, meta_side_t_win_rate
        - meta_side_ct_fk_rate, meta_side_t_fk_rate
        - meta_side_ct_kast, meta_side_t_kast
        - meta_side_rating_diff, meta_side_kd_diff
        - meta_side_preference
        - meta_side_balance_score
        """
        cursor = conn_l2.cursor()
        
        # Get CT side performance from fact_match_players_ct
        # Rating is now stored as rating2 from fight_ct
        cursor.execute("""
            SELECT 
                AVG(rating) as avg_rating,
                AVG(CAST(kills AS REAL) / NULLIF(deaths, 0)) as avg_kd,
                AVG(kast) as avg_kast,
                AVG(entry_kills) as avg_fk,
                SUM(CASE WHEN is_win = 1 THEN 1 ELSE 0 END) as wins,
                COUNT(*) as total_matches,
                SUM(round_total) as total_rounds
            FROM fact_match_players_ct
            WHERE steam_id_64 = ?
            AND rating IS NOT NULL AND rating > 0
        """, (steam_id,))
        
        ct_row = cursor.fetchone()
        ct_rating = ct_row[0] if ct_row and ct_row[0] else 0.0
        ct_kd = ct_row[1] if ct_row and ct_row[1] else 0.0
        ct_kast = ct_row[2] if ct_row and ct_row[2] else 0.0
        ct_fk = ct_row[3] if ct_row and ct_row[3] else 0.0
        ct_wins = ct_row[4] if ct_row and ct_row[4] else 0
        ct_matches = ct_row[5] if ct_row and ct_row[5] else 1
        ct_rounds = ct_row[6] if ct_row and ct_row[6] else 1
        
        ct_win_rate = SafeAggregator.safe_divide(ct_wins, ct_matches)
        ct_fk_rate = SafeAggregator.safe_divide(ct_fk, ct_rounds)
        
        # Get T side performance from fact_match_players_t
        cursor.execute("""
            SELECT 
                AVG(rating) as avg_rating,
                AVG(CAST(kills AS REAL) / NULLIF(deaths, 0)) as avg_kd,
                AVG(kast) as avg_kast,
                AVG(entry_kills) as avg_fk,
                SUM(CASE WHEN is_win = 1 THEN 1 ELSE 0 END) as wins,
                COUNT(*) as total_matches,
                SUM(round_total) as total_rounds
            FROM fact_match_players_t
            WHERE steam_id_64 = ?
            AND rating IS NOT NULL AND rating > 0
        """, (steam_id,))
        
        t_row = cursor.fetchone()
        t_rating = t_row[0] if t_row and t_row[0] else 0.0
        t_kd = t_row[1] if t_row and t_row[1] else 0.0
        t_kast = t_row[2] if t_row and t_row[2] else 0.0
        t_fk = t_row[3] if t_row and t_row[3] else 0.0
        t_wins = t_row[4] if t_row and t_row[4] else 0
        t_matches = t_row[5] if t_row and t_row[5] else 1
        t_rounds = t_row[6] if t_row and t_row[6] else 1
        
        t_win_rate = SafeAggregator.safe_divide(t_wins, t_matches)
        t_fk_rate = SafeAggregator.safe_divide(t_fk, t_rounds)
        
        # Differences
        rating_diff = ct_rating - t_rating
        kd_diff = ct_kd - t_kd
        
        # Side preference classification
        if abs(rating_diff) < 0.05:
            side_preference = 'Balanced'
        elif rating_diff > 0:
            side_preference = 'CT'
        else:
            side_preference = 'T'
        
        # Balance score (0-100, higher = more balanced)
        balance_score = max(0, 100 - abs(rating_diff) * 200)
        
        return {
            'meta_side_ct_rating': round(ct_rating, 3),
            'meta_side_t_rating': round(t_rating, 3),
            'meta_side_ct_kd': round(ct_kd, 3),
            'meta_side_t_kd': round(t_kd, 3),
            'meta_side_ct_win_rate': round(ct_win_rate, 3),
            'meta_side_t_win_rate': round(t_win_rate, 3),
            'meta_side_ct_fk_rate': round(ct_fk_rate, 3),
            'meta_side_t_fk_rate': round(t_fk_rate, 3),
            'meta_side_ct_kast': round(ct_kast, 3),
            'meta_side_t_kast': round(t_kast, 3),
            'meta_side_rating_diff': round(rating_diff, 3),
            'meta_side_kd_diff': round(kd_diff, 3),
            'meta_side_preference': side_preference,
            'meta_side_balance_score': round(balance_score, 2),
        }
    
    @staticmethod
    def _calculate_opponent_adaptation(steam_id: str, conn_l2: sqlite3.Connection) -> Dict[str, Any]:
        """
        Calculate Opponent Adaptation (12 columns)
        
        ELO tiers: lower (<-200), similar (±200), higher (>+200)
        
        Columns:
        - meta_opp_vs_lower_elo_rating, meta_opp_vs_similar_elo_rating, meta_opp_vs_higher_elo_rating
        - meta_opp_vs_lower_elo_kd, meta_opp_vs_similar_elo_kd, meta_opp_vs_higher_elo_kd
        - meta_opp_elo_adaptation
        - meta_opp_stomping_score, meta_opp_upset_score
        - meta_opp_consistency_across_elos
        - meta_opp_rank_resistance
        - meta_opp_smurf_detection
        
        NOTE: Using individual origin_elo from fact_match_players
        """
        cursor = conn_l2.cursor()
        
        # Get player's matches with individual ELO data
        cursor.execute("""
            SELECT 
                p.rating,
                CAST(p.kills AS REAL) / NULLIF(p.deaths, 0) as kd,
                p.is_win,
                p.origin_elo as player_elo,
                opp.avg_elo as opponent_avg_elo
            FROM fact_match_players p
            JOIN (
                SELECT 
                    match_id,
                    team_id,
                    AVG(origin_elo) as avg_elo
                FROM fact_match_players
                WHERE origin_elo IS NOT NULL
                GROUP BY match_id, team_id
            ) opp ON p.match_id = opp.match_id AND p.team_id != opp.team_id
            WHERE p.steam_id_64 = ?
            AND p.origin_elo IS NOT NULL
        """, (steam_id,))
        
        matches = cursor.fetchall()
        
        if not matches:
            return {
                'meta_opp_vs_lower_elo_rating': 0.0,
                'meta_opp_vs_lower_elo_kd': 0.0,
                'meta_opp_vs_similar_elo_rating': 0.0,
                'meta_opp_vs_similar_elo_kd': 0.0,
                'meta_opp_vs_higher_elo_rating': 0.0,
                'meta_opp_vs_higher_elo_kd': 0.0,
                'meta_opp_elo_adaptation': 0.0,
                'meta_opp_stomping_score': 0.0,
                'meta_opp_upset_score': 0.0,
                'meta_opp_consistency_across_elos': 0.0,
                'meta_opp_rank_resistance': 0.0,
                'meta_opp_smurf_detection': 0.0,
            }
        
        # Categorize by ELO difference
        lower_elo_ratings = []  # Playing vs weaker opponents
        lower_elo_kds = []
        similar_elo_ratings = []  # Similar skill
        similar_elo_kds = []
        higher_elo_ratings = []  # Playing vs stronger opponents
        higher_elo_kds = []
        
        stomping_score = 0  # Dominating weaker teams
        upset_score = 0  # Winning against stronger teams
        
        for rating, kd, is_win, player_elo, opp_elo in matches:
            if rating is None or kd is None:
                continue
            
            elo_diff = player_elo - opp_elo  # Positive = we're stronger
            
            # Categorize ELO tiers (±200 threshold)
            if elo_diff > 200:  # We're stronger (opponent is lower ELO)
                lower_elo_ratings.append(rating)
                lower_elo_kds.append(kd)
                if is_win:
                    stomping_score += 1
            elif elo_diff < -200:  # Opponent is stronger (higher ELO)
                higher_elo_ratings.append(rating)
                higher_elo_kds.append(kd)
                if is_win:
                    upset_score += 2  # Upset wins count more
            else:  # Similar ELO (±200)
                similar_elo_ratings.append(rating)
                similar_elo_kds.append(kd)
        
        # Calculate averages
        avg_lower_rating = SafeAggregator.safe_avg(lower_elo_ratings)
        avg_lower_kd = SafeAggregator.safe_avg(lower_elo_kds)
        avg_similar_rating = SafeAggregator.safe_avg(similar_elo_ratings)
        avg_similar_kd = SafeAggregator.safe_avg(similar_elo_kds)
        avg_higher_rating = SafeAggregator.safe_avg(higher_elo_ratings)
        avg_higher_kd = SafeAggregator.safe_avg(higher_elo_kds)
        
        # ELO adaptation: performance improvement vs stronger opponents
        # Positive = performs better vs stronger teams (rare, good trait)
        elo_adaptation = avg_higher_rating - avg_lower_rating
        
        # Consistency: std dev of ratings across ELO tiers
        all_tier_ratings = [avg_lower_rating, avg_similar_rating, avg_higher_rating]
        consistency = 100 - SafeAggregator.safe_stddev(all_tier_ratings) * 100
        
        # Rank resistance: K/D vs higher ELO opponents
        rank_resistance = avg_higher_kd
        
        # Smurf detection: high performance vs lower ELO
        # Indicators: rating > 1.15 AND kd > 1.2 when facing lower ELO opponents
        smurf_score = 0.0
        if len(lower_elo_ratings) > 0 and avg_lower_rating > 1.0:
            # Base score from rating dominance
            rating_bonus = max(0, (avg_lower_rating - 1.0) * 100)
            # Additional score from K/D dominance
            kd_bonus = max(0, (avg_lower_kd - 1.0) * 50)
            # Consistency bonus (more matches = more reliable indicator)
            consistency_bonus = min(len(lower_elo_ratings) / 5.0, 1.0) * 20
            
            smurf_score = rating_bonus + kd_bonus + consistency_bonus
            
            # Cap at 100
            smurf_score = min(smurf_score, 100.0)
        
        return {
            'meta_opp_vs_lower_elo_rating': round(avg_lower_rating, 3),
            'meta_opp_vs_lower_elo_kd': round(avg_lower_kd, 3),
            'meta_opp_vs_similar_elo_rating': round(avg_similar_rating, 3),
            'meta_opp_vs_similar_elo_kd': round(avg_similar_kd, 3),
            'meta_opp_vs_higher_elo_rating': round(avg_higher_rating, 3),
            'meta_opp_vs_higher_elo_kd': round(avg_higher_kd, 3),
            'meta_opp_elo_adaptation': round(elo_adaptation, 3),
            'meta_opp_stomping_score': round(stomping_score, 2),
            'meta_opp_upset_score': round(upset_score, 2),
            'meta_opp_consistency_across_elos': round(consistency, 2),
            'meta_opp_rank_resistance': round(rank_resistance, 3),
            'meta_opp_smurf_detection': round(smurf_score, 2),
        }
        
        # Performance vs lower ELO opponents (simplified - using match-level team ELO)
        # REMOVED DUPLICATE LOGIC BLOCK THAT WAS UNREACHABLE
        # The code previously had a return statement before this block, making it dead code.
        # Merged logic into the first block above using individual player ELOs which is more accurate.
    
    @staticmethod
    def _calculate_map_specialization(steam_id: str, conn_l2: sqlite3.Connection) -> Dict[str, Any]:
        """
        Calculate Map Specialization (10 columns)
        
        Columns:
        - meta_map_best_map, meta_map_best_rating
        - meta_map_worst_map, meta_map_worst_rating
        - meta_map_diversity
        - meta_map_pool_size
        - meta_map_specialist_score
        - meta_map_versatility
        - meta_map_comfort_zone_rate
        - meta_map_adaptation
        """
        cursor = conn_l2.cursor()
        
        # Map performance
        # Lower threshold to 1 match to ensure we catch high ratings even with low sample size
        cursor.execute("""
            SELECT 
                m.map_name,
                AVG(p.rating) as avg_rating,
                COUNT(*) as match_count
            FROM fact_match_players p
            JOIN fact_matches m ON p.match_id = m.match_id
            WHERE p.steam_id_64 = ?
            GROUP BY m.map_name
            HAVING match_count >= 1
            ORDER BY avg_rating DESC
        """, (steam_id,))
        
        map_data = cursor.fetchall()
        
        if not map_data:
            return {
                'meta_map_best_map': 'unknown',
                'meta_map_best_rating': 0.0,
                'meta_map_worst_map': 'unknown',
                'meta_map_worst_rating': 0.0,
                'meta_map_diversity': 0.0,
                'meta_map_pool_size': 0,
                'meta_map_specialist_score': 0.0,
                'meta_map_versatility': 0.0,
                'meta_map_comfort_zone_rate': 0.0,
                'meta_map_adaptation': 0.0,
            }
        
        # Best map
        best_map = map_data[0][0]
        best_rating = map_data[0][1]
        
        # Worst map
        worst_map = map_data[-1][0]
        worst_rating = map_data[-1][1]
        
        # Map diversity (entropy-based)
        map_ratings = [row[1] for row in map_data]
        map_diversity = SafeAggregator.safe_stddev(map_ratings, 0.0)
        
        # Map pool size (maps with 3+ matches, lowered from 5)
        cursor.execute("""
            SELECT COUNT(DISTINCT m.map_name)
            FROM fact_match_players p
            JOIN fact_matches m ON p.match_id = m.match_id
            WHERE p.steam_id_64 = ?
            GROUP BY m.map_name
            HAVING COUNT(*) >= 3
        """, (steam_id,))
        
        pool_rows = cursor.fetchall()
        pool_size = len(pool_rows)
        
        # Specialist score (difference between best and worst)
        specialist_score = best_rating - worst_rating
        
        # Versatility (inverse of specialist score, normalized)
        versatility = max(0, 100 - specialist_score * 100)
        
        # Comfort zone rate (% matches on top 3 maps)
        cursor.execute("""
            SELECT 
                SUM(CASE WHEN m.map_name IN (
                    SELECT map_name FROM (
                        SELECT m2.map_name, COUNT(*) as cnt
                    FROM fact_match_players p2
                        JOIN fact_matches m2 ON p2.match_id = m2.match_id
                        WHERE p2.steam_id_64 = ?
                        GROUP BY m2.map_name
                        ORDER BY cnt DESC
                        LIMIT 3
                    )
                ) THEN 1 ELSE 0 END) as comfort_matches,
                COUNT(*) as total_matches
            FROM fact_match_players p
            JOIN fact_matches m ON p.match_id = m.match_id
            WHERE p.steam_id_64 = ?
        """, (steam_id, steam_id))
        
        comfort_row = cursor.fetchone()
        comfort_matches = comfort_row[0] if comfort_row[0] else 0
        total_matches = comfort_row[1] if comfort_row[1] else 1
        comfort_zone_rate = SafeAggregator.safe_divide(comfort_matches, total_matches)
        
        # Map adaptation (avg rating on non-favorite maps)
        if len(map_data) > 1:
            non_favorite_ratings = [row[1] for row in map_data[1:]]
            map_adaptation = SafeAggregator.safe_avg(non_favorite_ratings, 0.0)
        else:
            map_adaptation = best_rating
        
        return {
            'meta_map_best_map': best_map,
            'meta_map_best_rating': round(best_rating, 3),
            'meta_map_worst_map': worst_map,
            'meta_map_worst_rating': round(worst_rating, 3),
            'meta_map_diversity': round(map_diversity, 3),
            'meta_map_pool_size': pool_size,
            'meta_map_specialist_score': round(specialist_score, 3),
            'meta_map_versatility': round(versatility, 2),
            'meta_map_comfort_zone_rate': round(comfort_zone_rate, 3),
            'meta_map_adaptation': round(map_adaptation, 3),
        }
    
    @staticmethod
    def _calculate_session_pattern(steam_id: str, conn_l2: sqlite3.Connection) -> Dict[str, Any]:
        """
        Calculate Session Pattern (8 columns)
        
        Columns:
        - meta_session_avg_matches_per_day
        - meta_session_longest_streak
        - meta_session_weekend_rating, meta_session_weekday_rating
        - meta_session_morning_rating, meta_session_afternoon_rating
        - meta_session_evening_rating, meta_session_night_rating
        
        Note: Requires timestamp data in fact_matches
        """
        cursor = conn_l2.cursor()
        
        # Check if start_time exists
        cursor.execute("""
            SELECT COUNT(*) FROM fact_matches
            WHERE start_time IS NOT NULL AND start_time > 0
            LIMIT 1
        """)
        
        has_timestamps = cursor.fetchone()[0] > 0
        
        if not has_timestamps:
            # Return placeholder values
            return {
                'meta_session_avg_matches_per_day': 0.0,
                'meta_session_longest_streak': 0,
                'meta_session_weekend_rating': 0.0,
                'meta_session_weekday_rating': 0.0,
                'meta_session_morning_rating': 0.0,
                'meta_session_afternoon_rating': 0.0,
                'meta_session_evening_rating': 0.0,
                'meta_session_night_rating': 0.0,
            }
        
        # 1. Matches per day
        cursor.execute("""
            SELECT 
                DATE(start_time, 'unixepoch') as match_date,
                COUNT(*) as daily_matches
            FROM fact_matches m
            JOIN fact_match_players p ON m.match_id = p.match_id
            WHERE p.steam_id_64 = ? AND m.start_time IS NOT NULL
            GROUP BY match_date
        """, (steam_id,))
        
        daily_stats = cursor.fetchall()
        if daily_stats:
            avg_matches_per_day = sum(row[1] for row in daily_stats) / len(daily_stats)
        else:
            avg_matches_per_day = 0.0
            
        # 2. Longest Streak (Consecutive wins)
        cursor.execute("""
            SELECT is_win
            FROM fact_match_players p
            JOIN fact_matches m ON p.match_id = m.match_id
            WHERE p.steam_id_64 = ? AND m.start_time IS NOT NULL
            ORDER BY m.start_time
        """, (steam_id,))
        
        results = cursor.fetchall()
        longest_streak = 0
        current_streak = 0
        for row in results:
            if row[0]: # Win
                current_streak += 1
            else:
                longest_streak = max(longest_streak, current_streak)
                current_streak = 0
        longest_streak = max(longest_streak, current_streak)
        
        # 3. Time of Day & Week Analysis
        # Weekend: 0 (Sun) and 6 (Sat)
        cursor.execute("""
            SELECT 
                CAST(strftime('%w', start_time, 'unixepoch') AS INTEGER) as day_of_week,
                CAST(strftime('%H', start_time, 'unixepoch') AS INTEGER) as hour_of_day,
                p.rating
            FROM fact_match_players p
            JOIN fact_matches m ON p.match_id = m.match_id
            WHERE p.steam_id_64 = ? 
            AND m.start_time IS NOT NULL 
            AND p.rating IS NOT NULL
        """, (steam_id,))
        
        matches = cursor.fetchall()
        
        weekend_ratings = []
        weekday_ratings = []
        morning_ratings = []   # 06-12
        afternoon_ratings = [] # 12-18
        evening_ratings = []   # 18-24
        night_ratings = []     # 00-06
        
        for dow, hour, rating in matches:
            # Weekday/Weekend
            if dow == 0 or dow == 6:
                weekend_ratings.append(rating)
            else:
                weekday_ratings.append(rating)
                
            # Time of Day
            if 6 <= hour < 12:
                morning_ratings.append(rating)
            elif 12 <= hour < 18:
                afternoon_ratings.append(rating)
            elif 18 <= hour <= 23:
                evening_ratings.append(rating)
            else: # 0-6
                night_ratings.append(rating)
        
        return {
            'meta_session_avg_matches_per_day': round(avg_matches_per_day, 2),
            'meta_session_longest_streak': longest_streak,
            'meta_session_weekend_rating': round(SafeAggregator.safe_avg(weekend_ratings), 3),
            'meta_session_weekday_rating': round(SafeAggregator.safe_avg(weekday_ratings), 3),
            'meta_session_morning_rating': round(SafeAggregator.safe_avg(morning_ratings), 3),
            'meta_session_afternoon_rating': round(SafeAggregator.safe_avg(afternoon_ratings), 3),
            'meta_session_evening_rating': round(SafeAggregator.safe_avg(evening_ratings), 3),
            'meta_session_night_rating': round(SafeAggregator.safe_avg(night_ratings), 3),
        }


def _get_default_meta_features() -> Dict[str, Any]:
    """Return default zero values for all 52 META features"""
    return {
        # Stability (8)
        'meta_rating_volatility': 0.0,
        'meta_recent_form_rating': 0.0,
        'meta_win_rating': 0.0,
        'meta_loss_rating': 0.0,
        'meta_rating_consistency': 0.0,
        'meta_time_rating_correlation': 0.0,
        'meta_map_stability': 0.0,
        'meta_elo_tier_stability': 0.0,
        # Side Preference (14)
        'meta_side_ct_rating': 0.0,
        'meta_side_t_rating': 0.0,
        'meta_side_ct_kd': 0.0,
        'meta_side_t_kd': 0.0,
        'meta_side_ct_win_rate': 0.0,
        'meta_side_t_win_rate': 0.0,
        'meta_side_ct_fk_rate': 0.0,
        'meta_side_t_fk_rate': 0.0,
        'meta_side_ct_kast': 0.0,
        'meta_side_t_kast': 0.0,
        'meta_side_rating_diff': 0.0,
        'meta_side_kd_diff': 0.0,
        'meta_side_preference': 'Balanced',
        'meta_side_balance_score': 0.0,
        # Opponent Adaptation (12)
        'meta_opp_vs_lower_elo_rating': 0.0,
        'meta_opp_vs_similar_elo_rating': 0.0,
        'meta_opp_vs_higher_elo_rating': 0.0,
        'meta_opp_vs_lower_elo_kd': 0.0,
        'meta_opp_vs_similar_elo_kd': 0.0,
        'meta_opp_vs_higher_elo_kd': 0.0,
        'meta_opp_elo_adaptation': 0.0,
        'meta_opp_stomping_score': 0.0,
        'meta_opp_upset_score': 0.0,
        'meta_opp_consistency_across_elos': 0.0,
        'meta_opp_rank_resistance': 0.0,
        'meta_opp_smurf_detection': 0.0,
        # Map Specialization (10)
        'meta_map_best_map': 'unknown',
        'meta_map_best_rating': 0.0,
        'meta_map_worst_map': 'unknown',
        'meta_map_worst_rating': 0.0,
        'meta_map_diversity': 0.0,
        'meta_map_pool_size': 0,
        'meta_map_specialist_score': 0.0,
        'meta_map_versatility': 0.0,
        'meta_map_comfort_zone_rate': 0.0,
        'meta_map_adaptation': 0.0,
        # Session Pattern (8)
        'meta_session_avg_matches_per_day': 0.0,
        'meta_session_longest_streak': 0,
        'meta_session_weekend_rating': 0.0,
        'meta_session_weekday_rating': 0.0,
        'meta_session_morning_rating': 0.0,
        'meta_session_afternoon_rating': 0.0,
        'meta_session_evening_rating': 0.0,
        'meta_session_night_rating': 0.0,
    }
