# discord_tools/scripts/voice_activity_tracker.py
import os
import sys
import asyncio
import websockets
import json
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 VoiceActivityTracker:
    def __init__(self, guild_ids=None, user_ids=None, exclude_guilds=None):
        # Support multiple guilds or specific users
        self.guild_ids = guild_ids if isinstance(guild_ids, list) else ([guild_ids] if guild_ids else [])
        self.user_ids = user_ids if isinstance(user_ids, list) else ([user_ids] if user_ids else [])
        self.exclude_guilds = exclude_guilds if isinstance(exclude_guilds, list) else ([exclude_guilds] if exclude_guilds else [])
        
        self.voice_states = {}  # user_id -> {channel_id, joined_at, user_info, guild_id}
        self.session_log = []  # List of all voice sessions
        self.active = True
        self.guild_names = {}  # Cache guild names
        self.channel_names = {}  # Cache channel names
        
    def get_guild_info(self, guild_id):
        # Get guild name and cache it
        if guild_id in self.guild_names:
            return self.guild_names[guild_id]
        
        response = make_discord_request('GET', f'/guilds/{guild_id}')
        if response:
            guild_data = response.json()
            self.guild_names[guild_id] = guild_data['name']
            return guild_data['name']
        
        self.guild_names[guild_id] = f'Guild-{guild_id}'
        return f'Guild-{guild_id}'
    
    def get_voice_channels(self, guild_id):
        # Get all voice channels in a guild
        response = make_discord_request('GET', f'/guilds/{guild_id}/channels')
        if not response:
            return {}
        
        channels = response.json()
        voice_channels = {}
        
        for channel in channels:
            # Type 2 = voice channel, Type 13 = stage channel
            if channel['type'] in [2, 13]:
                voice_channels[channel['id']] = channel['name']
                self.channel_names[channel['id']] = channel['name']
        
        return voice_channels
    
    def should_track_event(self, guild_id, user_id):
        # Check if we should track this event based on filters
        
        # Check excluded guilds
        if guild_id in self.exclude_guilds:
            return False
        
        # If specific guilds set, only track those
        if self.guild_ids and guild_id not in self.guild_ids:
            return False
        
        # If specific users set, only track those
        if self.user_ids and user_id not in self.user_ids:
            return False
        
        return True
    
    def handle_voice_state_update(self, data):
        # Process voice state changes
        user_id = data['user_id']
        channel_id = data.get('channel_id')  # None if user left voice
        guild_id = data.get('guild_id')
        
        # Check if we should track this event
        if not self.should_track_event(guild_id, user_id):
            return
        
        timestamp = datetime.now()
        
        # User info
        member = data.get('member', {})
        user = member.get('user', data.get('user', {}))
        username = f"{user.get('username', 'Unknown')}#{user.get('discriminator', '0000')}"
        
        # Get guild and channel names
        guild_name = self.get_guild_info(guild_id)
        
        # Build unique key for user+guild combo
        state_key = f"{user_id}:{guild_id}"
        
        # Check if user was already in a voice channel in this guild
        if state_key in self.voice_states:
            old_state = self.voice_states[state_key]
            
            # User left voice completely
            if channel_id is None:
                duration = (timestamp - old_state['joined_at']).total_seconds()
                
                session = {
                    'user_id': user_id,
                    'username': username,
                    'guild_id': guild_id,
                    'guild_name': guild_name,
                    'channel_id': old_state['channel_id'],
                    'channel_name': old_state.get('channel_name', 'Unknown'),
                    'joined_at': old_state['joined_at'].isoformat(),
                    'left_at': timestamp.isoformat(),
                    'duration_seconds': duration
                }
                
                self.session_log.append(session)
                del self.voice_states[state_key]
                
                print(f"[-] {username} left {old_state.get('channel_name', 'Unknown')} in {guild_name} (stayed {duration:.0f}s)")
            
            # User switched channels
            elif channel_id != old_state['channel_id']:
                duration = (timestamp - old_state['joined_at']).total_seconds()
                
                session = {
                    'user_id': user_id,
                    'username': username,
                    'guild_id': guild_id,
                    'guild_name': guild_name,
                    'channel_id': old_state['channel_id'],
                    'channel_name': old_state.get('channel_name', 'Unknown'),
                    'joined_at': old_state['joined_at'].isoformat(),
                    'left_at': timestamp.isoformat(),
                    'duration_seconds': duration
                }
                
                self.session_log.append(session)
                
                # Get new channel name
                if channel_id not in self.channel_names:
                    self.get_voice_channels(guild_id)
                
                new_channel_name = self.channel_names.get(channel_id, f'Channel-{channel_id}')
                
                self.voice_states[state_key] = {
                    'channel_id': channel_id,
                    'channel_name': new_channel_name,
                    'guild_id': guild_id,
                    'guild_name': guild_name,
                    'joined_at': timestamp,
                    'username': username
                }
                
                print(f"[→] {username} moved from {old_state.get('channel_name', 'Unknown')} to {new_channel_name} in {guild_name}")
        
        # User joined voice (wasn't in voice before)
        elif channel_id is not None:
            if channel_id not in self.channel_names:
                self.get_voice_channels(guild_id)
            
            channel_name = self.channel_names.get(channel_id, f'Channel-{channel_id}')
            
            self.voice_states[state_key] = {
                'channel_id': channel_id,
                'channel_name': channel_name,
                'guild_id': guild_id,
                'guild_name': guild_name,
                'joined_at': timestamp,
                'username': username
            }
            
            print(f"[+] {username} joined {channel_name} in {guild_name}")
    
    async def track_voice_activity(self):
        # Connect to Discord gateway and track voice state changes
        gateway_url = "wss://gateway.discord.gg/?v=9&encoding=json"
        
        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:
                    await asyncio.sleep(heartbeat_interval / 1000)
                    if self.active:
                        await ws.send(json.dumps({"op": 1, "d": None}))
            
            heartbeat_task = asyncio.create_task(heartbeat())
            
            # Wait for READY
            ready = False
            while not ready:
                msg = json.loads(await ws.recv())
                if msg.get('t') == 'READY':
                    ready = True
                    print("Connected to Discord Gateway")
                    print("Tracking voice activity...")
                    print("=" * 60)
            
            # Track voice state updates
            try:
                while self.active:
                    msg = await ws.recv()
                    data = json.loads(msg)
                    
                    event_type = data.get('t')
                    
                    if event_type == 'VOICE_STATE_UPDATE':
                        self.handle_voice_state_update(data['d'])
                    
                    # Also handle initial guild members in voice
                    elif event_type == 'GUILD_CREATE':
                        guild_id = data['d']['id']
                        # Only process if we should track this guild
                        if guild_id in self.exclude_guilds:
                            continue
                        if self.guild_ids and guild_id not in self.guild_ids:
                            continue
                        
                        # Process initial voice states
                        for voice_state in data['d'].get('voice_states', []):
                            voice_state['guild_id'] = guild_id
                            self.handle_voice_state_update(voice_state)
            
            except KeyboardInterrupt:
                print("\nStopping tracker...")
                self.active = False
            finally:
                heartbeat_task.cancel()
    
    def save_logs(self):
        # Save session logs to file
        if not self.session_log and not self.voice_states:
            print("No voice activity recorded")
            return
        
        # Close any active sessions
        timestamp = datetime.now()
        for state_key, state in self.voice_states.items():
            duration = (timestamp - state['joined_at']).total_seconds()
            
            session = {
                'user_id': state_key.split(':')[0],
                'username': state['username'],
                'guild_id': state['guild_id'],
                'guild_name': state.get('guild_name', 'Unknown'),
                'channel_id': state['channel_id'],
                'channel_name': state.get('channel_name', 'Unknown'),
                'joined_at': state['joined_at'].isoformat(),
                'left_at': timestamp.isoformat(),
                'duration_seconds': duration,
                'ongoing': True
            }
            
            self.session_log.append(session)
        
        # Create output folder
        if self.guild_ids and len(self.guild_ids) == 1:
            folder_name = f"voice_activity_{self.guild_ids[0]}"
        elif self.user_ids:
            folder_name = f"voice_activity_user_{'_'.join(self.user_ids[:3])}"
        else:
            folder_name = "voice_activity_all"
        
        output_folder = os.path.join(DATA_DIR, folder_name)
        os.makedirs(output_folder, exist_ok=True)
        
        timestamp_str = datetime.now().strftime('%Y%m%d_%H%M%S')
        
        # Save raw JSON
        json_file = os.path.join(output_folder, f"sessions_{timestamp_str}.json")
        with open(json_file, 'w', encoding='utf-8') as f:
            json.dump(self.session_log, f, indent=2, ensure_ascii=False)
        
        # Generate summary stats
        stats_file = os.path.join(output_folder, f"stats_{timestamp_str}.txt")
        
        # Calculate stats
        user_stats = defaultdict(lambda: {'total_time': 0, 'sessions': 0, 'channels': set(), 'guilds': set()})
        channel_stats = defaultdict(lambda: {'total_time': 0, 'unique_users': set()})
        guild_stats = defaultdict(lambda: {'total_time': 0, 'unique_users': set()})
        
        for session in self.session_log:
            user_id = session['user_id']
            username = session['username']
            channel_id = session['channel_id']
            channel_name = session['channel_name']
            guild_name = session.get('guild_name', 'Unknown')
            duration = session['duration_seconds']
            
            user_stats[username]['total_time'] += duration
            user_stats[username]['sessions'] += 1
            user_stats[username]['channels'].add(channel_name)
            user_stats[username]['guilds'].add(guild_name)
            
            channel_stats[f"{guild_name} > {channel_name}"]['total_time'] += duration
            channel_stats[f"{guild_name} > {channel_name}"]['unique_users'].add(username)
            
            guild_stats[guild_name]['total_time'] += duration
            guild_stats[guild_name]['unique_users'].add(username)
        
        with open(stats_file, 'w', encoding='utf-8') as f:
            f.write("Voice Activity Statistics\n")
            f.write("=" * 60 + "\n\n")
            f.write(f"Total Sessions: {len(self.session_log)}\n")
            f.write(f"Unique Users: {len(user_stats)}\n")
            f.write(f"Servers Tracked: {len(guild_stats)}\n\n")
            
            if len(guild_stats) > 1:
                f.write("\nServer Activity:\n")
                f.write("-" * 60 + "\n")
                sorted_guilds = sorted(guild_stats.items(), key=lambda x: x[1]['total_time'], reverse=True)
                for guild_name, stats in sorted_guilds:
                    hours = stats['total_time'] / 3600
                    f.write(f"{guild_name}:\n")
                    f.write(f"  Total Time: {hours:.2f} hours\n")
                    f.write(f"  Unique Users: {len(stats['unique_users'])}\n\n")
            
            f.write("\nMost Active Users (by time):\n")
            f.write("-" * 60 + "\n")
            sorted_users = sorted(user_stats.items(), key=lambda x: x[1]['total_time'], reverse=True)
            for username, stats in sorted_users[:10]:
                hours = stats['total_time'] / 3600
                f.write(f"{username}:\n")
                f.write(f"  Total Time: {hours:.2f} hours\n")
                f.write(f"  Sessions: {stats['sessions']}\n")
                if len(stats['guilds']) > 1:
                    f.write(f"  Servers: {', '.join(stats['guilds'])}\n")
                f.write(f"  Channels: {', '.join(stats['channels'])}\n\n")
            
            f.write("\nChannel Activity:\n")
            f.write("-" * 60 + "\n")
            sorted_channels = sorted(channel_stats.items(), key=lambda x: x[1]['total_time'], reverse=True)
            for channel_name, stats in sorted_channels:
                hours = stats['total_time'] / 3600
                f.write(f"{channel_name}:\n")
                f.write(f"  Total Time: {hours:.2f} hours\n")
                f.write(f"  Unique Users: {len(stats['unique_users'])}\n\n")
        
        # Save detailed log
        log_file = os.path.join(output_folder, f"detailed_log_{timestamp_str}.txt")
        with open(log_file, 'w', encoding='utf-8') as f:
            f.write("Detailed Voice Activity Log\n")
            f.write("=" * 80 + "\n\n")
            
            for session in sorted(self.session_log, key=lambda x: x['joined_at']):
                ongoing = session.get('ongoing', False)
                duration_min = session['duration_seconds'] / 60
                
                f.write(f"User: {session['username']}\n")
                f.write(f"Server: {session.get('guild_name', 'Unknown')}\n")
                f.write(f"Channel: {session['channel_name']}\n")
                f.write(f"Joined: {session['joined_at']}\n")
                f.write(f"Left: {session['left_at']}")
                if ongoing:
                    f.write(" (ongoing when tracking stopped)")
                f.write("\n")
                f.write(f"Duration: {duration_min:.1f} minutes\n")
                f.write("-" * 80 + "\n\n")
        
        print("\n" + "=" * 60)
        print(f"Logs saved to: {output_folder}")
        print(f"  - sessions_{timestamp_str}.json (raw data)")
        print(f"  - stats_{timestamp_str}.txt (summary)")
        print(f"  - detailed_log_{timestamp_str}.txt (full log)")

def main():
    print("Voice Activity Tracker")
    print("=" * 60)
    print("Track voice activity with filters\n")
    
    print("1. Track specific server(s)")
    print("2. Track specific user(s)")
    print("3. Track all servers")
    print("4. Track all servers except some")
    print("=" * 60)
    
    choice = input("Choice: ").strip()
    
    guild_ids = None
    user_ids = None
    exclude_guilds = None
    
    if choice == '1':
        # Track specific guilds
        guild_input = input("\nEnter guild ID(s) (comma-separated for multiple): ").strip()
        guild_ids = [g.strip() for g in guild_input.split(',') if g.strip()]
        
        if not all(g.isdigit() for g in guild_ids):
            print("Invalid guild ID(s)")
            return
    
    elif choice == '2':
        # Track specific users
        user_input = input("\nEnter user ID(s) (comma-separated for multiple): ").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
    
    elif choice == '3':
        # Track all servers
        print("\nTracking all servers...")
    
    elif choice == '4':
        # Exclude specific servers
        exclude_input = input("\nEnter guild ID(s) to EXCLUDE (comma-separated): ").strip()
        exclude_guilds = [g.strip() for g in exclude_input.split(',') if g.strip()]
        
        if not all(g.isdigit() for g in exclude_guilds):
            print("Invalid guild ID(s)")
            return
    
    else:
        print("Invalid choice")
        return
    
    tracker = VoiceActivityTracker(
        guild_ids=guild_ids,
        user_ids=user_ids,
        exclude_guilds=exclude_guilds
    )
    
    # Show what we're tracking
    print("\n" + "=" * 60)
    if guild_ids:
        print(f"Tracking {len(guild_ids)} specific server(s)")
        for gid in guild_ids:
            name = tracker.get_guild_info(gid)
            print(f"  - {name}")
    elif user_ids:
        print(f"Tracking {len(user_ids)} specific user(s) across all servers")
    elif exclude_guilds:
        print(f"Tracking all servers except {len(exclude_guilds)}")
    else:
        print("Tracking all servers")
    
    print("\nPress Ctrl+C to stop tracking and save logs\n")
    print("=" * 60)
    
    try:
        asyncio.run(tracker.track_voice_activity())
    except KeyboardInterrupt:
        print("\n\nStopping tracker...")
    finally:
        tracker.save_logs()

if __name__ == "__main__":
    main()
