from web.database import query_db
from web.services.web_service import WebService
import json

class OpponentService:
    @staticmethod
    def _get_active_roster_ids():
        lineups = WebService.get_lineups()
        active_roster_ids = []
        if lineups:
            try:
                raw_ids = json.loads(lineups[0]['player_ids_json'])
                active_roster_ids = [str(uid) for uid in raw_ids]
            except:
                pass
        return active_roster_ids

    @staticmethod
    def get_opponent_list(page=1, per_page=20, sort_by='matches', search=None):
        roster_ids = OpponentService._get_active_roster_ids()
        if not roster_ids:
            return [], 0

        # Placeholders
        roster_ph = ','.join('?' for _ in roster_ids)
        
        # 1. Identify Matches involving our roster (at least 1 member? usually 2 for 'team' match)
        # Let's say at least 1 for broader coverage as requested ("1 match sample")
        # But "Our Team" usually implies the entity. Let's stick to matches where we can identify "Us".
        # If we use >=1, we catch solo Q matches of roster members. The user said "Non-team members or 1 match sample", 
        # but implied "facing different our team lineups".
        # Let's use the standard "candidate matches" logic (>=2 roster members) to represent "The Team".
        # OR, if user wants "Opponent Analysis" for even 1 match, maybe they mean ANY match in DB?
        # "Left Top add Opponent Analysis... (non-team member or 1 sample)"
        # This implies we analyze PLAYERS who are NOT us.
        # Let's stick to matches where >= 1 roster member played, to define "Us" vs "Them".
        
        # Actually, let's look at ALL matches in DB, and any player NOT in active roster is an "Opponent".
        # This covers "1 sample".
        
        # Query:
        # Select all players who are NOT in active roster.
        # Group by steam_id.
        # Aggregate stats.
        
        where_clauses = [f"CAST(mp.steam_id_64 AS TEXT) NOT IN ({roster_ph})"]
        args = list(roster_ids)
        
        if search:
            where_clauses.append("(LOWER(p.username) LIKE LOWER(?) OR mp.steam_id_64 LIKE ?)")
            args.extend([f"%{search}%", f"%{search}%"])
            
        where_str = " AND ".join(where_clauses)
        
        # Sort mapping
        sort_sql = "matches DESC"
        if sort_by == 'rating':
            sort_sql = "avg_rating DESC"
        elif sort_by == 'kd':
            sort_sql = "avg_kd DESC"
        elif sort_by == 'win_rate':
            sort_sql = "win_rate DESC"
        
        # Main Aggregation Query
        # We need to join fact_matches to get match info (win/loss, elo) if needed, 
        # but fact_match_players has is_win (boolean) usually? No, it has team_id.
        # We need to determine if THEY won.
        # fact_match_players doesn't store is_win directly in schema (I should check schema, but stats_service calculates it).
        # Wait, stats_service.get_player_trend uses `mp.is_win`? 
        # Let's check schema. `fact_match_players` usually has `match_id`, `team_id`.
        # `fact_matches` has `winner_team`.
        # So we join.
        
        offset = (page - 1) * per_page
        
        sql = f"""
            SELECT 
                mp.steam_id_64,
                MAX(p.username) as username,
                MAX(p.avatar_url) as avatar_url,
                COUNT(DISTINCT mp.match_id) as matches,
                AVG(mp.rating) as avg_rating,
                AVG(mp.kd_ratio) as avg_kd,
                AVG(mp.adr) as avg_adr,
                SUM(CASE WHEN mp.is_win = 1 THEN 1 ELSE 0 END) as wins,
                AVG(NULLIF(COALESCE(fmt_gid.group_origin_elo, fmt_tid.group_origin_elo), 0)) as avg_match_elo
            FROM fact_match_players mp
            JOIN fact_matches m ON mp.match_id = m.match_id
            LEFT JOIN dim_players p ON mp.steam_id_64 = p.steam_id_64
            LEFT JOIN fact_match_teams fmt_gid ON mp.match_id = fmt_gid.match_id AND fmt_gid.group_id = mp.team_id
            LEFT JOIN fact_match_teams fmt_tid ON mp.match_id = fmt_tid.match_id AND fmt_tid.group_tid = mp.match_team_id
            WHERE {where_str}
            GROUP BY mp.steam_id_64
            ORDER BY {sort_sql}
            LIMIT ? OFFSET ?
        """
        
        # Count query
        count_sql = f"""
            SELECT COUNT(DISTINCT mp.steam_id_64) as cnt
            FROM fact_match_players mp
            LEFT JOIN dim_players p ON mp.steam_id_64 = p.steam_id_64
            WHERE {where_str}
        """
        
        query_args = args + [per_page, offset]
        rows = query_db('l2', sql, query_args)
        total = query_db('l2', count_sql, args, one=True)['cnt']
        
        # Post-process for derived stats
        results = []
        # Resolve avatar fallback from local static if missing
        from web.services.stats_service import StatsService
        for r in rows or []:
            d = dict(r)
            d['win_rate'] = (d['wins'] / d['matches']) if d['matches'] else 0
            d['avatar_url'] = StatsService.resolve_avatar_url(d.get('steam_id_64'), d.get('avatar_url'))
            results.append(d)
            
        return results, total

    @staticmethod
    def get_global_opponent_stats():
        """
        Calculates aggregate statistics for ALL opponents.
        Returns:
            {
                'elo_dist': {'<1200': 10, '1200-1500': 20...},
                'rating_dist': {'<0.8': 5, '0.8-1.0': 15...},
                'win_rate_dist': {'<40%': 5, '40-60%': 10...} (Opponent Win Rate)
            }
        """
        roster_ids = OpponentService._get_active_roster_ids()
        if not roster_ids:
            return {}
            
        roster_ph = ','.join('?' for _ in roster_ids)
        
        # 1. Fetch Aggregated Stats for ALL opponents
        # We group by steam_id first to get each opponent's AVG stats
        
        sql = f"""
            SELECT 
                mp.steam_id_64,
                COUNT(DISTINCT mp.match_id) as matches,
                AVG(mp.rating) as avg_rating,
                AVG(NULLIF(COALESCE(fmt_gid.group_origin_elo, fmt_tid.group_origin_elo), 0)) as avg_match_elo,
                SUM(CASE WHEN mp.is_win = 1 THEN 1 ELSE 0 END) as wins
            FROM fact_match_players mp
            JOIN fact_matches m ON mp.match_id = m.match_id
            LEFT JOIN fact_match_teams fmt_gid ON mp.match_id = fmt_gid.match_id AND fmt_gid.group_id = mp.team_id
            LEFT JOIN fact_match_teams fmt_tid ON mp.match_id = fmt_tid.match_id AND fmt_tid.group_tid = mp.match_team_id
            WHERE CAST(mp.steam_id_64 AS TEXT) NOT IN ({roster_ph})
            GROUP BY mp.steam_id_64
        """
        
        rows = query_db('l2', sql, roster_ids)
        
        # Initialize Buckets
        elo_buckets = {'<1000': 0, '1000-1200': 0, '1200-1400': 0, '1400-1600': 0, '1600-1800': 0, '1800-2000': 0, '>2000': 0}
        rating_buckets = {'<0.8': 0, '0.8-1.0': 0, '1.0-1.2': 0, '1.2-1.4': 0, '>1.4': 0}
        win_rate_buckets = {'<30%': 0, '30-45%': 0, '45-55%': 0, '55-70%': 0, '>70%': 0}
        elo_values = []
        rating_values = []
        
        for r in rows:
            elo_val = r['avg_match_elo']
            if elo_val is None or elo_val <= 0:
                pass
            else:
                elo = elo_val
                if elo < 1000: k = '<1000'
                elif elo < 1200: k = '1000-1200'
                elif elo < 1400: k = '1200-1400'
                elif elo < 1600: k = '1400-1600'
                elif elo < 1800: k = '1600-1800'
                elif elo < 2000: k = '1800-2000'
                else: k = '>2000'
                elo_buckets[k] += 1
                elo_values.append(float(elo))
            
            rtg = r['avg_rating'] or 0
            if rtg < 0.8: k = '<0.8'
            elif rtg < 1.0: k = '0.8-1.0'
            elif rtg < 1.2: k = '1.0-1.2'
            elif rtg < 1.4: k = '1.2-1.4'
            else: k = '>1.4'
            rating_buckets[k] += 1
            rating_values.append(float(rtg))
            
            matches = r['matches'] or 0
            if matches > 0:
                wr = (r['wins'] or 0) / matches
                if wr < 0.30: k = '<30%'
                elif wr < 0.45: k = '30-45%'
                elif wr < 0.55: k = '45-55%'
                elif wr < 0.70: k = '55-70%'
                else: k = '>70%'
                win_rate_buckets[k] += 1
                
        return {
            'elo_dist': elo_buckets,
            'rating_dist': rating_buckets,
            'win_rate_dist': win_rate_buckets,
            'elo_values': elo_values,
            'rating_values': rating_values
        }

    @staticmethod
    def get_opponent_detail(steam_id):
        # 1. Basic Info
        info = query_db('l2', "SELECT * FROM dim_players WHERE steam_id_64 = ?", [steam_id], one=True)
        if not info:
            return None
        from web.services.stats_service import StatsService
        player = dict(info)
        player['avatar_url'] = StatsService.resolve_avatar_url(steam_id, player.get('avatar_url'))
            
        # 2. Match History vs Us (All matches this player played)
        # We define "Us" as matches where this player is an opponent.
        # But actually, we just show ALL their matches in our DB, assuming our DB only contains matches relevant to us?
        # Usually yes, but if we have a huge DB, we might want to filter by "Contains Roster Member".
        # For now, show all matches in DB for this player.
        
        sql_history = """
            SELECT 
                m.match_id, m.start_time, m.map_name, m.score_team1, m.score_team2, m.winner_team,
                mp.team_id, mp.match_team_id, mp.rating, mp.kd_ratio, mp.adr, mp.kills, mp.deaths,
                mp.is_win as is_win,
                CASE 
                    WHEN COALESCE(fmt_gid.group_origin_elo, fmt_tid.group_origin_elo) > 0 
                    THEN COALESCE(fmt_gid.group_origin_elo, fmt_tid.group_origin_elo)
                END as elo
            FROM fact_match_players mp
            JOIN fact_matches m ON mp.match_id = m.match_id
            LEFT JOIN fact_match_teams fmt_gid ON mp.match_id = fmt_gid.match_id AND fmt_gid.group_id = mp.team_id
            LEFT JOIN fact_match_teams fmt_tid ON mp.match_id = fmt_tid.match_id AND fmt_tid.group_tid = mp.match_team_id
            WHERE mp.steam_id_64 = ?
            ORDER BY m.start_time DESC
        """
        history = query_db('l2', sql_history, [steam_id])
        
        # 3. Aggregation by ELO
        elo_buckets = {
            '<1200': {'matches': 0, 'rating_sum': 0, 'kd_sum': 0},
            '1200-1500': {'matches': 0, 'rating_sum': 0, 'kd_sum': 0},
            '1500-1800': {'matches': 0, 'rating_sum': 0, 'kd_sum': 0},
            '1800-2100': {'matches': 0, 'rating_sum': 0, 'kd_sum': 0},
            '>2100': {'matches': 0, 'rating_sum': 0, 'kd_sum': 0}
        }
        
        # 4. Aggregation by Side (T/CT)
        # Using fact_match_players_t / ct
        sql_side = """
            SELECT
                (SELECT CASE 
                            WHEN SUM(CASE WHEN t.rating2 IS NOT NULL AND t.rating2 != 0 THEN t.round_total END) > 0
                            THEN SUM(CASE WHEN t.rating2 IS NOT NULL AND t.rating2 != 0 THEN t.rating2 * t.round_total END) 
                                  / SUM(CASE WHEN t.rating2 IS NOT NULL AND t.rating2 != 0 THEN t.round_total END)
                            WHEN COUNT(*) > 0
                            THEN AVG(NULLIF(t.rating2, 0))
                        END
                 FROM fact_match_players_t t WHERE t.steam_id_64 = ?) as rating_t,
                (SELECT CASE 
                            WHEN SUM(CASE WHEN ct.rating2 IS NOT NULL AND ct.rating2 != 0 THEN ct.round_total END) > 0
                            THEN SUM(CASE WHEN ct.rating2 IS NOT NULL AND ct.rating2 != 0 THEN ct.rating2 * ct.round_total END) 
                                  / SUM(CASE WHEN ct.rating2 IS NOT NULL AND ct.rating2 != 0 THEN ct.round_total END)
                            WHEN COUNT(*) > 0
                            THEN AVG(NULLIF(ct.rating2, 0))
                        END
                 FROM fact_match_players_ct ct WHERE ct.steam_id_64 = ?) as rating_ct,
                (SELECT CASE 
                            WHEN SUM(t.deaths) > 0 THEN SUM(t.kills) * 1.0 / SUM(t.deaths)
                            WHEN SUM(t.kills) > 0 THEN SUM(t.kills) * 1.0
                            WHEN COUNT(*) > 0 THEN AVG(NULLIF(t.kd_ratio, 0))
                        END
                 FROM fact_match_players_t t WHERE t.steam_id_64 = ?) as kd_t,
                (SELECT CASE 
                            WHEN SUM(ct.deaths) > 0 THEN SUM(ct.kills) * 1.0 / SUM(ct.deaths)
                            WHEN SUM(ct.kills) > 0 THEN SUM(ct.kills) * 1.0
                            WHEN COUNT(*) > 0 THEN AVG(NULLIF(ct.kd_ratio, 0))
                        END
                 FROM fact_match_players_ct ct WHERE ct.steam_id_64 = ?) as kd_ct,
                (SELECT SUM(t.round_total) FROM fact_match_players_t t WHERE t.steam_id_64 = ?) as rounds_t,
                (SELECT SUM(ct.round_total) FROM fact_match_players_ct ct WHERE ct.steam_id_64 = ?) as rounds_ct
        """
        side_stats = query_db('l2', sql_side, [steam_id, steam_id, steam_id, steam_id, steam_id, steam_id], one=True)
        
        # Process History for ELO & KD Diff
        # We also want "Our Team KD" in these matches to calc Diff.
        # This requires querying the OTHER team in these matches.
        
        match_ids = [h['match_id'] for h in history]
        
        # Get Our Team Stats per match
        # "Our Team" = All players in the match EXCEPT this opponent (and their teammates?)
        # Simplification: "Avg Lobby KD" vs "Opponent KD".
        # Or better: "Avg KD of Opposing Team".
        
        match_stats_map = {}
        if match_ids:
            ph = ','.join('?' for _ in match_ids)
            # Calculate Avg KD of the team that is NOT the opponent's team
            opp_stats_sql = f"""
                SELECT match_id, match_team_id, AVG(kd_ratio) as team_avg_kd
                FROM fact_match_players
                WHERE match_id IN ({ph})
                GROUP BY match_id, match_team_id
            """
            opp_rows = query_db('l2', opp_stats_sql, match_ids)
            
            # Organize by match
            for r in opp_rows:
                mid = r['match_id']
                tid = r['match_team_id']
                if mid not in match_stats_map:
                    match_stats_map[mid] = {}
                match_stats_map[mid][tid] = r['team_avg_kd']

        processed_history = []
        for h in history:
            # ELO Bucketing
            elo = h['elo'] or 0
            if elo < 1200: b = '<1200'
            elif elo < 1500: b = '1200-1500'
            elif elo < 1800: b = '1500-1800'
            elif elo < 2100: b = '1800-2100'
            else: b = '>2100'
            
            elo_buckets[b]['matches'] += 1
            elo_buckets[b]['rating_sum'] += (h['rating'] or 0)
            elo_buckets[b]['kd_sum'] += (h['kd_ratio'] or 0)
            
            # KD Diff
            # Find the OTHER team's avg KD
            my_tid = h['match_team_id']
            # Assuming 2 teams: if my_tid is 1, other is 2. But IDs can be anything.
            # Look at match_stats_map[mid] keys.
            mid = h['match_id']
            other_team_kd = 1.0 # Default
            if mid in match_stats_map:
                for tid, avg_kd in match_stats_map[mid].items():
                    if tid != my_tid:
                        other_team_kd = avg_kd
                        break
            
            kd_diff = (h['kd_ratio'] or 0) - other_team_kd
            
            d = dict(h)
            d['kd_diff'] = kd_diff
            d['other_team_kd'] = other_team_kd
            processed_history.append(d)

        # Format ELO Stats
        elo_stats = []
        for k, v in elo_buckets.items():
            if v['matches'] > 0:
                elo_stats.append({
                    'range': k,
                    'matches': v['matches'],
                    'avg_rating': v['rating_sum'] / v['matches'],
                    'avg_kd': v['kd_sum'] / v['matches']
                })
        
        return {
            'player': player,
            'history': processed_history,
            'elo_stats': elo_stats,
            'side_stats': dict(side_stats) if side_stats else {}
        }

    @staticmethod
    def get_map_opponent_stats():
        roster_ids = OpponentService._get_active_roster_ids()
        if not roster_ids:
            return []
        roster_ph = ','.join('?' for _ in roster_ids)
        sql = f"""
            SELECT 
                m.map_name as map_name,
                COUNT(DISTINCT mp.match_id) as matches,
                AVG(mp.rating) as avg_rating,
                AVG(mp.kd_ratio) as avg_kd,
                AVG(NULLIF(COALESCE(fmt_gid.group_origin_elo, fmt_tid.group_origin_elo), 0)) as avg_elo,
                COUNT(DISTINCT CASE WHEN mp.is_win = 1 THEN mp.match_id END) as wins,
                COUNT(DISTINCT CASE WHEN mp.rating > 1.5 THEN mp.match_id END) as shark_matches
            FROM fact_match_players mp
            JOIN fact_matches m ON mp.match_id = m.match_id
            LEFT JOIN fact_match_teams fmt_gid ON mp.match_id = fmt_gid.match_id AND fmt_gid.group_id = mp.team_id
            LEFT JOIN fact_match_teams fmt_tid ON mp.match_id = fmt_tid.match_id AND fmt_tid.group_tid = mp.match_team_id
            WHERE CAST(mp.steam_id_64 AS TEXT) NOT IN ({roster_ph})
              AND m.map_name IS NOT NULL AND m.map_name <> ''
            GROUP BY m.map_name
            ORDER BY matches DESC
        """
        rows = query_db('l2', sql, roster_ids)
        results = []
        for r in rows:
            d = dict(r)
            matches = d.get('matches') or 0
            wins = d.get('wins') or 0
            d['win_rate'] = (wins / matches) if matches else 0
            results.append(d)
        return results
