# discord_tools/scripts/voice_call_keeper.py
import os
import sys
import asyncio
import websockets
import json
from datetime import datetime

# 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
from discord_tools.utils.api_utils import make_discord_request

class VoiceCallKeeper:
    def __init__(self):
        self.active = True
        self.current_channel_id = None
        self.current_guild_id = None
        self.session_id = None
        self.token = None
        self.endpoint = None
        self.my_user_id = None
        self.start_time = None
        self.total_reconnects = 0
        
    async def find_current_voice_channel(self, ws):
        # Wait for READY and get current voice state
        ready = False
        current_voice = None
        
        while not ready:
            msg = json.loads(await ws.recv())
            
            if msg.get('t') == 'READY':
                ready_data = msg['d']
                self.my_user_id = ready_data['user']['id']
                
                # Check private calls
                private_calls = ready_data.get('private_calls', [])
                for call in private_calls:
                    # Check if we're in this call
                    voice_states = call.get('voice_states', [])
                    for vs in voice_states:
                        if vs.get('user_id') == self.my_user_id:
                            current_voice = {
                                'channel_id': call['channel_id'],
                                'guild_id': None,
                                'type': 'DM Call'
                            }
                            break
                    if current_voice:
                        break
                
                # Check guild voice states
                if not current_voice:
                    guilds = ready_data.get('guilds', [])
                    for guild in guilds:
                        voice_states = guild.get('voice_states', [])
                        for vs in voice_states:
                            if vs.get('user_id') == self.my_user_id and vs.get('channel_id'):
                                current_voice = {
                                    'channel_id': vs['channel_id'],
                                    'guild_id': guild['id'],
                                    'type': 'Server VC'
                                }
                                break
                        if current_voice:
                            break
                
                ready = True
        
        return current_voice
    
    async def maintain_voice_connection(self, channel_id=None, guild_id=None, auto_join=False):
        # Connect to gateway and maintain voice connection with auto-reconnect
        gateway_url = "wss://gateway.discord.gg/?v=9&encoding=json"
        
        # Set start time on first connection
        if self.start_time is None:
            self.start_time = datetime.now()
        
        while self.active:
            try:
                await self._connect_and_run(gateway_url, channel_id, guild_id, auto_join)
            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)
                    # After reconnect, don't auto-join again, use stored IDs
                    auto_join = False
                else:
                    break
            except KeyboardInterrupt:
                print("\n\nDisconnecting...")
                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)
                    auto_join = False
                else:
                    break
    
    async def _connect_and_run(self, gateway_url, channel_id, guild_id, auto_join):
        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())
            
            try:
                # If auto-join, find current voice channel
                if auto_join:
                    print("Detecting current voice channel...")
                    current_voice = await self.find_current_voice_channel(ws)
                    
                    if not current_voice:
                        print("Not currently in any voice channel/call")
                        return
                    
                    channel_id = current_voice['channel_id']
                    guild_id = current_voice['guild_id']
                    print(f"Found: {current_voice['type']}")
                    
                    # Store for reconnections
                    self.current_channel_id = channel_id
                    self.current_guild_id = guild_id
                else:
                    # Wait for READY
                    ready = False
                    while not ready:
                        msg = json.loads(await ws.recv())
                        if msg.get('t') == 'READY':
                            ready = True
                            self.my_user_id = msg['d']['user']['id']
                            
                            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)")
                    
                    # Use stored or provided IDs
                    if self.current_channel_id is None:
                        self.current_channel_id = channel_id
                        self.current_guild_id = guild_id
                
                # Join/maintain voice channel/call
                if self.total_reconnects == 0:
                    print(f"Maintaining voice connection...")
                    print("Call keeper active - Press Ctrl+C to disconnect")
                    print("=" * 60)
                
                voice_state_update = {
                    "op": 4,
                    "d": {
                        "guild_id": self.current_guild_id,
                        "channel_id": self.current_channel_id,
                        "self_mute": False,
                        "self_deaf": False
                    }
                }
                await ws.send(json.dumps(voice_state_update))
                
                # Monitor voice state and keep connection alive
                while self.active:
                    msg = await ws.recv()
                    data = json.loads(msg)
                    
                    event_type = data.get('t')
                    
                    # Track voice state changes
                    if event_type == 'VOICE_STATE_UPDATE':
                        voice_data = data['d']
                        # Check if it's related to our channel
                        if voice_data.get('channel_id') == self.current_channel_id or voice_data.get('user_id') == self.my_user_id:
                            user_id = voice_data.get('user_id')
                            member = voice_data.get('member', {})
                            user = member.get('user', {})
                            username = user.get('username', 'Unknown')
                            
                            if voice_data.get('channel_id') is None and user_id != self.my_user_id:
                                print(f"[-] {username} left")
                            elif voice_data.get('channel_id') == self.current_channel_id and user_id != self.my_user_id:
                                elapsed = (datetime.now() - self.start_time).total_seconds()
                                print(f"[+] {username} joined (running {elapsed:.0f}s)")
                    
                    # Voice server update
                    elif event_type == 'VOICE_SERVER_UPDATE':
                        self.token = data['d'].get('token')
                        self.endpoint = data['d'].get('endpoint')
                        if self.total_reconnects == 0:
                            print(f"[✓] Connection established")
                        else:
                            print(f"[✓] Voice connection re-established")
                    
                    elif event_type == 'CALL_CREATE':
                        print("[📞] Call created")
                    
                    elif event_type == 'CALL_UPDATE':
                        ringing = data['d'].get('ringing', [])
                        if ringing:
                            print(f"[📞] Ringing: {len(ringing)} user(s)")
                
            finally:
                heartbeat_task.cancel()
                
                # Only leave voice if we're truly shutting down
                if not self.active:
                    try:
                        leave_voice = {
                            "op": 4,
                            "d": {
                                "guild_id": self.current_guild_id,
                                "channel_id": None,
                                "self_mute": False,
                                "self_deaf": False
                            }
                        }
                        await ws.send(json.dumps(leave_voice))
                    except Exception:
                        pass
                    
                    # Stats
                    total_time = (datetime.now() - self.start_time).total_seconds()
                    hours = total_time / 3600
                    minutes = (total_time % 3600) / 60
                    
                    print("\n" + "=" * 60)
                    print(f"Duration: {int(hours)}h {int(minutes)}m")
                    print(f"Total reconnections: {self.total_reconnects}")
                    print("=" * 60)

def main():
    print("Voice Call Keeper")
    print("=" * 60)
    
    print("1. Auto-join current call/VC")
    print("2. Join specific channel")
    print("=" * 60)
    
    choice = input("Choice: ").strip()
    
    if choice == '1':
        # Auto-join current voice
        keeper = VoiceCallKeeper()
        asyncio.run(keeper.maintain_voice_connection(auto_join=True))
    
    elif choice == '2':
        # Manual join
        channel_id = input("\nChannel ID: ").strip()
        
        if not channel_id.isdigit():
            print("Invalid channel ID")
            return
        
        # Ask if it's a server VC
        is_server = input("Server VC? (y/n): ").strip().lower()
        
        guild_id = None
        if is_server == 'y':
            guild_id = input("Guild ID: ").strip()
            if not guild_id.isdigit():
                print("Invalid guild ID")
                return
        
        keeper = VoiceCallKeeper()
        asyncio.run(keeper.maintain_voice_connection(channel_id, guild_id=guild_id))
    
    else:
        print("Invalid choice")

if __name__ == "__main__":
    main()
