import sqlite3
import json
import os
import sys
import logging
from dataclasses import dataclass, field
from typing import List, Dict, Optional, Any, Tuple
from datetime import datetime

# Setup logging
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)

# Constants
L1A_DB_PATH = 'database/L1A/L1A.sqlite'
L2_DB_PATH = 'database/L2/L2_Main.sqlite'
SCHEMA_PATH = 'database/L2/schema.sql'

# --- Data Structures for Unification ---

@dataclass
class PlayerStats:
    steam_id_64: str
    team_id: int = 0
    kills: int = 0
    deaths: int = 0
    assists: int = 0
    headshot_count: int = 0
    kd_ratio: float = 0.0
    adr: float = 0.0
    rating: float = 0.0
    rating2: float = 0.0
    rating3: float = 0.0
    rws: float = 0.0
    mvp_count: int = 0
    elo_change: float = 0.0
    rank_score: int = 0
    is_win: bool = False
    
    # VIP Stats
    kast: float = 0.0
    entry_kills: int = 0
    entry_deaths: int = 0
    awp_kills: int = 0
    clutch_1v1: int = 0
    clutch_1v2: int = 0
    clutch_1v3: int = 0
    clutch_1v4: int = 0
    clutch_1v5: int = 0
    flash_assists: int = 0
    flash_duration: float = 0.0
    jump_count: int = 0
    damage_total: int = 0
    damage_received: int = 0
    damage_receive: int = 0
    damage_stats: int = 0
    assisted_kill: int = 0
    awp_kill: int = 0
    awp_kill_ct: int = 0
    awp_kill_t: int = 0
    benefit_kill: int = 0
    day: str = ""
    defused_bomb: int = 0
    end_1v1: int = 0
    end_1v2: int = 0
    end_1v3: int = 0
    end_1v4: int = 0
    end_1v5: int = 0
    explode_bomb: int = 0
    first_death: int = 0
    fd_ct: int = 0
    fd_t: int = 0
    first_kill: int = 0
    flash_enemy: int = 0
    flash_team: int = 0
    flash_team_time: float = 0.0
    flash_time: float = 0.0
    game_mode: str = ""
    group_id: int = 0
    hold_total: int = 0
    id: int = 0
    is_highlight: int = 0
    is_most_1v2: int = 0
    is_most_assist: int = 0
    is_most_awp: int = 0
    is_most_end: int = 0
    is_most_first_kill: int = 0
    is_most_headshot: int = 0
    is_most_jump: int = 0
    is_svp: int = 0
    is_tie: int = 0
    kill_1: int = 0
    kill_2: int = 0
    kill_3: int = 0
    kill_4: int = 0
    kill_5: int = 0
    many_assists_cnt1: int = 0
    many_assists_cnt2: int = 0
    many_assists_cnt3: int = 0
    many_assists_cnt4: int = 0
    many_assists_cnt5: int = 0
    map: str = ""
    match_code: str = ""
    match_mode: str = ""
    match_team_id: int = 0
    match_time: int = 0
    per_headshot: float = 0.0
    perfect_kill: int = 0
    planted_bomb: int = 0
    revenge_kill: int = 0
    round_total: int = 0
    season: str = ""
    team_kill: int = 0
    throw_harm: int = 0
    throw_harm_enemy: int = 0
    uid: int = 0
    year: str = ""
    sts_raw: str = ""
    level_info_raw: str = ""

@dataclass
class RoundEvent:
    event_id: str
    event_type: str # 'kill', 'bomb_plant', etc.
    event_time: int
    attacker_steam_id: Optional[str] = None
    victim_steam_id: Optional[str] = None
    assister_steam_id: Optional[str] = None
    flash_assist_steam_id: Optional[str] = None
    trade_killer_steam_id: Optional[str] = None
    weapon: Optional[str] = None
    is_headshot: bool = False
    is_wallbang: bool = False
    is_blind: bool = False
    is_through_smoke: bool = False
    is_noscope: bool = False
    # Spatial
    attacker_pos: Optional[Tuple[int, int, int]] = None
    victim_pos: Optional[Tuple[int, int, int]] = None
    # Score
    score_change_attacker: float = 0.0
    score_change_victim: float = 0.0

@dataclass
class PlayerEconomy:
    steam_id_64: str
    side: str
    start_money: int = 0
    equipment_value: int = 0
    main_weapon: str = ""
    has_helmet: bool = False
    has_defuser: bool = False
    round_performance_score: float = 0.0

@dataclass
class RoundData:
    round_num: int
    winner_side: str
    win_reason: int
    win_reason_desc: str
    duration: float
    end_time_stamp: str
    ct_score: int
    t_score: int
    ct_money_start: int = 0
    t_money_start: int = 0
    events: List[RoundEvent] = field(default_factory=list)
    economies: List[PlayerEconomy] = field(default_factory=list)

@dataclass
class MatchTeamData:
    group_id: int
    group_all_score: int = 0
    group_change_elo: float = 0.0
    group_fh_role: int = 0
    group_fh_score: int = 0
    group_origin_elo: float = 0.0
    group_sh_role: int = 0
    group_sh_score: int = 0
    group_tid: int = 0
    group_uids: str = ""

@dataclass
class MatchData:
    match_id: str
    match_code: str = ""
    map_name: str = ""
    start_time: int = 0
    end_time: int = 0
    duration: int = 0
    winner_team: int = 0
    score_team1: int = 0
    score_team2: int = 0
    server_ip: str = ""
    server_port: int = 0
    location: str = ""
    has_side_data_and_rating2: int = 0
    match_main_id: int = 0
    demo_url: str = ""
    game_mode: int = 0
    game_name: str = ""
    map_desc: str = ""
    location_full: str = ""
    match_mode: int = 0
    match_status: int = 0
    match_flag: int = 0
    status: int = 0
    waiver: int = 0
    year: int = 0
    season: str = ""
    round_total: int = 0
    cs_type: int = 0
    priority_show_type: int = 0
    pug10m_show_type: int = 0
    credit_match_status: int = 0
    knife_winner: int = 0
    knife_winner_role: int = 0
    most_1v2_uid: int = 0
    most_assist_uid: int = 0
    most_awp_uid: int = 0
    most_end_uid: int = 0
    most_first_kill_uid: int = 0
    most_headshot_uid: int = 0
    most_jump_uid: int = 0
    mvp_uid: int = 0
    response_code: int = 0
    response_message: str = ""
    response_status: int = 0
    response_timestamp: int = 0
    response_trace_id: str = ""
    response_success: int = 0
    response_errcode: int = 0
    treat_info_raw: str = ""
    round_list_raw: str = ""
    leetify_data_raw: str = ""
    data_source_type: str = "unknown"
    players: Dict[str, PlayerStats] = field(default_factory=dict) # Key: steam_id_64
    players_t: Dict[str, PlayerStats] = field(default_factory=dict)
    players_ct: Dict[str, PlayerStats] = field(default_factory=dict)
    rounds: List[RoundData] = field(default_factory=list)
    player_meta: Dict[str, Dict] = field(default_factory=dict) # steam_id -> {uid, name, avatar, ...}
    teams: List[MatchTeamData] = field(default_factory=list)

# --- Database Helper ---

def init_db():
    if os.path.exists(L2_DB_PATH):
        logger.info(f"Removing existing L2 DB at {L2_DB_PATH}")
        try:
            os.remove(L2_DB_PATH)
        except PermissionError:
            logger.error("Cannot remove L2 DB, it might be open.")
            return False

    conn = sqlite3.connect(L2_DB_PATH)
    with open(SCHEMA_PATH, 'r', encoding='utf-8') as f:
        schema_sql = f.read()
        conn.executescript(schema_sql)
    conn.commit()
    conn.close()
    logger.info("L2 DB Initialized.")
    return True

# --- Parsers ---

class MatchParser:
    def __init__(self, match_id, raw_requests):
        self.match_id = match_id
        self.raw_requests = raw_requests
        self.match_data = MatchData(match_id=match_id)
        
        # Extracted JSON bodies
        self.data_match = None
        self.data_match_wrapper = None
        self.data_vip = None
        self.data_leetify = None
        self.data_round_list = None
        
        self._extract_payloads()

    def _extract_payloads(self):
        for req in self.raw_requests:
            url = req.get('url', '')
            body = req.get('body', {})
            
            if not body:
                continue

            # Check URLs
            if 'crane/http/api/data/match/' in url:
                self.data_match_wrapper = body
                self.data_match = body.get('data', {})
            elif 'crane/http/api/data/vip_plus_match_data/' in url:
                self.data_vip = body.get('data', {})
            elif 'crane/http/api/match/leetify_rating/' in url:
                self.data_leetify = body.get('data', {})
            elif 'crane/http/api/match/round/' in url:
                self.data_round_list = body.get('data', {})

    def parse(self) -> MatchData:
        if not self.data_match:
            logger.warning(f"No base match data found for {self.match_id}")
            return self.match_data

        self._parse_base_info()
        self._parse_players_base()
        self._parse_players_vip()
        
        # Decide which round source to use
        if self.data_leetify and self.data_leetify.get('leetify_data'):
            self.match_data.data_source_type = 'leetify'
            try:
                self.match_data.leetify_data_raw = json.dumps(self.data_leetify.get('leetify_data', {}), ensure_ascii=False)
            except:
                self.match_data.leetify_data_raw = ""
            self.match_data.round_list_raw = ""
            self._parse_leetify_rounds()
        elif self.data_round_list and self.data_round_list.get('round_list'):
            self.match_data.data_source_type = 'classic'
            try:
                self.match_data.round_list_raw = json.dumps(self.data_round_list.get('round_list', []), ensure_ascii=False)
            except:
                self.match_data.round_list_raw = ""
            self.match_data.leetify_data_raw = ""
            self._parse_classic_rounds()
        else:
            self.match_data.data_source_type = 'unknown'
            self.match_data.round_list_raw = ""
            self.match_data.leetify_data_raw = ""
            logger.info(f"No round data found for {self.match_id}")

        return self.match_data

    def _parse_base_info(self):
        m = self.data_match.get('main', {})
        self.match_data.match_code = m.get('match_code', '')
        self.match_data.map_name = m.get('map', '')
        self.match_data.start_time = m.get('start_time', 0)
        self.match_data.end_time = m.get('end_time', 0)
        self.match_data.duration = self.match_data.end_time - self.match_data.start_time if self.match_data.end_time else 0
        self.match_data.winner_team = m.get('match_winner', 0)
        self.match_data.score_team1 = m.get('group1_all_score', 0)
        self.match_data.score_team2 = m.get('group2_all_score', 0)
        self.match_data.server_ip = m.get('server_ip', '')
        # Port is sometimes string
        try:
            self.match_data.server_port = int(m.get('server_port', 0))
        except:
            self.match_data.server_port = 0
        self.match_data.location = m.get('location', '')
        def safe_int(val):
            try:
                return int(float(val)) if val is not None else 0
            except:
                return 0
        def safe_float(val):
            try:
                return float(val) if val is not None else 0.0
            except:
                return 0.0
        def safe_text(val):
            return "" if val is None else str(val)
        wrapper = self.data_match_wrapper or {}
        self.match_data.response_code = safe_int(wrapper.get('code'))
        self.match_data.response_message = safe_text(wrapper.get('message'))
        self.match_data.response_status = safe_int(wrapper.get('status'))
        self.match_data.response_timestamp = safe_int(wrapper.get('timeStamp') if wrapper.get('timeStamp') is not None else wrapper.get('timestamp'))
        self.match_data.response_trace_id = safe_text(wrapper.get('traceId') if wrapper.get('traceId') is not None else wrapper.get('trace_id'))
        self.match_data.response_success = safe_int(wrapper.get('success'))
        self.match_data.response_errcode = safe_int(wrapper.get('errcode'))
        self.match_data.has_side_data_and_rating2 = safe_int(self.data_match.get('has_side_data_and_rating2'))
        self.match_data.match_main_id = safe_int(m.get('id'))
        self.match_data.demo_url = safe_text(m.get('demo_url'))
        self.match_data.game_mode = safe_int(m.get('game_mode'))
        self.match_data.game_name = safe_text(m.get('game_name'))
        self.match_data.map_desc = safe_text(m.get('map_desc'))
        self.match_data.location_full = safe_text(m.get('location_full'))
        self.match_data.match_mode = safe_int(m.get('match_mode'))
        self.match_data.match_status = safe_int(m.get('match_status'))
        self.match_data.match_flag = safe_int(m.get('match_flag'))
        self.match_data.status = safe_int(m.get('status'))
        self.match_data.waiver = safe_int(m.get('waiver'))
        self.match_data.year = safe_int(m.get('year'))
        self.match_data.season = safe_text(m.get('season'))
        self.match_data.round_total = safe_int(m.get('round_total'))
        self.match_data.cs_type = safe_int(m.get('cs_type'))
        self.match_data.priority_show_type = safe_int(m.get('priority_show_type'))
        self.match_data.pug10m_show_type = safe_int(m.get('pug10m_show_type'))
        self.match_data.credit_match_status = safe_int(m.get('credit_match_status'))
        self.match_data.knife_winner = safe_int(m.get('knife_winner'))
        self.match_data.knife_winner_role = safe_int(m.get('knife_winner_role'))
        self.match_data.most_1v2_uid = safe_int(m.get('most_1v2_uid'))
        self.match_data.most_assist_uid = safe_int(m.get('most_assist_uid'))
        self.match_data.most_awp_uid = safe_int(m.get('most_awp_uid'))
        self.match_data.most_end_uid = safe_int(m.get('most_end_uid'))
        self.match_data.most_first_kill_uid = safe_int(m.get('most_first_kill_uid'))
        self.match_data.most_headshot_uid = safe_int(m.get('most_headshot_uid'))
        self.match_data.most_jump_uid = safe_int(m.get('most_jump_uid'))
        self.match_data.mvp_uid = safe_int(m.get('mvp_uid'))
        treat_info = self.data_match.get('treat_info')
        if treat_info is not None:
            try:
                self.match_data.treat_info_raw = json.dumps(treat_info, ensure_ascii=False)
            except:
                self.match_data.treat_info_raw = ""
        self.match_data.teams = []
        for idx in [1, 2]:
            team = MatchTeamData(
                group_id=idx,
                group_all_score=safe_int(m.get(f"group{idx}_all_score")),
                group_change_elo=safe_float(m.get(f"group{idx}_change_elo")),
                group_fh_role=safe_int(m.get(f"group{idx}_fh_role")),
                group_fh_score=safe_int(m.get(f"group{idx}_fh_score")),
                group_origin_elo=safe_float(m.get(f"group{idx}_origin_elo")),
                group_sh_role=safe_int(m.get(f"group{idx}_sh_role")),
                group_sh_score=safe_int(m.get(f"group{idx}_sh_score")),
                group_tid=safe_int(m.get(f"group{idx}_tid")),
                group_uids=safe_text(m.get(f"group{idx}_uids"))
            )
            self.match_data.teams.append(team)

    def _parse_players_base(self):
        # Players are in group_1 and group_2 lists in data_match
        groups = []
        if 'group_1' in self.data_match: groups.extend(self.data_match['group_1'])
        if 'group_2' in self.data_match: groups.extend(self.data_match['group_2'])
        def safe_int(val):
            try:
                return int(float(val)) if val is not None else 0
            except:
                return 0
        def safe_text(val):
            return "" if val is None else str(val)

        for p in groups:
            # We need steam_id. 
            # Structure: user_info -> user_data -> steam -> steamId
            user_info = p.get('user_info', {})
            user_data = user_info.get('user_data', {})
            steam_data = user_data.get('steam', {})
            steam_id = str(steam_data.get('steamId', ''))
            
            fight = p.get('fight', {})
            fight_t = p.get('fight_t', {})
            fight_ct = p.get('fight_ct', {})
            uid = fight.get('uid')
            
            # Store meta for dim_players
            user_data = user_info.get('user_data', {})
            profile = user_data.get('profile', {})
            
            # If steam_id is empty, use temporary placeholder '5E:{uid}'
            # Ideally we want steam_id_64.
            if not steam_id and uid:
                steam_id = f"5E:{uid}"
            
            if not steam_id:
                continue

            status = user_data.get('status', {})
            platform_exp = user_data.get('platformExp', {})
            trusted = user_data.get('trusted', {})
            certify = user_data.get('certify', {})
            identity = user_data.get('identity', {})
            plus_info = user_info.get('plus_info', {}) or p.get('plus_info', {})
            user_info_raw = ""
            try:
                user_info_raw = json.dumps(user_info, ensure_ascii=False)
            except:
                user_info_raw = ""

            self.match_data.player_meta[steam_id] = {
                'uid': safe_int(uid),
                'username': safe_text(user_data.get('username')),
                'uuid': safe_text(user_data.get('uuid')),
                'email': safe_text(user_data.get('email')),
                'area': safe_text(user_data.get('area')),
                'mobile': safe_text(user_data.get('mobile')),
                'avatar_url': safe_text(profile.get('avatarUrl')),
                'domain': safe_text(profile.get('domain')),
                'user_domain': safe_text(user_data.get('domain')),
                'created_at': safe_int(user_data.get('createdAt')),
                'updated_at': safe_int(user_data.get('updatedAt')),
                'username_audit_status': safe_int(user_data.get('usernameAuditStatus')),
                'accid': safe_text(user_data.get('Accid')),
                'team_id': safe_int(user_data.get('teamID')),
                'trumpet_count': safe_int(user_data.get('trumpetCount')),
                'profile_nickname': safe_text(profile.get('nickname')),
                'profile_avatar_audit_status': safe_int(profile.get('avatarAuditStatus')),
                'profile_rgb_avatar_url': safe_text(profile.get('rgbAvatarUrl')),
                'profile_photo_url': safe_text(profile.get('photoUrl')),
                'profile_gender': safe_int(profile.get('gender')),
                'profile_birthday': safe_int(profile.get('birthday')),
                'profile_country_id': safe_text(profile.get('countryId')),
                'profile_region_id': safe_text(profile.get('regionId')),
                'profile_city_id': safe_text(profile.get('cityId')),
                'profile_language': safe_text(profile.get('language')),
                'profile_recommend_url': safe_text(profile.get('recommendUrl')),
                'profile_group_id': safe_int(profile.get('groupId')),
                'profile_reg_source': safe_int(profile.get('regSource')),
                'status_status': safe_int(status.get('status')),
                'status_expire': safe_int(status.get('expire')),
                'status_cancellation_status': safe_int(status.get('cancellationStatus')),
                'status_new_user': safe_int(status.get('newUser')),
                'status_login_banned_time': safe_int(status.get('loginBannedTime')),
                'status_anticheat_type': safe_int(status.get('anticheatType')),
                'status_flag_status1': safe_text(status.get('flagStatus1')),
                'status_anticheat_status': safe_text(status.get('anticheatStatus')),
                'status_flag_honor': safe_text(status.get('FlagHonor')),
                'status_privacy_policy_status': safe_int(status.get('PrivacyPolicyStatus')),
                'status_csgo_frozen_exptime': safe_int(status.get('csgoFrozenExptime')),
                'platformexp_level': safe_int(platform_exp.get('level')),
                'platformexp_exp': safe_int(platform_exp.get('exp')),
                'steam_account': safe_text(steam_data.get('steamAccount')),
                'steam_trade_url': safe_text(steam_data.get('tradeUrl')),
                'steam_rent_id': safe_text(steam_data.get('rentSteamId')),
                'trusted_credit': safe_int(trusted.get('credit')),
                'trusted_credit_level': safe_int(trusted.get('creditLevel')),
                'trusted_score': safe_int(trusted.get('score')),
                'trusted_status': safe_int(trusted.get('status')),
                'trusted_credit_status': safe_int(trusted.get('creditStatus')),
                'certify_id_type': safe_int(certify.get('idType')),
                'certify_status': safe_int(certify.get('status')),
                'certify_age': safe_int(certify.get('age')),
                'certify_real_name': safe_text(certify.get('realName')),
                'certify_uid_list': safe_text(json.dumps(certify.get('uidList'), ensure_ascii=False)) if certify.get('uidList') is not None else "",
                'certify_audit_status': safe_int(certify.get('auditStatus')),
                'certify_gender': safe_int(certify.get('gender')),
                'identity_type': safe_int(identity.get('type')),
                'identity_extras': safe_text(identity.get('extras')),
                'identity_status': safe_int(identity.get('status')),
                'identity_slogan': safe_text(identity.get('slogan')),
                'identity_list': safe_text(json.dumps(identity.get('identity_list'), ensure_ascii=False)) if identity.get('identity_list') is not None else "",
                'identity_slogan_ext': safe_text(identity.get('slogan_ext')),
                'identity_live_url': safe_text(identity.get('live_url')),
                'identity_live_type': safe_int(identity.get('live_type')),
                'plus_is_plus': safe_int(plus_info.get('is_plus')),
                'user_info_raw': user_info_raw
            }

            stats = PlayerStats(steam_id_64=steam_id)
            sts = p.get('sts', {})
            level_info = p.get('level_info', {})

            try:
                # Use safe conversion helper
                def safe_int(val):
                    try: return int(float(val)) if val is not None else 0
                    except: return 0
                
                def safe_float(val):
                    try: return float(val) if val is not None else 0.0
                    except: return 0.0
                
                def safe_text(val):
                    return "" if val is None else str(val)
                if sts is not None:
                    try:
                        stats.sts_raw = json.dumps(sts, ensure_ascii=False)
                    except:
                        stats.sts_raw = ""
                if level_info is not None:
                    try:
                        stats.level_info_raw = json.dumps(level_info, ensure_ascii=False)
                    except:
                        stats.level_info_raw = ""

                def get_stat(key):
                    if key in fight and fight.get(key) not in [None, ""]:
                        return fight.get(key)
                    return 0

                def build_side_stats(fight_side, team_id_value):
                    side_stats = PlayerStats(steam_id_64=steam_id)
                    side_stats.team_id = team_id_value
                    side_stats.kills = safe_int(fight_side.get('kill'))
                    side_stats.deaths = safe_int(fight_side.get('death'))
                    side_stats.assists = safe_int(fight_side.get('assist'))
                    side_stats.headshot_count = safe_int(fight_side.get('headshot'))
                    side_stats.adr = safe_float(fight_side.get('adr'))
                    side_stats.rating = safe_float(fight_side.get('rating'))
                    side_stats.rating2 = safe_float(fight_side.get('rating2'))
                    side_stats.rating3 = safe_float(fight_side.get('rating3'))
                    side_stats.rws = safe_float(fight_side.get('rws'))
                    side_stats.mvp_count = safe_int(fight_side.get('is_mvp'))
                    side_stats.flash_duration = safe_float(fight_side.get('flash_enemy_time'))
                    side_stats.jump_count = safe_int(fight_side.get('jump_total'))
                    side_stats.is_win = bool(safe_int(fight_side.get('is_win')))
                    side_stats.assisted_kill = safe_int(fight_side.get('assisted_kill'))
                    side_stats.awp_kill = safe_int(fight_side.get('awp_kill'))
                    side_stats.benefit_kill = safe_int(fight_side.get('benefit_kill'))
                    side_stats.day = safe_text(fight_side.get('day'))
                    side_stats.defused_bomb = safe_int(fight_side.get('defused_bomb'))
                    side_stats.end_1v1 = safe_int(fight_side.get('end_1v1'))
                    side_stats.end_1v2 = safe_int(fight_side.get('end_1v2'))
                    side_stats.end_1v3 = safe_int(fight_side.get('end_1v3'))
                    side_stats.end_1v4 = safe_int(fight_side.get('end_1v4'))
                    side_stats.end_1v5 = safe_int(fight_side.get('end_1v5'))
                    side_stats.explode_bomb = safe_int(fight_side.get('explode_bomb'))
                    side_stats.first_death = safe_int(fight_side.get('first_death'))
                    side_stats.first_kill = safe_int(fight_side.get('first_kill'))
                    side_stats.flash_enemy = safe_int(fight_side.get('flash_enemy'))
                    side_stats.flash_team = safe_int(fight_side.get('flash_team'))
                    side_stats.flash_team_time = safe_float(fight_side.get('flash_team_time'))
                    side_stats.flash_time = safe_float(fight_side.get('flash_time'))
                    side_stats.game_mode = safe_text(fight_side.get('game_mode'))
                    side_stats.group_id = safe_int(fight_side.get('group_id'))
                    side_stats.hold_total = safe_int(fight_side.get('hold_total'))
                    side_stats.id = safe_int(fight_side.get('id'))
                    side_stats.is_highlight = safe_int(fight_side.get('is_highlight'))
                    side_stats.is_most_1v2 = safe_int(fight_side.get('is_most_1v2'))
                    side_stats.is_most_assist = safe_int(fight_side.get('is_most_assist'))
                    side_stats.is_most_awp = safe_int(fight_side.get('is_most_awp'))
                    side_stats.is_most_end = safe_int(fight_side.get('is_most_end'))
                    side_stats.is_most_first_kill = safe_int(fight_side.get('is_most_first_kill'))
                    side_stats.is_most_headshot = safe_int(fight_side.get('is_most_headshot'))
                    side_stats.is_most_jump = safe_int(fight_side.get('is_most_jump'))
                    side_stats.is_svp = safe_int(fight_side.get('is_svp'))
                    side_stats.is_tie = safe_int(fight_side.get('is_tie'))
                    side_stats.kill_1 = safe_int(fight_side.get('kill_1'))
                    side_stats.kill_2 = safe_int(fight_side.get('kill_2'))
                    side_stats.kill_3 = safe_int(fight_side.get('kill_3'))
                    side_stats.kill_4 = safe_int(fight_side.get('kill_4'))
                    side_stats.kill_5 = safe_int(fight_side.get('kill_5'))
                    side_stats.many_assists_cnt1 = safe_int(fight_side.get('many_assists_cnt1'))
                    side_stats.many_assists_cnt2 = safe_int(fight_side.get('many_assists_cnt2'))
                    side_stats.many_assists_cnt3 = safe_int(fight_side.get('many_assists_cnt3'))
                    side_stats.many_assists_cnt4 = safe_int(fight_side.get('many_assists_cnt4'))
                    side_stats.many_assists_cnt5 = safe_int(fight_side.get('many_assists_cnt5'))
                    side_stats.map = safe_text(fight_side.get('map'))
                    side_stats.match_code = safe_text(fight_side.get('match_code'))
                    side_stats.match_mode = safe_text(fight_side.get('match_mode'))
                    side_stats.match_team_id = safe_int(fight_side.get('match_team_id'))
                    side_stats.match_time = safe_int(fight_side.get('match_time'))
                    side_stats.per_headshot = safe_float(fight_side.get('per_headshot'))
                    side_stats.perfect_kill = safe_int(fight_side.get('perfect_kill'))
                    side_stats.planted_bomb = safe_int(fight_side.get('planted_bomb'))
                    side_stats.revenge_kill = safe_int(fight_side.get('revenge_kill'))
                    side_stats.round_total = safe_int(fight_side.get('round_total'))
                    side_stats.season = safe_text(fight_side.get('season'))
                    side_stats.team_kill = safe_int(fight_side.get('team_kill'))
                    side_stats.throw_harm = safe_int(fight_side.get('throw_harm'))
                    side_stats.throw_harm_enemy = safe_int(fight_side.get('throw_harm_enemy'))
                    side_stats.uid = safe_int(fight_side.get('uid'))
                    side_stats.year = safe_text(fight_side.get('year'))

                    # Map missing fields
                    side_stats.clutch_1v1 = side_stats.end_1v1
                    side_stats.clutch_1v2 = side_stats.end_1v2
                    side_stats.clutch_1v3 = side_stats.end_1v3
                    side_stats.clutch_1v4 = side_stats.end_1v4
                    side_stats.clutch_1v5 = side_stats.end_1v5
                    side_stats.entry_kills = side_stats.first_kill
                    side_stats.entry_deaths = side_stats.first_death

                    return side_stats

                team_id_value = safe_int(fight.get('match_team_id'))
                stats.team_id = team_id_value
                stats.kills = safe_int(get_stat('kill'))
                stats.deaths = safe_int(get_stat('death'))
                stats.assists = safe_int(get_stat('assist'))
                stats.headshot_count = safe_int(get_stat('headshot'))
                
                stats.adr = safe_float(get_stat('adr'))
                stats.rating = safe_float(get_stat('rating'))
                stats.rating2 = safe_float(get_stat('rating2'))
                stats.rating3 = safe_float(get_stat('rating3'))
                stats.rws = safe_float(get_stat('rws'))
                
                # is_mvp might be string "1" or int 1
                stats.mvp_count = safe_int(get_stat('is_mvp'))
                
                stats.flash_duration = safe_float(get_stat('flash_enemy_time'))
                stats.jump_count = safe_int(get_stat('jump_total'))
                stats.is_win = bool(safe_int(get_stat('is_win')))
                
                stats.elo_change = safe_float(sts.get('change_elo'))
                stats.rank_score = safe_int(sts.get('rank'))
                stats.assisted_kill = safe_int(fight.get('assisted_kill'))
                stats.awp_kill = safe_int(fight.get('awp_kill'))
                stats.benefit_kill = safe_int(fight.get('benefit_kill'))
                stats.day = safe_text(fight.get('day'))
                stats.defused_bomb = safe_int(fight.get('defused_bomb'))
                stats.end_1v1 = safe_int(fight.get('end_1v1'))
                stats.end_1v2 = safe_int(fight.get('end_1v2'))
                stats.end_1v3 = safe_int(fight.get('end_1v3'))
                stats.end_1v4 = safe_int(fight.get('end_1v4'))
                stats.end_1v5 = safe_int(fight.get('end_1v5'))
                stats.explode_bomb = safe_int(fight.get('explode_bomb'))
                stats.first_death = safe_int(fight.get('first_death'))
                stats.first_kill = safe_int(fight.get('first_kill'))
                stats.flash_enemy = safe_int(fight.get('flash_enemy'))
                stats.flash_team = safe_int(fight.get('flash_team'))
                stats.flash_team_time = safe_float(fight.get('flash_team_time'))
                stats.flash_time = safe_float(fight.get('flash_time'))
                stats.game_mode = safe_text(fight.get('game_mode'))
                stats.group_id = safe_int(fight.get('group_id'))
                stats.hold_total = safe_int(fight.get('hold_total'))
                stats.id = safe_int(fight.get('id'))
                stats.is_highlight = safe_int(fight.get('is_highlight'))
                stats.is_most_1v2 = safe_int(fight.get('is_most_1v2'))
                stats.is_most_assist = safe_int(fight.get('is_most_assist'))
                stats.is_most_awp = safe_int(fight.get('is_most_awp'))
                stats.is_most_end = safe_int(fight.get('is_most_end'))
                stats.is_most_first_kill = safe_int(fight.get('is_most_first_kill'))
                stats.is_most_headshot = safe_int(fight.get('is_most_headshot'))
                stats.is_most_jump = safe_int(fight.get('is_most_jump'))
                stats.is_svp = safe_int(fight.get('is_svp'))
                stats.is_tie = safe_int(fight.get('is_tie'))
                stats.kill_1 = safe_int(fight.get('kill_1'))
                stats.kill_2 = safe_int(fight.get('kill_2'))
                stats.kill_3 = safe_int(fight.get('kill_3'))
                stats.kill_4 = safe_int(fight.get('kill_4'))
                stats.kill_5 = safe_int(fight.get('kill_5'))
                stats.many_assists_cnt1 = safe_int(fight.get('many_assists_cnt1'))
                stats.many_assists_cnt2 = safe_int(fight.get('many_assists_cnt2'))
                stats.many_assists_cnt3 = safe_int(fight.get('many_assists_cnt3'))
                stats.many_assists_cnt4 = safe_int(fight.get('many_assists_cnt4'))
                stats.many_assists_cnt5 = safe_int(fight.get('many_assists_cnt5'))
                stats.map = safe_text(fight.get('map'))
                stats.match_code = safe_text(fight.get('match_code'))
                stats.match_mode = safe_text(fight.get('match_mode'))
                stats.match_team_id = safe_int(fight.get('match_team_id'))
                stats.match_time = safe_int(fight.get('match_time'))
                stats.per_headshot = safe_float(fight.get('per_headshot'))
                stats.perfect_kill = safe_int(fight.get('perfect_kill'))
                stats.planted_bomb = safe_int(fight.get('planted_bomb'))
                stats.revenge_kill = safe_int(fight.get('revenge_kill'))
                stats.round_total = safe_int(fight.get('round_total'))
                stats.season = safe_text(fight.get('season'))
                stats.team_kill = safe_int(fight.get('team_kill'))
                stats.throw_harm = safe_int(fight.get('throw_harm'))
                stats.throw_harm_enemy = safe_int(fight.get('throw_harm_enemy'))
                stats.uid = safe_int(fight.get('uid'))
                stats.year = safe_text(fight.get('year'))
                
                # Map missing fields
                stats.clutch_1v1 = stats.end_1v1
                stats.clutch_1v2 = stats.end_1v2
                stats.clutch_1v3 = stats.end_1v3
                stats.clutch_1v4 = stats.end_1v4
                stats.clutch_1v5 = stats.end_1v5
                stats.entry_kills = stats.first_kill
                stats.entry_deaths = stats.first_death

            except Exception as e:
                logger.error(f"Error parsing stats for {steam_id} in {self.match_id}: {e}")
                pass

            self.match_data.players[steam_id] = stats
            if isinstance(fight_t, dict) and fight_t:
                t_team_id = team_id_value or safe_int(fight_t.get('match_team_id'))
                self.match_data.players_t[steam_id] = build_side_stats(fight_t, t_team_id)
            if isinstance(fight_ct, dict) and fight_ct:
                ct_team_id = team_id_value or safe_int(fight_ct.get('match_team_id'))
                self.match_data.players_ct[steam_id] = build_side_stats(fight_ct, ct_team_id)

    def _parse_players_vip(self):
        if not self.data_vip:
            return

        # Structure: data_vip -> steamid (key) -> dict
        for sid, vdata in self.data_vip.items():
            # SID might be steam_id_64 directly
            if sid in self.match_data.players:
                p = self.match_data.players[sid]
                p.kast = float(vdata.get('kast', 0))
                p.awp_kills = int(vdata.get('awp_kill', 0))
                p.awp_kill_ct = int(vdata.get('awp_kill_ct', 0))
                p.awp_kill_t = int(vdata.get('awp_kill_t', 0))
                p.fd_ct = int(vdata.get('fd_ct', 0))
                p.fd_t = int(vdata.get('fd_t', 0))
                p.damage_receive = int(vdata.get('damage_receive', 0))
                p.damage_stats = int(vdata.get('damage_stats', 0))
                p.damage_total = int(vdata.get('damage_total', 0))
                p.damage_received = int(vdata.get('damage_received', 0))
                p.flash_assists = int(vdata.get('flash_assists', 0))
            else:
                # Try to match by 5E ID if possible, but here keys are steamids usually
                pass
        for sid, p in self.match_data.players.items():
            if sid in self.match_data.players_t:
                self.match_data.players_t[sid].awp_kill_t = p.awp_kill_t
                self.match_data.players_t[sid].fd_t = p.fd_t
            if sid in self.match_data.players_ct:
                self.match_data.players_ct[sid].awp_kill_ct = p.awp_kill_ct
                self.match_data.players_ct[sid].fd_ct = p.fd_ct

    def _parse_leetify_rounds(self):
        l_data = self.data_leetify.get('leetify_data', {})
        round_list = l_data.get('round_stat', [])
        
        for idx, r in enumerate(round_list):
            rd = RoundData(
                round_num=r.get('round', idx + 1),
                winner_side='CT' if r.get('win_reason') in [7, 8, 9] else 'T', # Approximate logic, need real enum
                win_reason=r.get('win_reason', 0),
                win_reason_desc=str(r.get('win_reason', 0)),
                duration=0, # Leetify might not have exact duration easily
                end_time_stamp=r.get('end_ts', ''),
                ct_score=r.get('sfui_event', {}).get('score_ct', 0),
                t_score=r.get('sfui_event', {}).get('score_t', 0),
                ct_money_start=r.get('ct_money_group', 0),
                t_money_start=r.get('t_money_group', 0)
            )
            
            # Events
            # Leetify has 'show_event' list
            events = r.get('show_event', [])
            for evt in events:
                e_type_code = evt.get('event_type')
                # Mapping needed for event types. 
                # Assuming 3 is kill based on schema 'kill_event' presence
                
                if evt.get('kill_event'):
                    k = evt['kill_event']
                    re = RoundEvent(
                        event_id=f"{self.match_id}_{rd.round_num}_{k.get('Ts', '')}_{k.get('Killer')}",
                        event_type='kill',
                        event_time=evt.get('ts', 0),
                        attacker_steam_id=k.get('Killer'),
                        victim_steam_id=k.get('Victim'),
                        weapon=k.get('WeaponName'),
                        is_headshot=k.get('Headshot', False),
                        is_wallbang=k.get('Penetrated', False),
                        is_blind=k.get('AttackerBlind', False),
                        is_through_smoke=k.get('ThroughSmoke', False),
                        is_noscope=k.get('NoScope', False)
                    )
                    
                    # Leetify specifics
                    # Trade?
                    if evt.get('trade_score_change'):
                        re.trade_killer_steam_id = list(evt['trade_score_change'].keys())[0]
                    
                    if evt.get('flash_assist_killer_score_change'):
                        re.flash_assist_steam_id = list(evt['flash_assist_killer_score_change'].keys())[0]
                        
                    # Score changes
                    if evt.get('killer_score_change'):
                        # e.g. {'<steamid>': {'score': 17.0}}
                        vals = list(evt['killer_score_change'].values())
                        if vals: re.score_change_attacker = vals[0].get('score', 0)
                    
                    if evt.get('victim_score_change'):
                        vals = list(evt['victim_score_change'].values())
                        if vals: re.score_change_victim = vals[0].get('score', 0)

                    rd.events.append(re)

            bron_equipment = r.get('bron_equipment') or {}
            player_t_score = r.get('player_t_score') or {}
            player_ct_score = r.get('player_ct_score') or {}
            player_bron_crash = r.get('player_bron_crash') or {}

            def pick_main_weapon(items):
                if not isinstance(items, list):
                    return ""
                ignore = {
                    "weapon_knife",
                    "weapon_knife_t",
                    "weapon_knife_gg",
                    "weapon_knife_ct",
                    "weapon_c4",
                    "weapon_flashbang",
                    "weapon_hegrenade",
                    "weapon_smokegrenade",
                    "weapon_molotov",
                    "weapon_incgrenade",
                    "weapon_decoy"
                }
                for it in items:
                    if not isinstance(it, dict):
                        continue
                    name = it.get('WeaponName')
                    if name and name not in ignore:
                        return name
                for it in items:
                    if not isinstance(it, dict):
                        continue
                    name = it.get('WeaponName')
                    if name:
                        return name
                return ""

            def pick_money(items):
                if not isinstance(items, list):
                    return 0
                vals = []
                for it in items:
                    if isinstance(it, dict) and it.get('Money') is not None:
                        vals.append(it.get('Money'))
                return int(max(vals)) if vals else 0

            side_scores = {}
            for sid, val in player_t_score.items():
                side_scores[str(sid)] = ("T", float(val) if val is not None else 0.0)
            for sid, val in player_ct_score.items():
                side_scores[str(sid)] = ("CT", float(val) if val is not None else 0.0)

            for sid in set(list(side_scores.keys()) + [str(k) for k in bron_equipment.keys()]):
                if sid not in side_scores:
                    continue
                side, score = side_scores[sid]
                items = bron_equipment.get(sid) or bron_equipment.get(str(sid)) or []
                start_money = pick_money(items)
                equipment_value = player_bron_crash.get(sid)
                if equipment_value is None:
                    equipment_value = player_bron_crash.get(str(sid))
                equipment_value = int(equipment_value) if equipment_value is not None else 0
                main_weapon = pick_main_weapon(items)
                
                has_helmet = False
                has_defuser = False
                if isinstance(items, list):
                    for it in items:
                        if isinstance(it, dict):
                            name = it.get('WeaponName', '')
                            if name == 'item_assaultsuit':
                                has_helmet = True
                            elif name == 'item_defuser':
                                has_defuser = True

                rd.economies.append(PlayerEconomy(
                    steam_id_64=str(sid),
                    side=side,
                    start_money=start_money,
                    equipment_value=equipment_value,
                    main_weapon=main_weapon,
                    has_helmet=has_helmet,
                    has_defuser=has_defuser,
                    round_performance_score=float(score)
                ))

            self.match_data.rounds.append(rd)

    def _parse_classic_rounds(self):
        r_list = self.data_round_list.get('round_list', [])
        for idx, r in enumerate(r_list):
            # Classic round data often lacks score/winner in the list root?
            # Check schema: 'current_score' -> ct/t
            cur_score = r.get('current_score', {})
            
            rd = RoundData(
                round_num=idx + 1,
                winner_side='None', # Default to None if unknown
                win_reason=0,
                win_reason_desc='',
                duration=float(cur_score.get('final_round_time', 0)),
                end_time_stamp='',
                ct_score=cur_score.get('ct', 0),
                t_score=cur_score.get('t', 0)
            )

            # Kills
            # Classic has 'all_kill' list
            kills = r.get('all_kill', [])
            for k in kills:
                attacker = k.get('attacker', {})
                victim = k.get('victim', {})
                
                # Pos extraction
                apos = attacker.get('pos', {})
                vpos = victim.get('pos', {})
                
                re = RoundEvent(
                    event_id=f"{self.match_id}_{rd.round_num}_{k.get('pasttime')}_{attacker.get('steamid_64')}",
                    event_type='kill',
                    event_time=k.get('pasttime', 0),
                    attacker_steam_id=str(attacker.get('steamid_64', '')),
                    victim_steam_id=str(victim.get('steamid_64', '')),
                    weapon=k.get('weapon', ''),
                    is_headshot=k.get('headshot', False),
                    is_wallbang=k.get('penetrated', False),
                    is_blind=k.get('attackerblind', False),
                    is_through_smoke=k.get('throughsmoke', False),
                    is_noscope=k.get('noscope', False),
                    attacker_pos=(apos.get('x', 0), apos.get('y', 0), apos.get('z', 0)),
                    victim_pos=(vpos.get('x', 0), vpos.get('y', 0), vpos.get('z', 0))
                )
                rd.events.append(re)
            
            self.match_data.rounds.append(rd)

# --- Main Execution ---

def process_matches():
    if not init_db():
        return

    l1_conn = sqlite3.connect(L1A_DB_PATH)
    l1_cursor = l1_conn.cursor()
    
    l2_conn = sqlite3.connect(L2_DB_PATH)
    l2_cursor = l2_conn.cursor()

    logger.info("Reading from L1A...")
    l1_cursor.execute("SELECT match_id, content FROM raw_iframe_network")
    
    count = 0
    while True:
        rows = l1_cursor.fetchmany(10)
        if not rows:
            break
            
        for row in rows:
            match_id, content = row
            try:
                raw_requests = json.loads(content)
                parser = MatchParser(match_id, raw_requests)
                match_data = parser.parse()
                save_match(l2_cursor, match_data)
                count += 1
                if count % 10 == 0:
                    l2_conn.commit()
                    print(f"Processed {count} matches...", end='\r')
            except Exception as e:
                logger.error(f"Error processing match {match_id}: {e}")
                # continue

    l2_conn.commit()
    l1_conn.close()
    l2_conn.close()
    logger.info(f"\nDone. Processed {count} matches.")

def save_match(cursor, m: MatchData):
    # 1. Dim Players (Upsert)
    player_meta_columns = [
        "steam_id_64", "uid", "username", "avatar_url", "domain", "created_at", "updated_at",
        "last_seen_match_id", "uuid", "email", "area", "mobile", "user_domain",
        "username_audit_status", "accid", "team_id", "trumpet_count",
        "profile_nickname", "profile_avatar_audit_status", "profile_rgb_avatar_url",
        "profile_photo_url", "profile_gender", "profile_birthday", "profile_country_id",
        "profile_region_id", "profile_city_id", "profile_language", "profile_recommend_url",
        "profile_group_id", "profile_reg_source", "status_status", "status_expire",
        "status_cancellation_status", "status_new_user", "status_login_banned_time",
        "status_anticheat_type", "status_flag_status1", "status_anticheat_status",
        "status_flag_honor", "status_privacy_policy_status", "status_csgo_frozen_exptime",
        "platformexp_level", "platformexp_exp", "steam_account", "steam_trade_url",
        "steam_rent_id", "trusted_credit", "trusted_credit_level", "trusted_score",
        "trusted_status", "trusted_credit_status", "certify_id_type", "certify_status",
        "certify_age", "certify_real_name", "certify_uid_list", "certify_audit_status",
        "certify_gender", "identity_type", "identity_extras", "identity_status",
        "identity_slogan", "identity_list", "identity_slogan_ext", "identity_live_url",
        "identity_live_type", "plus_is_plus", "user_info_raw"
    ]
    player_meta_placeholders = ",".join(["?"] * len(player_meta_columns))
    player_meta_columns_sql = ",".join(player_meta_columns)
    for sid, meta in m.player_meta.items():
        cursor.execute("""
            INSERT INTO dim_players (""" + player_meta_columns_sql + """)
            VALUES (""" + player_meta_placeholders + """)
            ON CONFLICT(steam_id_64) DO UPDATE SET
                uid=excluded.uid,
                username=excluded.username,
                avatar_url=excluded.avatar_url,
                domain=excluded.domain,
                created_at=excluded.created_at,
                updated_at=excluded.updated_at,
                last_seen_match_id=excluded.last_seen_match_id,
                uuid=excluded.uuid,
                email=excluded.email,
                area=excluded.area,
                mobile=excluded.mobile,
                user_domain=excluded.user_domain,
                username_audit_status=excluded.username_audit_status,
                accid=excluded.accid,
                team_id=excluded.team_id,
                trumpet_count=excluded.trumpet_count,
                profile_nickname=excluded.profile_nickname,
                profile_avatar_audit_status=excluded.profile_avatar_audit_status,
                profile_rgb_avatar_url=excluded.profile_rgb_avatar_url,
                profile_photo_url=excluded.profile_photo_url,
                profile_gender=excluded.profile_gender,
                profile_birthday=excluded.profile_birthday,
                profile_country_id=excluded.profile_country_id,
                profile_region_id=excluded.profile_region_id,
                profile_city_id=excluded.profile_city_id,
                profile_language=excluded.profile_language,
                profile_recommend_url=excluded.profile_recommend_url,
                profile_group_id=excluded.profile_group_id,
                profile_reg_source=excluded.profile_reg_source,
                status_status=excluded.status_status,
                status_expire=excluded.status_expire,
                status_cancellation_status=excluded.status_cancellation_status,
                status_new_user=excluded.status_new_user,
                status_login_banned_time=excluded.status_login_banned_time,
                status_anticheat_type=excluded.status_anticheat_type,
                status_flag_status1=excluded.status_flag_status1,
                status_anticheat_status=excluded.status_anticheat_status,
                status_flag_honor=excluded.status_flag_honor,
                status_privacy_policy_status=excluded.status_privacy_policy_status,
                status_csgo_frozen_exptime=excluded.status_csgo_frozen_exptime,
                platformexp_level=excluded.platformexp_level,
                platformexp_exp=excluded.platformexp_exp,
                steam_account=excluded.steam_account,
                steam_trade_url=excluded.steam_trade_url,
                steam_rent_id=excluded.steam_rent_id,
                trusted_credit=excluded.trusted_credit,
                trusted_credit_level=excluded.trusted_credit_level,
                trusted_score=excluded.trusted_score,
                trusted_status=excluded.trusted_status,
                trusted_credit_status=excluded.trusted_credit_status,
                certify_id_type=excluded.certify_id_type,
                certify_status=excluded.certify_status,
                certify_age=excluded.certify_age,
                certify_real_name=excluded.certify_real_name,
                certify_uid_list=excluded.certify_uid_list,
                certify_audit_status=excluded.certify_audit_status,
                certify_gender=excluded.certify_gender,
                identity_type=excluded.identity_type,
                identity_extras=excluded.identity_extras,
                identity_status=excluded.identity_status,
                identity_slogan=excluded.identity_slogan,
                identity_list=excluded.identity_list,
                identity_slogan_ext=excluded.identity_slogan_ext,
                identity_live_url=excluded.identity_live_url,
                identity_live_type=excluded.identity_live_type,
                plus_is_plus=excluded.plus_is_plus,
                user_info_raw=excluded.user_info_raw
        """, (
            sid, meta.get('uid'), meta.get('username'), meta.get('avatar_url'),
            meta.get('domain'), meta.get('created_at'), meta.get('updated_at'),
            m.match_id, meta.get('uuid'), meta.get('email'), meta.get('area'),
            meta.get('mobile'), meta.get('user_domain'), meta.get('username_audit_status'),
            meta.get('accid'), meta.get('team_id'), meta.get('trumpet_count'),
            meta.get('profile_nickname'), meta.get('profile_avatar_audit_status'),
            meta.get('profile_rgb_avatar_url'), meta.get('profile_photo_url'),
            meta.get('profile_gender'), meta.get('profile_birthday'),
            meta.get('profile_country_id'), meta.get('profile_region_id'),
            meta.get('profile_city_id'), meta.get('profile_language'),
            meta.get('profile_recommend_url'), meta.get('profile_group_id'),
            meta.get('profile_reg_source'), meta.get('status_status'),
            meta.get('status_expire'), meta.get('status_cancellation_status'),
            meta.get('status_new_user'), meta.get('status_login_banned_time'),
            meta.get('status_anticheat_type'), meta.get('status_flag_status1'),
            meta.get('status_anticheat_status'), meta.get('status_flag_honor'),
            meta.get('status_privacy_policy_status'), meta.get('status_csgo_frozen_exptime'),
            meta.get('platformexp_level'), meta.get('platformexp_exp'),
            meta.get('steam_account'), meta.get('steam_trade_url'),
            meta.get('steam_rent_id'), meta.get('trusted_credit'),
            meta.get('trusted_credit_level'), meta.get('trusted_score'),
            meta.get('trusted_status'), meta.get('trusted_credit_status'),
            meta.get('certify_id_type'), meta.get('certify_status'),
            meta.get('certify_age'), meta.get('certify_real_name'),
            meta.get('certify_uid_list'), meta.get('certify_audit_status'),
            meta.get('certify_gender'), meta.get('identity_type'),
            meta.get('identity_extras'), meta.get('identity_status'),
            meta.get('identity_slogan'), meta.get('identity_list'),
            meta.get('identity_slogan_ext'), meta.get('identity_live_url'),
            meta.get('identity_live_type'), meta.get('plus_is_plus'),
            meta.get('user_info_raw')
        ))

    # 2. Dim Maps (Ignore if exists)
    if m.map_name:
        cursor.execute("""
            INSERT INTO dim_maps (map_name, map_desc)
            VALUES (?, ?)
            ON CONFLICT(map_name) DO UPDATE SET
                map_desc=excluded.map_desc
        """, (m.map_name, m.map_desc))

    # 3. Fact Matches
    cursor.execute("""
        INSERT OR REPLACE INTO fact_matches 
        (match_id, match_code, map_name, start_time, end_time, duration, winner_team, score_team1, score_team2, server_ip, server_port, location, has_side_data_and_rating2, match_main_id, demo_url, game_mode, game_name, map_desc, location_full, match_mode, match_status, match_flag, status, waiver, year, season, round_total, cs_type, priority_show_type, pug10m_show_type, credit_match_status, knife_winner, knife_winner_role, most_1v2_uid, most_assist_uid, most_awp_uid, most_end_uid, most_first_kill_uid, most_headshot_uid, most_jump_uid, mvp_uid, response_code, response_message, response_status, response_timestamp, response_trace_id, response_success, response_errcode, treat_info_raw, round_list_raw, leetify_data_raw, data_source_type)
        VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
    """, (
        m.match_id, m.match_code, m.map_name, m.start_time, m.end_time, m.duration, 
        m.winner_team, m.score_team1, m.score_team2, m.server_ip, m.server_port, m.location,
        m.has_side_data_and_rating2, m.match_main_id, m.demo_url, m.game_mode, m.game_name, m.map_desc,
        m.location_full, m.match_mode, m.match_status, m.match_flag, m.status, m.waiver, m.year, m.season,
        m.round_total, m.cs_type, m.priority_show_type, m.pug10m_show_type, m.credit_match_status,
        m.knife_winner, m.knife_winner_role, m.most_1v2_uid, m.most_assist_uid, m.most_awp_uid,
        m.most_end_uid, m.most_first_kill_uid, m.most_headshot_uid, m.most_jump_uid, m.mvp_uid,
        m.response_code, m.response_message, m.response_status, m.response_timestamp, m.response_trace_id,
        m.response_success, m.response_errcode, m.treat_info_raw, m.round_list_raw, m.leetify_data_raw, m.data_source_type
    ))

    for t in m.teams:
        cursor.execute("""
            INSERT OR REPLACE INTO fact_match_teams
            (match_id, group_id, group_all_score, group_change_elo, group_fh_role, group_fh_score, group_origin_elo, group_sh_role, group_sh_score, group_tid, group_uids)
            VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
        """, (
            m.match_id, t.group_id, t.group_all_score, t.group_change_elo, t.group_fh_role, t.group_fh_score,
            t.group_origin_elo, t.group_sh_role, t.group_sh_score, t.group_tid, t.group_uids
        ))

    # 4. Fact Match Players
    player_columns = [
        "match_id", "steam_id_64", "team_id", "kills", "deaths", "assists", "headshot_count",
        "kd_ratio", "adr", "rating", "rating2", "rating3", "rws", "mvp_count", "elo_change",
        "rank_score", "is_win", "kast", "entry_kills", "entry_deaths", "awp_kills",
        "clutch_1v1", "clutch_1v2", "clutch_1v3", "clutch_1v4", "clutch_1v5",
        "flash_assists", "flash_duration", "jump_count", "damage_total", "damage_received",
        "damage_receive", "damage_stats", "assisted_kill", "awp_kill", "awp_kill_ct",
        "awp_kill_t", "benefit_kill", "day", "defused_bomb", "end_1v1",
        "end_1v2", "end_1v3", "end_1v4", "end_1v5", "explode_bomb", "first_death",
        "fd_ct", "fd_t", "first_kill", "flash_enemy", "flash_team", "flash_team_time", "flash_time",
        "game_mode", "group_id", "hold_total", "id", "is_highlight", "is_most_1v2",
        "is_most_assist", "is_most_awp", "is_most_end", "is_most_first_kill",
        "is_most_headshot", "is_most_jump", "is_svp", "is_tie", "kill_1", "kill_2",
        "kill_3", "kill_4", "kill_5", "many_assists_cnt1", "many_assists_cnt2",
        "many_assists_cnt3", "many_assists_cnt4", "many_assists_cnt5", "map",
        "match_code", "match_mode", "match_team_id", "match_time", "per_headshot",
        "perfect_kill", "planted_bomb", "revenge_kill", "round_total", "season",
        "team_kill", "throw_harm", "throw_harm_enemy", "uid", "year", "sts_raw", "level_info_raw"
    ]
    player_placeholders = ",".join(["?"] * len(player_columns))
    player_columns_sql = ",".join(player_columns)

    def player_values(sid, p):
        return [
            m.match_id, sid, p.team_id, p.kills, p.deaths, p.assists, p.headshot_count,
            p.kd_ratio, p.adr, p.rating, p.rating2, p.rating3, p.rws, p.mvp_count,
            p.elo_change, p.rank_score, p.is_win, p.kast, p.entry_kills, p.entry_deaths,
            p.awp_kills, p.clutch_1v1, p.clutch_1v2, p.clutch_1v3, p.clutch_1v4,
            p.clutch_1v5, p.flash_assists, p.flash_duration, p.jump_count, p.damage_total,
            p.damage_received, p.damage_receive, p.damage_stats, p.assisted_kill, p.awp_kill,
            p.awp_kill_ct, p.awp_kill_t, p.benefit_kill, p.day, p.defused_bomb, p.end_1v1,
            p.end_1v2, p.end_1v3, p.end_1v4, p.end_1v5, p.explode_bomb, p.first_death,
            p.fd_ct, p.fd_t, p.first_kill, p.flash_enemy, p.flash_team,
            p.flash_team_time, p.flash_time, p.game_mode, p.group_id, p.hold_total,
            p.id, p.is_highlight, p.is_most_1v2, p.is_most_assist, p.is_most_awp,
            p.is_most_end, p.is_most_first_kill, p.is_most_headshot, p.is_most_jump,
            p.is_svp, p.is_tie, p.kill_1, p.kill_2, p.kill_3, p.kill_4, p.kill_5,
            p.many_assists_cnt1, p.many_assists_cnt2, p.many_assists_cnt3, p.many_assists_cnt4,
            p.many_assists_cnt5, p.map, p.match_code, p.match_mode, p.match_team_id,
            p.match_time, p.per_headshot, p.perfect_kill, p.planted_bomb, p.revenge_kill,
            p.round_total, p.season, p.team_kill, p.throw_harm, p.throw_harm_enemy,
            p.uid, p.year, p.sts_raw, p.level_info_raw
        ]

    for sid, p in m.players.items():
        cursor.execute(
            f"INSERT OR REPLACE INTO fact_match_players ({player_columns_sql}) VALUES ({player_placeholders})",
            player_values(sid, p)
        )
    for sid, p in m.players_t.items():
        cursor.execute(
            f"INSERT OR REPLACE INTO fact_match_players_t ({player_columns_sql}) VALUES ({player_placeholders})",
            player_values(sid, p)
        )
    for sid, p in m.players_ct.items():
        cursor.execute(
            f"INSERT OR REPLACE INTO fact_match_players_ct ({player_columns_sql}) VALUES ({player_placeholders})",
            player_values(sid, p)
        )

    # 5. Rounds & Events
    for r in m.rounds:
        cursor.execute("""
            INSERT OR REPLACE INTO fact_rounds
            (match_id, round_num, winner_side, win_reason, win_reason_desc, duration, end_time_stamp, ct_score, t_score, ct_money_start, t_money_start)
            VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
        """, (
            m.match_id, r.round_num, r.winner_side, r.win_reason, r.win_reason_desc, 
            r.duration, r.end_time_stamp, r.ct_score, r.t_score, r.ct_money_start, r.t_money_start
        ))
        
        for e in r.events:
            # Handle Pos
            ax, ay, az = e.attacker_pos if e.attacker_pos else (None, None, None)
            vx, vy, vz = e.victim_pos if e.victim_pos else (None, None, None)
            
            # Use uuid for event_id to ensure uniqueness if logic fails
            import uuid
            if not e.event_id:
                e.event_id = str(uuid.uuid4())
                
            cursor.execute("""
                INSERT OR REPLACE INTO fact_round_events
                (event_id, match_id, round_num, event_type, event_time, attacker_steam_id, victim_steam_id, 
                weapon, is_headshot, is_wallbang, is_blind, is_through_smoke, is_noscope,
                trade_killer_steam_id, flash_assist_steam_id, score_change_attacker, score_change_victim,
                attacker_pos_x, attacker_pos_y, attacker_pos_z, victim_pos_x, victim_pos_y, victim_pos_z)
                VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
            """, (
                e.event_id, m.match_id, r.round_num, e.event_type, e.event_time, e.attacker_steam_id, e.victim_steam_id,
                e.weapon, e.is_headshot, e.is_wallbang, e.is_blind, e.is_through_smoke, e.is_noscope,
                e.trade_killer_steam_id, e.flash_assist_steam_id, e.score_change_attacker, e.score_change_victim,
                ax, ay, az, vx, vy, vz
            ))

        for pe in r.economies:
            cursor.execute("""
                INSERT OR REPLACE INTO fact_round_player_economy
                (match_id, round_num, steam_id_64, side, start_money, equipment_value, main_weapon, has_helmet, has_defuser, round_performance_score)
                VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
            """, (
                m.match_id, r.round_num, pe.steam_id_64, pe.side, pe.start_money, pe.equipment_value, pe.main_weapon, pe.has_helmet, pe.has_defuser, pe.round_performance_score
            ))

if __name__ == "__main__":
    process_matches()
