import customtkinter as ctk
import asyncio
import aiohttp
import threading
import logging
import sys
import tkinter.messagebox as tkmb

# Ensure compatibility with asyncio and tkinter
if sys.platform.startswith('win'):
    asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())

class RobloxServerBrowser(ctk.CTk):
    def __init__(self):
        super().__init__()
        
        # Configure window
        self.title("Roblox Server Browser")
        self.geometry("900x700")
        ctk.set_appearance_mode("dark")
        ctk.set_default_color_theme("blue")
        
        # Initialize variables
        self.current_game = None
        self.servers_list = []
        self.session = aiohttp.ClientSession()
        self.loop = asyncio.get_event_loop()

        # Pagination variables
        self.current_page = 0
        self.servers_per_page = 10

        self.setup_logging()
        self.create_gui()
        
    def setup_logging(self):
        logging.basicConfig(
            level=logging.INFO,
            format='%(asctime)s - %(levelname)s - %(message)s',
            handlers=[
                logging.FileHandler('roblox_browser.log'),
                logging.StreamHandler(sys.stdout)
            ]
        )
        logging.info("Application started.")
        
    def create_gui(self):
        # Create main container with grid layout
        self.grid_rowconfigure(0, weight=1)
        self.grid_columnconfigure(0, weight=1)
        
        self.main_frame = ctk.CTkFrame(self)
        self.main_frame.grid(row=0, column=0, sticky="nsew", padx=20, pady=20)
        self.main_frame.grid_rowconfigure(4, weight=1)
        self.main_frame.grid_columnconfigure(0, weight=1)
        
        # Game search section
        self.search_frame = ctk.CTkFrame(self.main_frame)
        self.search_frame.grid(row=0, column=0, sticky="ew", pady=(0, 10))
        self.search_frame.grid_columnconfigure(1, weight=1)
        
        ctk.CTkLabel(self.search_frame, text="Search Game (by ID):").grid(row=0, column=0, sticky="e", padx=(0,5))
        self.game_entry = ctk.CTkEntry(
            self.search_frame, 
            placeholder_text="Enter Game ID"
        )
        self.game_entry.grid(row=0, column=1, sticky="ew", pady=5)
        
        self.search_button = ctk.CTkButton(
            self.search_frame,
            text="Search",
            command=self.search_game
        )
        self.search_button.grid(row=0, column=2, padx=(10,0))
        
        # Filter and Sort section
        self.filter_sort_frame = ctk.CTkFrame(self.main_frame)
        self.filter_sort_frame.grid(row=1, column=0, sticky="ew", pady=(0,10))
        self.filter_sort_frame.grid_columnconfigure(3, weight=1)
        
        # Filter by Player Count
        ctk.CTkLabel(self.filter_sort_frame, text="Min Players:").grid(row=0, column=0, padx=(0,5), pady=5)
        self.min_players_entry = ctk.CTkEntry(self.filter_sort_frame, width=50, placeholder_text="0")
        self.min_players_entry.grid(row=0, column=1, padx=(0,15), pady=5)
        
        # Sort Options
        ctk.CTkLabel(self.filter_sort_frame, text="Sort By:").grid(row=0, column=2, padx=(0,5), pady=5)
        self.sort_options = ["Player Count", "Server ID"]
        self.sort_var = ctk.StringVar(value=self.sort_options[0])
        self.sort_menu = ctk.CTkOptionMenu(
            self.filter_sort_frame, 
            values=self.sort_options, 
            variable=self.sort_var,
            command=self.apply_filters
        )
        self.sort_menu.grid(row=0, column=3, padx=(0,15), pady=5)
        
        # Refresh Button
        self.refresh_button = ctk.CTkButton(
            self.filter_sort_frame,
            text="Refresh",
            command=self.refresh_servers
        )
        self.refresh_button.grid(row=0, column=4, padx=(0,0), pady=5)
        
        # Server list with pagination
        self.servers_frame = ctk.CTkFrame(self.main_frame)
        self.servers_frame.grid(row=2, column=0, sticky="nsew")
        self.servers_frame.grid_rowconfigure(0, weight=1)
        self.servers_frame.grid_columnconfigure(0, weight=1)
        
        self.servers_list_box = ctk.CTkScrollableFrame(self.servers_frame)
        self.servers_list_box.grid(row=0, column=0, sticky="nsew")
        
        # Pagination controls
        self.pagination_frame = ctk.CTkFrame(self.main_frame)
        self.pagination_frame.grid(row=3, column=0, sticky="ew", pady=10)
        self.pagination_frame.grid_columnconfigure(0, weight=1)
        self.pagination_frame.grid_columnconfigure(1, weight=1)
        self.pagination_frame.grid_columnconfigure(2, weight=1)
        
        self.prev_button = ctk.CTkButton(
            self.pagination_frame,
            text="Previous",
            command=self.prev_page,
            state="disabled"
        )
        self.prev_button.grid(row=0, column=0, padx=10)
        
        self.page_label = ctk.CTkLabel(self.pagination_frame, text="Page 0 of 0")
        self.page_label.grid(row=0, column=1)
        
        self.next_button = ctk.CTkButton(
            self.pagination_frame,
            text="Next",
            command=self.next_page,
            state="disabled"
        )
        self.next_button.grid(row=0, column=2, padx=10)
        
        # Status bar
        self.status_label = ctk.CTkLabel(
            self.main_frame, 
            text="Status: Ready",
            anchor="w"
        )
        self.status_label.grid(row=4, column=0, sticky="ew", pady=(10,0))
        
    def search_game(self):
        game_query = self.game_entry.get()
        if not game_query:
            self.show_error("Please enter a game ID")
            return
        if not game_query.isdigit():
            self.show_error("Please enter a valid game ID (numbers only)")
            return
            
        # Reset pagination
        self.current_page = 0
        self.servers_list.clear()
        self.clear_servers_display()
        
        self.update_status(f"Searching for game ID: {game_query}")
        logging.info(f"Searching for game ID: {game_query}")
        
        # Start the search process in a separate thread
        threading.Thread(
            target=self.loop.run_until_complete, 
            args=(self._search_game_process(game_query),)
        ).start()
        
    async def _search_game_process(self, game_id):
        try:
            game_id = int(game_id)
            self.current_game = game_id
            self.update_status(f"Fetching servers for game ID: {game_id}")
            
            # Fetch servers
            servers = await self.get_game_servers(game_id)
            if servers:
                self.servers_list = servers
                self.apply_filters()
                self.update_status(f"Found {len(self.servers_list)} servers.")
                logging.info(f"Fetched {len(self.servers_list)} servers for game ID: {game_id}")
            else:
                self.show_error("No servers found for this game.")
                self.update_status("No servers found.")
        except Exception as e:
            logging.error(f"Game search error: {str(e)}")
            self.show_error(f"Failed to search for game servers: {str(e)}")
            self.update_status("Game search failed.")
                
    async def get_game_servers(self, game_id):
        try:
            servers = []
            cursor = ""
            while True:
                url = f"https://games.roblox.com/v1/games/{game_id}/servers/Public?sortOrder=Asc&limit=100&cursor={cursor}"
                async with self.session.get(url) as resp:
                    if resp.status == 200:
                        data = await resp.json()
                        for server in data.get('data', []):
                            servers.append({
                                "id": server['id'],
                                "player_count": server['playing'],
                                "max_players": server['maxPlayers']
                            })
                        cursor = data.get('nextPageCursor')
                        if not cursor:
                            break
                    else:
                        break
            return servers
        except Exception as e:
            logging.error(f"Error fetching game servers: {str(e)}")
            return []
            
    def apply_filters(self, *args):
        try:
            min_players = int(self.min_players_entry.get()) if self.min_players_entry.get().isdigit() else 0
            sort_by = self.sort_var.get()
            
            filtered = [
                server for server in self.servers_list
                if server['player_count'] >= min_players
            ]
            
            if sort_by == "Player Count":
                filtered.sort(key=lambda x: x['player_count'], reverse=True)
            elif sort_by == "Server ID":
                filtered.sort(key=lambda x: x['id'])
            
            self.display_servers(filtered)
            logging.info(f"Applied filters: min_players={min_players}, sort_by={sort_by}")
        except Exception as e:
            logging.error(f"Error applying filters: {str(e)}")
            self.show_error("Error applying filters.")
            
    def display_servers(self, servers):
        self.clear_servers_display()
        
        start = self.current_page * self.servers_per_page
        end = start + self.servers_per_page
        paginated = servers[start:end]
        
        for server in paginated:
            server_frame = ctk.CTkFrame(self.servers_list_box, corner_radius=8)
            server_frame.pack(fill="x", padx=10, pady=5)
            
            server_info = (
                f"Server ID: {server['id']}\n"
                f"Players: {server['player_count']}/{server['max_players']}"
            )
            ctk.CTkLabel(server_frame, text=server_info, justify="left").pack(side="left", padx=10, pady=10)
            
            # Optionally, add a button to copy the server ID
            copy_button = ctk.CTkButton(server_frame, text="Copy Server ID", command=lambda s_id=server['id']: self.copy_server_id(s_id))
            copy_button.pack(side="right", padx=10, pady=10)
            
        total_pages = max(1, (len(servers) + self.servers_per_page - 1) // self.servers_per_page)
        self.page_label.configure(text=f"Page {self.current_page + 1} of {total_pages}")
        
        # Update pagination buttons
        if self.current_page > 0:
            self.prev_button.configure(state="normal")
        else:
            self.prev_button.configure(state="disabled")
            
        if (self.current_page + 1) < total_pages:
            self.next_button.configure(state="normal")
        else:
            self.next_button.configure(state="disabled")
            
    def clear_servers_display(self):
        for widget in self.servers_list_box.winfo_children():
            widget.destroy()
        self.page_label.configure(text="Page 0 of 0")
        self.prev_button.configure(state="disabled")
        self.next_button.configure(state="disabled")
        
    def refresh_servers(self):
        if not self.current_game:
            self.show_error("No game selected to refresh.")
            return
        
        self.update_status("Refreshing server list...")
        logging.info("Refreshing server list.")
        self.servers_list.clear()
        self.clear_servers_display()
        threading.Thread(
            target=self.loop.run_until_complete, 
            args=(self._search_game_process(self.current_game),)
        ).start()
        
    def next_page(self):
        self.current_page += 1
        self.apply_filters()
        
    def prev_page(self):
        if self.current_page > 0:
            self.current_page -= 1
            self.apply_filters()
            
    def copy_server_id(self, server_id):
        self.clipboard_clear()
        self.clipboard_append(str(server_id))
        self.update_status(f"Copied Server ID: {server_id}")
            
    def show_error(self, message):
        tkmb.showerror(title="Error", message=message)
        logging.warning(f"User Error: {message}")
        
    def update_status(self, message):
        self.status_label.configure(text=f"Status: {message}")
        logging.info(f"Status Update: {message}")
        
    def on_closing(self):
        logging.info("Shutting down application.")
        self.loop.run_until_complete(self.session.close())
        self.destroy()
        
    def run(self):
        self.protocol("WM_DELETE_WINDOW", self.on_closing)
        self.mainloop()

if __name__ == "__main__":
    app = RobloxServerBrowser()
    app.run() make the cod 80% harder coding style coded thank you alot better logics used pro coding experience give all the complete code