# discord_tools/scripts/profile_stalker.py
import os
import sys
import asyncio
import websockets
import json
import aiohttp
from datetime import datetime
from collections import defaultdict

# Add the parent directory to the Python path
script_dir = os.path.dirname(os.path.abspath(__file__))
project_root = os.path.dirname(os.path.dirname(script_dir))
sys.path.insert(0, project_root)

from discord_tools.config.settings import DISCORD_TOKEN, DATA_DIR
from discord_tools.utils.api_utils import make_discord_request

class ProfileStalker:
    def __init__(self, user_ids):
        self.user_ids = user_ids if isinstance(user_ids, list) else [user_ids]
        self.active = True
        self.user_data = {}  # Stores current state for each user
        self.change_log = {
            'profile': [],  # Username, avatar, banner, bio, etc
            'activity': [],  # Status, games, Spotify, typing
            'social': []    # Friends, servers, roles, nicknames
        }
        self.message_counts = defaultdict(lambda: defaultdict(int))  # user_id -> channel_id -> count
        self.spotify_history = defaultdict(list)  # user_id -> [songs]
        self.voice_sessions = {}  # Track voice session start times
        self.voice_history = defaultdict(list)  # user_id -> [sessions]
        self.active_calls = {}  # Track active DM calls
        self.start_time = None
        self.total_reconnects = 0
        
        # Cache for guild/channel names
        self.guild_cache = {}
        self.channel_cache = {}
        
        # Initialize user data structure
        for user_id in self.user_ids:
            self.user_data[user_id] = {
                'username': None,
                'global_name': None,
                'discriminator': None,
                'avatar': None,
                'avatar_decoration': None,
                'banner': None,
                'bio': None,
                'pronouns': None,
                'clan_tag': None,
                'connections': [],
                'status': None,
                'custom_status': None,
                'activities': [],
                'current_spotify_song': None,
                'voice_channel': None,
                'voice_guild': None,
                'voice_muted': None,
                'voice_deafened': None,
                'voice_video': None,
                'voice_screenshare': None,
                'mutual_guilds': [],
                'mutual_friends': [],
                'guild_nicknames': {},
                'guild_roles': {},
                'badges': None,
                'nitro': None,
                'accent_color': None,
                'banner_color': None,
                'theme_colors': None,
                'typing_in': []
            }
    
    def get_output_folder(self, user_id, subfolder=None):
        """Get organized output folder path"""
        base = os.path.join(DATA_DIR, f"stalker_{user_id}")
        if subfolder:
            return os.path.join(base, subfolder)
        return base
    
    def get_guild_name(self, guild_id):
        if guild_id in self.guild_cache:
            return self.guild_cache[guild_id]
        
        response = make_discord_request('GET', f'/guilds/{guild_id}')
        if response:
            name = response.json().get('name', f'Guild-{guild_id}')
            self.guild_cache[guild_id] = name
            return name
        
        return f'Guild-{guild_id}'
    
    def get_channel_name(self, channel_id):
        if channel_id in self.channel_cache:
            return self.channel_cache[channel_id]
        
        response = make_discord_request('GET', f'/channels/{channel_id}')
        if response:
            name = response.json().get('name', f'Channel-{channel_id}')
            self.channel_cache[channel_id] = name
            return name
        
        return f'Channel-{channel_id}'
    
    def log_change(self, user_id, category, change_type, old_value, new_value, extra_info=None):
        """Log a change with timestamp and category"""
        timestamp = datetime.now()
        
        change_entry = {
            'timestamp': timestamp.isoformat(),
            'user_id': user_id,
            'type': change_type,
            'old': old_value,
            'new': new_value
        }
        
        if extra_info:
            change_entry['extra'] = extra_info
        
        self.change_log[category].append(change_entry)
        
        # Print to console with category color coding
        time_str = timestamp.strftime('%H:%M:%S')
        category_prefix = {
            'profile': '👤',
            'activity': '🎮',
            'social': '👥'
        }
        prefix = category_prefix.get(category, '📝')
        print(f"{prefix} [{time_str}] {change_type}: {old_value} → {new_value}")
    
    async def download_asset(self, session, url, folder, filename):
        try:
            async with session.get(url) as resp:
                if resp.status == 200:
                    filepath = os.path.join(folder, filename)
                    with open(filepath, 'wb') as f:
                        f.write(await resp.read())
                    return filepath
        except Exception as e:
            print(f"Failed to download {filename}: {e}")
        return None
    
    async def fetch_user_profile(self, user_id):
        response = make_discord_request('GET', f'/users/{user_id}/profile')
        if response:
            return response.json()
        return None
    
    def handle_presence_update(self, data, session):
        user_id = data.get('user', {}).get('id')
        
        if user_id not in self.user_ids:
            return
        
        user_data = self.user_data[user_id]
        
        # Status change
        status = data.get('status')
        if status and status != user_data['status']:
            self.log_change(user_id, 'activity', 'Status', user_data['status'], status)
            user_data['status'] = status
        
        # Activities
        activities = data.get('activities', [])
        
        # Custom status
        custom_status = None
        for activity in activities:
            if activity.get('type') == 4:
                custom_status = activity.get('state', '')
                emoji = activity.get('emoji')
                if emoji:
                    emoji_str = emoji.get('name', '')
                    custom_status = f"{emoji_str} {custom_status}".strip()
        
        if custom_status != user_data['custom_status']:
            self.log_change(user_id, 'activity', 'Custom Status', user_data['custom_status'], custom_status)
            user_data['custom_status'] = custom_status
        
        # Other activities
        activity_names = []
        spotify_song = None
        
        for activity in activities:
            if activity.get('type') == 2:  # Spotify
                song_name = activity.get('details', 'Unknown')
                artist = activity.get('state', 'Unknown')
                album = activity.get('assets', {}).get('large_text', 'Unknown')
                
                spotify_song = f"{song_name} by {artist}"
                activity_names.append(f"Listening to {spotify_song}")
                
                if spotify_song != user_data.get('current_spotify_song'):
                    self.log_change(user_id, 'activity', 'Spotify', 
                                  user_data.get('current_spotify_song'), 
                                  spotify_song,
                                  {'song': song_name, 'artist': artist, 'album': album})
                    user_data['current_spotify_song'] = spotify_song
                    
                    # Add to Spotify history
                    self.spotify_history[user_id].append({
                        'timestamp': datetime.now().isoformat(),
                        'song': song_name,
                        'artist': artist,
                        'album': album
                    })
                    
            elif activity.get('type') in [0, 1]:
                activity_type = ['Playing', 'Streaming'][activity['type']]
                activity_name = activity.get('name', 'Unknown')
                activity_names.append(f"{activity_type} {activity_name}")
        
        if not spotify_song and user_data.get('current_spotify_song'):
            self.log_change(user_id, 'activity', 'Spotify', user_data.get('current_spotify_song'), 'Stopped listening')
            user_data['current_spotify_song'] = None
        
        current_activities = ', '.join(activity_names) if activity_names else None
        old_activities = ', '.join(user_data['activities']) if user_data['activities'] else None
        
        if current_activities != old_activities:
            self.log_change(user_id, 'activity', 'Activity', old_activities, current_activities)
            user_data['activities'] = activity_names
    
    def handle_call_create(self, data):
        channel_id = data.get('channel_id')
        ringing = data.get('ringing', [])
        
        for user_id in self.user_ids:
            if user_id in ringing:
                timestamp = datetime.now().strftime('%H:%M:%S')
                print(f"📞 [{timestamp}] Incoming call in DM {channel_id}")
                self.active_calls[channel_id] = {
                    'start_time': datetime.now(),
                    'participants': ringing
                }
    
    def handle_call_update(self, data):
        channel_id = data.get('channel_id')
        ringing = data.get('ringing', [])
        
        for user_id in self.user_ids:
            if user_id in ringing:
                timestamp = datetime.now().strftime('%H:%M:%S')
                print(f"📞 [{timestamp}] User {user_id} in call {channel_id}")
    
    def handle_call_delete(self, data):
        channel_id = data.get('channel_id')
        
        if channel_id in self.active_calls:
            call_info = self.active_calls[channel_id]
            duration = (datetime.now() - call_info['start_time']).total_seconds()
            duration_min = duration / 60
            
            timestamp = datetime.now().strftime('%H:%M:%S')
            print(f"📞 [{timestamp}] Call ended in DM {channel_id} (duration: {duration_min:.1f} min)")
            
            del self.active_calls[channel_id]
    
    def handle_guild_member_update(self, data):
        member_data = data
        user = member_data.get('user', {})
        user_id = user.get('id')
        
        if user_id not in self.user_ids:
            return
        
        guild_id = member_data.get('guild_id')
        user_data = self.user_data[user_id]
        
        # Nickname change
        nick = member_data.get('nick')
        old_nick = user_data['guild_nicknames'].get(guild_id)
        
        if nick != old_nick:
            guild_name = self.get_guild_name(guild_id)
            
            self.log_change(user_id, 'social', 'Nickname', 
                          f"{guild_name}: {old_nick or 'None'}", 
                          f"{guild_name}: {nick or 'None'}",
                          {'guild_id': guild_id})
            
            if nick:
                user_data['guild_nicknames'][guild_id] = nick
            elif guild_id in user_data['guild_nicknames']:
                del user_data['guild_nicknames'][guild_id]
        
        # Role changes
        new_roles = set(member_data.get('roles', []))
        old_roles = set(user_data['guild_roles'].get(guild_id, []))
        
        added_roles = new_roles - old_roles
        removed_roles = old_roles - new_roles
        
        if added_roles or removed_roles:
            guild_name = self.get_guild_name(guild_id)
            
            if added_roles:
                self.log_change(user_id, 'social', 'Roles Added',
                              f"{guild_name}: {len(old_roles)} roles",
                              f"{guild_name}: {len(new_roles)} roles",
                              {'guild_id': guild_id, 'added': list(added_roles)})
            
            if removed_roles:
                self.log_change(user_id, 'social', 'Roles Removed',
                              f"{guild_name}: {len(old_roles)} roles",
                              f"{guild_name}: {len(new_roles)} roles",
                              {'guild_id': guild_id, 'removed': list(removed_roles)})
            
            user_data['guild_roles'][guild_id] = list(new_roles)
    
    def handle_typing_start(self, data):
        user_id = data.get('user_id')
        
        if user_id not in self.user_ids:
            return
        
        channel_id = data.get('channel_id')
        user_data = self.user_data[user_id]
        
        if channel_id not in user_data['typing_in']:
            user_data['typing_in'].append(channel_id)
            
            timestamp = datetime.now().strftime('%H:%M:%S')
            print(f"⌨️  [{timestamp}] Started typing in channel {channel_id}")
            
            async def clear_typing():
                await asyncio.sleep(10)
                if channel_id in user_data['typing_in']:
                    user_data['typing_in'].remove(channel_id)
            
            asyncio.create_task(clear_typing())
    
    def handle_relationship_add(self, data):
        user_id = data.get('id')
        
        if user_id not in self.user_ids:
            return
        
        friend = data.get('user', {})
        friend_name = f"{friend.get('username', 'Unknown')}#{friend.get('discriminator', '0000')}"
        
        self.log_change(user_id, 'social', 'Friend Added', None, friend_name, {'friend_id': friend.get('id')})
    
    def handle_relationship_remove(self, data):
        relationship_id = data.get('id')
        timestamp = datetime.now().strftime('%H:%M:%S')
        print(f"👥 [{timestamp}] Relationship removed: {relationship_id}")
    
    def handle_voice_state_update(self, data):
        user_id = data.get('user_id')
        
        if user_id not in self.user_ids:
            return
        
        user_data = self.user_data[user_id]
        channel_id = data.get('channel_id')
        guild_id = data.get('guild_id')
        
        # Voice state flags
        self_mute = data.get('self_mute', False)
        self_deaf = data.get('self_deaf', False)
        self_video = data.get('self_video', False)
        self_stream = data.get('self_stream', False)
        
        # Track mute/deaf changes
        if self_mute != user_data['voice_muted']:
            self.log_change(user_id, 'activity', 'Voice Muted', user_data['voice_muted'], self_mute)
            user_data['voice_muted'] = self_mute
        
        if self_deaf != user_data['voice_deafened']:
            self.log_change(user_id, 'activity', 'Voice Deafened', user_data['voice_deafened'], self_deaf)
            user_data['voice_deafened'] = self_deaf
        
        if self_video != user_data['voice_video']:
            self.log_change(user_id, 'activity', 'Camera', user_data['voice_video'], self_video)
            user_data['voice_video'] = self_video
        
        if self_stream != user_data['voice_screenshare']:
            self.log_change(user_id, 'activity', 'Screenshare', user_data['voice_screenshare'], self_stream)
            user_data['voice_screenshare'] = self_stream
        
        guild_name = self.get_guild_name(guild_id) if guild_id else 'DM'
        channel_name = self.get_channel_name(channel_id) if channel_id else None
        
        # Check for channel changes
        if channel_id != user_data['voice_channel']:
            if channel_id is None:
                # Left voice
                session_key = f"{user_id}:{user_data.get('voice_channel')}"
                if session_key in self.voice_sessions:
                    start_time = self.voice_sessions[session_key]
                    duration = (datetime.now() - start_time).total_seconds()
                    duration_min = duration / 60
                    
                    old_location = f"{user_data.get('voice_guild', 'Unknown')} > {user_data.get('voice_channel', 'Unknown')}"
                    self.log_change(user_id, 'activity', 'Voice', old_location, 'Left voice', {
                        'action': 'left',
                        'duration_seconds': duration,
                        'duration_minutes': round(duration_min, 1)
                    })
                    
                    # Add to voice history
                    self.voice_history[user_id].append({
                        'start': start_time.isoformat(),
                        'end': datetime.now().isoformat(),
                        'duration_minutes': round(duration_min, 1),
                        'location': old_location
                    })
                    
                    del self.voice_sessions[session_key]
                else:
                    old_location = f"{user_data.get('voice_guild', 'Unknown')} > {user_data.get('voice_channel', 'Unknown')}"
                    self.log_change(user_id, 'activity', 'Voice', old_location, 'Left voice', {'action': 'left'})
                    
            elif user_data['voice_channel'] is None:
                # Joined voice
                new_location = f"{guild_name} > {channel_name}"
                self.log_change(user_id, 'activity', 'Voice', None, new_location, {'action': 'joined'})
                
                session_key = f"{user_id}:{channel_name}"
                self.voice_sessions[session_key] = datetime.now()
                
            else:
                # Switched channels
                old_session_key = f"{user_id}:{user_data.get('voice_channel')}"
                if old_session_key in self.voice_sessions:
                    start_time = self.voice_sessions[old_session_key]
                    duration = (datetime.now() - start_time).total_seconds()
                    duration_min = duration / 60
                    
                    # Add to voice history
                    old_location = f"{user_data.get('voice_guild', 'Unknown')} > {user_data.get('voice_channel', 'Unknown')}"
                    self.voice_history[user_id].append({
                        'start': start_time.isoformat(),
                        'end': datetime.now().isoformat(),
                        'duration_minutes': round(duration_min, 1),
                        'location': old_location
                    })
                    
                    del self.voice_sessions[old_session_key]
                else:
                    duration_min = None
                
                old_location = f"{user_data.get('voice_guild', 'Unknown')} > {user_data.get('voice_channel', 'Unknown')}"
                new_location = f"{guild_name} > {channel_name}"
                
                extra_info = {'action': 'moved'}
                if duration_min:
                    extra_info['prev_duration_minutes'] = round(duration_min, 1)
                
                self.log_change(user_id, 'activity', 'Voice', old_location, new_location, extra_info)
                
                # Start tracking new session
                new_session_key = f"{user_id}:{channel_name}"
                self.voice_sessions[new_session_key] = datetime.now()
            
            user_data['voice_channel'] = channel_name if channel_id else None
            user_data['voice_guild'] = guild_name if guild_id else None
    
    def handle_user_update(self, data, session):
        user_id = data.get('id')
        
        if user_id not in self.user_ids:
            return
        
        user_data = self.user_data[user_id]
        
        # Username change
        username = data.get('username')
        if username and username != user_data['username']:
            self.log_change(user_id, 'profile', 'Username', user_data['username'], username)
            user_data['username'] = username
        
        # Display name change
        global_name = data.get('global_name')
        if global_name != user_data['global_name']:
            self.log_change(user_id, 'profile', 'Display Name', user_data['global_name'], global_name)
            user_data['global_name'] = global_name
        
        # Discriminator change
        discriminator = data.get('discriminator')
        if discriminator and discriminator != user_data['discriminator']:
            self.log_change(user_id, 'profile', 'Discriminator', user_data['discriminator'], discriminator)
            user_data['discriminator'] = discriminator
        
        # Avatar change
        avatar = data.get('avatar')
        if avatar and avatar != user_data['avatar']:
            self.log_change(user_id, 'profile', 'Avatar', user_data['avatar'], avatar)
            user_data['avatar'] = avatar
            
            avatar_url = f"https://cdn.discordapp.com/avatars/{user_id}/{avatar}.png?size=1024"
            timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
            filename = f"avatar_{timestamp}.png"
            
            folder = self.get_output_folder(user_id, "profile/avatars")
            os.makedirs(folder, exist_ok=True)
            
            asyncio.create_task(self.download_asset(session, avatar_url, folder, filename))
        
        # Banner change
        banner = data.get('banner')
        if banner and banner != user_data['banner']:
            self.log_change(user_id, 'profile', 'Banner', user_data['banner'], banner)
            user_data['banner'] = banner
            
            banner_url = f"https://cdn.discordapp.com/banners/{user_id}/{banner}.png?size=1024"
            timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
            filename = f"banner_{timestamp}.png"
            
            folder = self.get_output_folder(user_id, "profile/banners")
            os.makedirs(folder, exist_ok=True)
            
            asyncio.create_task(self.download_asset(session, banner_url, folder, filename))
    
    async def fetch_initial_data(self, session):
        print("Fetching initial profile data...")
        
        for user_id in self.user_ids:
            profile = await self.fetch_user_profile(user_id)
            
            if profile:
                user_info = profile.get('user', {})
                user_data = self.user_data[user_id]
                
                # Set initial values
                user_data['username'] = user_info.get('username')
                user_data['global_name'] = user_info.get('global_name')
                user_data['discriminator'] = user_info.get('discriminator')
                user_data['avatar'] = user_info.get('avatar')
                user_data['banner'] = user_info.get('banner')
                user_data['bio'] = user_info.get('bio', '')
                user_data['badges'] = user_info.get('public_flags', 0)
                user_data['accent_color'] = user_info.get('accent_color')
                user_data['avatar_decoration'] = user_info.get('avatar_decoration_data')
                user_data['pronouns'] = user_info.get('pronouns', '')
                user_data['clan_tag'] = user_info.get('clan', {}).get('tag') if user_info.get('clan') else None
                
                connected_accounts = profile.get('connected_accounts', [])
                user_data['connections'] = [
                    {'type': conn.get('type'), 'name': conn.get('name'), 'visible': conn.get('visibility', 0) == 1}
                    for conn in connected_accounts
                ]
                
                premium_type = user_info.get('premium_type', 0)
                user_data['nitro'] = ['None', 'Nitro Classic', 'Nitro', 'Nitro Basic'][premium_type] if premium_type < 4 else 'Unknown'
                
                mutual_guilds_data = profile.get('mutual_guilds', [])
                user_data['mutual_guilds'] = [g.get('id') for g in mutual_guilds_data]
                
                for guild in mutual_guilds_data:
                    guild_id = guild.get('id')
                    guild_name = guild.get('name')
                    if guild_id and guild_name:
                        self.guild_cache[guild_id] = guild_name
                
                # Download initial avatar
                if user_data['avatar']:
                    avatar_url = f"https://cdn.discordapp.com/avatars/{user_id}/{user_data['avatar']}.png?size=1024"
                    timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
                    filename = f"avatar_initial_{timestamp}.png"
                    
                    folder = self.get_output_folder(user_id, "profile/avatars")
                    os.makedirs(folder, exist_ok=True)
                    
                    await self.download_asset(session, avatar_url, folder, filename)
                
                # Download initial banner
                if user_data['banner']:
                    banner_url = f"https://cdn.discordapp.com/banners/{user_id}/{user_data['banner']}.png?size=1024"
                    timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
                    filename = f"banner_initial_{timestamp}.png"
                    
                    folder = self.get_output_folder(user_id, "profile/banners")
                    os.makedirs(folder, exist_ok=True)
                    
                    await self.download_asset(session, banner_url, folder, filename)
                
                print(f"  ✓ Loaded: {user_data['global_name'] or user_data['username']} ({user_id})")
                print(f"    Mutual servers: {len(user_data['mutual_guilds'])}")
        
        print("Initial data loaded\n")
    
    async def periodic_profile_check(self, session):
        while self.active:
            await asyncio.sleep(300)  # Check every 5 minutes
            
            for user_id in self.user_ids:
                profile = await self.fetch_user_profile(user_id)
                
                if not profile:
                    continue
                
                user_data = self.user_data[user_id]
                user_info = profile.get('user', {})
                
                # Bio change
                bio = user_info.get('bio', '')
                if bio != user_data['bio']:
                    self.log_change(user_id, 'profile', 'Bio', user_data['bio'], bio)
                    user_data['bio'] = bio
                
                # Pronouns
                pronouns = user_info.get('pronouns', '')
                if pronouns != user_data['pronouns']:
                    self.log_change(user_id, 'profile', 'Pronouns', user_data['pronouns'], pronouns)
                    user_data['pronouns'] = pronouns
                
                # Clan tag
                clan_tag = user_info.get('clan', {}).get('tag') if user_info.get('clan') else None
                if clan_tag != user_data['clan_tag']:
                    self.log_change(user_id, 'profile', 'Clan Tag', user_data['clan_tag'], clan_tag)
                    user_data['clan_tag'] = clan_tag
                
                # Connections
                connected_accounts = profile.get('connected_accounts', [])
                new_connections = [
                    {'type': conn.get('type'), 'name': conn.get('name')}
                    for conn in connected_accounts if conn.get('visibility', 0) == 1
                ]
                
                old_conn_str = ', '.join([f"{c['type']}:{c['name']}" for c in user_data['connections']])
                new_conn_str = ', '.join([f"{c['type']}:{c['name']}" for c in new_connections])
                
                if old_conn_str != new_conn_str:
                    self.log_change(user_id, 'profile', 'Connections', old_conn_str or 'None', new_conn_str or 'None')
                    user_data['connections'] = new_connections
                
                # Avatar decoration
                avatar_decoration = user_info.get('avatar_decoration_data')
                if avatar_decoration != user_data['avatar_decoration']:
                    self.log_change(user_id, 'profile', 'Avatar Decoration', user_data['avatar_decoration'], avatar_decoration)
                    user_data['avatar_decoration'] = avatar_decoration
                
                # Badges
                flags = user_info.get('public_flags', 0)
                if flags != user_data['badges']:
                    self.log_change(user_id, 'profile', 'Badges', user_data['badges'], flags)
                    user_data['badges'] = flags
                
                # Accent color
                accent_color = user_info.get('accent_color')
                if accent_color != user_data['accent_color']:
                    self.log_change(user_id, 'profile', 'Accent Color', user_data['accent_color'], accent_color)
                    user_data['accent_color'] = accent_color
                
                # Banner color
                banner_color = user_info.get('banner_color')
                if banner_color != user_data['banner_color']:
                    self.log_change(user_id, 'profile', 'Banner Color', user_data['banner_color'], banner_color)
                    user_data['banner_color'] = banner_color
                
                # Nitro status
                premium_type = user_info.get('premium_type', 0)
                nitro = ['None', 'Nitro Classic', 'Nitro', 'Nitro Basic'][premium_type] if premium_type < 4 else 'Unknown'
                if nitro != user_data['nitro']:
                    self.log_change(user_id, 'profile', 'Nitro', user_data['nitro'], nitro)
                    user_data['nitro'] = nitro
                
                # Mutual servers
                mutual_guilds_data = profile.get('mutual_guilds', [])
                new_mutual_guilds = [g.get('id') for g in mutual_guilds_data]
                
                old_guilds = set(user_data['mutual_guilds'])
                new_guilds = set(new_mutual_guilds)
                
                joined = new_guilds - old_guilds
                left = old_guilds - new_guilds
                
                for guild_id in joined:
                    guild_name = self.get_guild_name(guild_id)
                    self.log_change(user_id, 'social', 'Mutual Server Joined', None, guild_name, {'guild_id': guild_id})
                
                for guild_id in left:
                    guild_name = self.get_guild_name(guild_id)
                    self.log_change(user_id, 'social', 'Mutual Server Left', guild_name, None, {'guild_id': guild_id})
                
                user_data['mutual_guilds'] = new_mutual_guilds
    
    async def stalk(self):
        """Main stalking loop with auto-reconnect"""
        gateway_url = "wss://gateway.discord.gg/?v=9&encoding=json"
        
        if self.start_time is None:
            self.start_time = datetime.now()
        
        while self.active:
            try:
                await self._connect_and_run(gateway_url)
            except (websockets.exceptions.ConnectionClosed, 
                    websockets.exceptions.ConnectionClosedOK,
                    websockets.exceptions.ConnectionClosedError) as e:
                if self.active:
                    self.total_reconnects += 1
                    elapsed = (datetime.now() - self.start_time).total_seconds()
                    print(f"\n⚠️  Connection lost after {elapsed:.0f}s: {e.reason if hasattr(e, 'reason') else str(e)}")
                    print(f"🔄 Reconnecting... (attempt #{self.total_reconnects})")
                    await asyncio.sleep(3)
                else:
                    break
            except KeyboardInterrupt:
                print("\n\n⏹️  Stopping stalker...")
                self.active = False
                break
            except Exception as e:
                if self.active:
                    self.total_reconnects += 1
                    print(f"\n⚠️  Unexpected error: {e}")
                    print(f"🔄 Reconnecting... (attempt #{self.total_reconnects})")
                    await asyncio.sleep(3)
                else:
                    break
    
    async def _connect_and_run(self, gateway_url):
        """Internal method to connect and run gateway loop"""
        async with aiohttp.ClientSession() as session:
            async with websockets.connect(gateway_url, max_size=16 * 1024 * 1024) as ws:
                # Receive Hello
                hello = json.loads(await ws.recv())
                heartbeat_interval = hello['d']['heartbeat_interval']
                
                # Send Identify
                identify = {
                    "op": 2,
                    "d": {
                        "token": DISCORD_TOKEN,
                        "properties": {
                            "$os": "windows",
                            "$browser": "chrome",
                            "$device": "pc"
                        },
                        "compress": False
                    }
                }
                await ws.send(json.dumps(identify))
                
                # Heartbeat task
                async def heartbeat():
                    while self.active:
                        try:
                            await asyncio.sleep(heartbeat_interval / 1000)
                            if self.active:
                                await ws.send(json.dumps({"op": 1, "d": None}))
                        except Exception:
                            break
                
                heartbeat_task = asyncio.create_task(heartbeat())
                
                # Periodic profile check
                profile_check_task = asyncio.create_task(self.periodic_profile_check(session))
                
                try:
                    # Wait for READY
                    ready = False
                    while not ready:
                        msg = json.loads(await ws.recv())
                        if msg.get('t') == 'READY':
                            ready = True
                            if self.total_reconnects == 0:
                                print("✓ Connected to Gateway")
                            else:
                                elapsed = (datetime.now() - self.start_time).total_seconds()
                                print(f"✓ Reconnected successfully (total uptime: {elapsed:.0f}s)")
                    
                    # Fetch initial data (only on first connect)
                    if self.total_reconnects == 0:
                        await self.fetch_initial_data(session)
                        print(f"👁️  Stalking {len(self.user_ids)} user(s)")
                        print("=" * 60)
                    
                    # Monitor events
                    while self.active:
                        msg = await ws.recv()
                        data = json.loads(msg)
                        
                        event_type = data.get('t')
                        
                        if event_type == 'PRESENCE_UPDATE':
                            self.handle_presence_update(data['d'], session)
                        
                        elif event_type == 'VOICE_STATE_UPDATE':
                            self.handle_voice_state_update(data['d'])
                        
                        elif event_type == 'USER_UPDATE':
                            self.handle_user_update(data['d'], session)
                        
                        elif event_type == 'TYPING_START':
                            self.handle_typing_start(data['d'])
                        
                        elif event_type == 'RELATIONSHIP_ADD':
                            self.handle_relationship_add(data['d'])
                        
                        elif event_type == 'RELATIONSHIP_REMOVE':
                            self.handle_relationship_remove(data['d'])
                        
                        elif event_type == 'CALL_CREATE':
                            self.handle_call_create(data['d'])
                        
                        elif event_type == 'CALL_UPDATE':
                            self.handle_call_update(data['d'])
                        
                        elif event_type == 'CALL_DELETE':
                            self.handle_call_delete(data['d'])
                        
                        elif event_type == 'GUILD_MEMBER_UPDATE':
                            self.handle_guild_member_update(data['d'])
                        
                        elif event_type == 'MESSAGE_CREATE':
                            message_data = data['d']
                            author_id = message_data.get('author', {}).get('id')
                            
                            if author_id in self.user_ids:
                                channel_id = message_data.get('channel_id')
                                guild_id = message_data.get('guild_id')
                                
                                self.message_counts[author_id][channel_id] += 1
                                total = self.message_counts[author_id][channel_id]
                                
                                timestamp = datetime.now().strftime('%H:%M:%S')
                                if guild_id:
                                    print(f"💬 [{timestamp}] Message sent in server channel {channel_id} (#{total} total)")
                                else:
                                    print(f"💬 [{timestamp}] Message sent in DM {channel_id} (#{total} total)")
                
                finally:
                    heartbeat_task.cancel()
                    profile_check_task.cancel()
    
    def save_logs(self):
        """Save all collected data in organized structure"""
        if not any(self.change_log.values()):
            print("No changes recorded")
            return
        
        # Log any active voice sessions
        for session_key, start_time in self.voice_sessions.items():
            user_id = session_key.split(':')[0]
            duration = (datetime.now() - start_time).total_seconds()
            duration_min = duration / 60
            print(f"\nℹ️  User {user_id} was still in voice when tracking stopped ({duration_min:.1f} min)")
        
        timestamp_str = datetime.now().strftime('%Y%m%d_%H%M%S')
        
        for user_id in self.user_ids:
            base_folder = self.get_output_folder(user_id)
            
            # Create folder structure
            folders = {
                'profile': self.get_output_folder(user_id, 'profile'),
                'activity': self.get_output_folder(user_id, 'activity'),
                'social': self.get_output_folder(user_id, 'social'),
                'logs': self.get_output_folder(user_id, 'logs')
            }
            
            for folder in folders.values():
                os.makedirs(folder, exist_ok=True)
            
            # Save categorized change logs
            for category in ['profile', 'activity', 'social']:
                user_changes = [c for c in self.change_log[category] if c['user_id'] == user_id]
                if user_changes:
                    log_file = os.path.join(folders[category], f"changes_{timestamp_str}.json")
                    with open(log_file, 'w', encoding='utf-8') as f:
                        json.dump(user_changes, f, indent=2, ensure_ascii=False)
            
            # Save message activity
            if user_id in self.message_counts and self.message_counts[user_id]:
                activity_file = os.path.join(folders['activity'], f"messages_{timestamp_str}.txt")
                with open(activity_file, 'w', encoding='utf-8') as f:
                    f.write(f"Message Activity for {user_id}\n")
                    f.write("=" * 60 + "\n\n")
                    
                    for channel_id, count in sorted(self.message_counts[user_id].items(), key=lambda x: x[1], reverse=True):
                        channel_name = self.get_channel_name(channel_id)
                        f.write(f"{channel_name} ({channel_id}): {count} messages\n")
            
            # Save Spotify history
            if user_id in self.spotify_history and self.spotify_history[user_id]:
                spotify_file = os.path.join(folders['activity'], f"spotify_{timestamp_str}.txt")
                with open(spotify_file, 'w', encoding='utf-8') as f:
                    f.write(f"Spotify Listening History\n")
                    f.write("=" * 60 + "\n\n")
                    
                    for entry in self.spotify_history[user_id]:
                        time = entry['timestamp']
                        f.write(f"[{time}]\n")
                        f.write(f"  🎵 {entry['song']}\n")
                        f.write(f"  👤 {entry['artist']}\n")
                        f.write(f"  💿 {entry['album']}\n\n")
            
            # Save voice history
            if user_id in self.voice_history and self.voice_history[user_id]:
                voice_file = os.path.join(folders['activity'], f"voice_{timestamp_str}.txt")
                with open(voice_file, 'w', encoding='utf-8') as f:
                    f.write(f"Voice Session History\n")
                    f.write("=" * 60 + "\n\n")
                    
                    total_minutes = 0
                    for session in self.voice_history[user_id]:
                        f.write(f"📍 {session['location']}\n")
                        f.write(f"   Started: {session['start']}\n")
                        f.write(f"   Ended: {session['end']}\n")
                        f.write(f"   Duration: {session['duration_minutes']} minutes\n\n")
                        total_minutes += session['duration_minutes']
                    
                    f.write(f"\n{'=' * 60}\n")
                    f.write(f"Total voice time: {total_minutes:.1f} minutes ({total_minutes/60:.1f} hours)\n")
            
            # Save complete change log
            all_changes = []
            for category in ['profile', 'activity', 'social']:
                all_changes.extend([c for c in self.change_log[category] if c['user_id'] == user_id])
            
            all_changes.sort(key=lambda x: x['timestamp'])
            
            complete_log_file = os.path.join(folders['logs'], f"all_changes_{timestamp_str}.json")
            with open(complete_log_file, 'w', encoding='utf-8') as f:
                json.dump(all_changes, f, indent=2, ensure_ascii=False)
            
            # Save current state snapshot
            state_file = os.path.join(base_folder, "current_state.json")
            with open(state_file, 'w', encoding='utf-8') as f:
                json.dump(self.user_data[user_id], f, indent=2, ensure_ascii=False)
            
            # Generate summary
            summary_file = os.path.join(folders['logs'], f"summary_{timestamp_str}.txt")
            with open(summary_file, 'w', encoding='utf-8') as f:
                f.write(f"Profile Stalker Summary\n")
                f.write("=" * 60 + "\n\n")
                f.write(f"User ID: {user_id}\n")
                f.write(f"Username: {self.user_data[user_id]['username']}\n")
                f.write(f"Display Name: {self.user_data[user_id]['global_name']}\n")
                f.write(f"Session Duration: {(datetime.now() - self.start_time).total_seconds() / 3600:.1f} hours\n")
                f.write(f"Reconnections: {self.total_reconnects}\n")
                f.write(f"Total Changes: {len(all_changes)}\n\n")
                
                # Count changes by category and type
                f.write("Changes by Category:\n")
                for category in ['profile', 'activity', 'social']:
                    category_changes = [c for c in self.change_log[category] if c['user_id'] == user_id]
                    f.write(f"  {category.capitalize()}: {len(category_changes)}\n")
                
                f.write("\nChanges by Type:\n")
                change_types = defaultdict(int)
                for change in all_changes:
                    change_types[change['type']] += 1
                
                for change_type, count in sorted(change_types.items()):
                    f.write(f"  {change_type}: {count}\n")
                
                # Activity stats
                f.write("\n" + "=" * 60 + "\n")
                f.write("Activity Statistics:\n\n")
                
                if user_id in self.message_counts:
                    total_messages = sum(self.message_counts[user_id].values())
                    f.write(f"💬 Messages Sent: {total_messages}\n")
                    f.write(f"   Channels: {len(self.message_counts[user_id])}\n")
                
                if user_id in self.spotify_history:
                    f.write(f"🎵 Songs Listened: {len(self.spotify_history[user_id])}\n")
                
                if user_id in self.voice_history:
                    total_voice_min = sum(s['duration_minutes'] for s in self.voice_history[user_id])
                    f.write(f"🎤 Voice Sessions: {len(self.voice_history[user_id])}\n")
                    f.write(f"   Total Time: {total_voice_min:.1f} min ({total_voice_min/60:.1f} hrs)\n")
                
                # Current state
                f.write("\n" + "=" * 60 + "\n")
                f.write("Current State:\n\n")
                f.write(f"Status: {self.user_data[user_id]['status']}\n")
                f.write(f"Custom Status: {self.user_data[user_id]['custom_status']}\n")
                f.write(f"Nitro: {self.user_data[user_id]['nitro']}\n")
                f.write(f"Bio: {self.user_data[user_id]['bio'][:100]}...\n" if self.user_data[user_id]['bio'] else "Bio: None\n")
                f.write(f"Mutual Servers: {len(self.user_data[user_id]['mutual_guilds'])}\n")
                
                if self.user_data[user_id]['voice_channel']:
                    f.write(f"Currently in Voice: {self.user_data[user_id]['voice_guild']} > {self.user_data[user_id]['voice_channel']}\n")
            
            print(f"\n📁 Logs saved to: {base_folder}/")
            print(f"   └── profile/")
            print(f"       ├── avatars/")
            print(f"       ├── banners/")
            print(f"       └── changes_{timestamp_str}.json")
            print(f"   └── activity/")
            print(f"       ├── changes_{timestamp_str}.json")
            print(f"       ├── messages_{timestamp_str}.txt")
            print(f"       ├── spotify_{timestamp_str}.txt")
            print(f"       └── voice_{timestamp_str}.txt")
            print(f"   └── social/")
            print(f"       └── changes_{timestamp_str}.json")
            print(f"   └── logs/")
            print(f"       ├── all_changes_{timestamp_str}.json")
            print(f"       └── summary_{timestamp_str}.txt")
            print(f"   └── current_state.json")

def main():
    print("Profile Stalker")
    print("=" * 60)
    
    user_input = input("User ID(s) to stalk (comma-separated): ").strip()
    user_ids = [u.strip() for u in user_input.split(',') if u.strip()]
    
    if not all(u.isdigit() for u in user_ids):
        print("Invalid user ID(s)")
        return
    
    stalker = ProfileStalker(user_ids)
    
    print(f"\nStarting stalker for {len(user_ids)} user(s)...")
    print("Press Ctrl+C to stop and save logs\n")
    
    try:
        asyncio.run(stalker.stalk())
    except KeyboardInterrupt:
        print("\n\n⏹️  Stopping...")
    finally:
        stalker.save_logs()

if __name__ == "__main__":
    main()
