import customtkinter as ctk
from robloxapi import Client, RobloxAPI
import asyncio
import json
from PIL import Image, ImageTk
import aiohttp
from io import BytesIO
import threading
import logging
import sys

# 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.roblox_client = None
        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(6, weight=1)
        self.main_frame.grid_columnconfigure(0, weight=1)
        
        # Login section
        self.login_frame = ctk.CTkFrame(self.main_frame)
        self.login_frame.grid(row=0, column=0, sticky="ew", pady=(0, 10))
        self.login_frame.grid_columnconfigure(1, weight=1)
        
        ctk.CTkLabel(self.login_frame, text="Roblox Login", font=ctk.CTkFont(size=16, weight="bold")).grid(row=0, column=0, columnspan=2, pady=(0, 10))
        
        ctk.CTkLabel(self.login_frame, text="Username:").grid(row=1, column=0, sticky="e", padx=(0,5))
        self.username_entry = ctk.CTkEntry(self.login_frame, placeholder_text="Enter your username")
        self.username_entry.grid(row=1, column=1, sticky="ew", pady=5)
        
        ctk.CTkLabel(self.login_frame, text="Password:").grid(row=2, column=0, sticky="e", padx=(0,5))
        self.password_entry = ctk.CTkEntry(self.login_frame, placeholder_text="Enter your password", show="*")
        self.password_entry.grid(row=2, column=1, sticky="ew", pady=5)
        
        self.login_button = ctk.CTkButton(
            self.login_frame, 
            text="Login", 
            command=self.handle_login
        )
        self.login_button.grid(row=3, column=0, columnspan=2, pady=10)
        
        # Separator
        self.separator = ctk.CTkFrame(self.main_frame, height=2, fg_color="gray")
        self.separator.grid(row=1, column=0, sticky="ew", pady=10)
        
        # Game search section
        self.search_frame = ctk.CTkFrame(self.main_frame)
        self.search_frame.grid(row=2, column=0, sticky="ew", pady=(0, 10))
        self.search_frame.grid_columnconfigure(1, weight=1)
        
        ctk.CTkLabel(self.search_frame, text="Search Game:").grid(row=0, column=0, sticky="e", padx=(0,5))
        self.game_entry = ctk.CTkEntry(
            self.search_frame, 
            placeholder_text="Enter Game Name or ID",
            state="disabled"
        )
        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,
            state="disabled"
        )
        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=3, 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)
        
        # Filter by Ping
        ctk.CTkLabel(self.filter_sort_frame, text="Max Ping:").grid(row=0, column=2, padx=(0,5), pady=5)
        self.max_ping_entry = ctk.CTkEntry(self.filter_sort_frame, width=50, placeholder_text="999")
        self.max_ping_entry.grid(row=0, column=3, padx=(0,15), pady=5)
        
        # Sort Options
        ctk.CTkLabel(self.filter_sort_frame, text="Sort By:").grid(row=0, column=4, padx=(0,5), pady=5)
        self.sort_options = ["Player Count", "Ping"]
        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=5, padx=(0,15), pady=5)
        
        # Refresh Button
        self.refresh_button = ctk.CTkButton(
            self.filter_sort_frame,
            text="Refresh",
            command=self.refresh_servers,
            state="disabled"
        )
        self.refresh_button.grid(row=0, column=6, padx=(0,0), pady=5)
        
        # Server list with pagination
        self.servers_frame = ctk.CTkFrame(self.main_frame)
        self.servers_frame.grid(row=4, 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=5, 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: Not logged in",
            anchor="w"
        )
        self.status_label.grid(row=6, column=0, sticky="ew", pady=(10,0))
        
    def handle_login(self):
        username = self.username_entry.get()
        password = self.password_entry.get()
        
        if not username or not password:
            self.show_error("Please enter both username and password")
            return
            
        # Disable login button to prevent multiple clicks
        self.login_button.configure(state="disabled")
        self.update_status("Logging in...")
        logging.info("Initiating login process.")

        # Start the login process in a separate thread
        threading.Thread(
            target=self.loop.run_until_complete, 
            args=(self._login_process(username, password),)
        ).start()
        
    async def _login_process(self, username, password):
        try:
            # Initialize Roblox client
            self.roblox_client = RobloxAPI()
            logging.info("RobloxAPI client initialized.")
            
            # Perform login (Assuming robloxapi supports async login)
            # Replace with actual login method if available
            success = await self.roblox_client.login(username, password)
            if success:
                self.update_status("Logged in successfully!")
                self.enable_search_controls()
                logging.info("User logged in successfully.")
            else:
                self.show_error("Login failed. Please check your credentials.")
                self.update_status("Login failed.")
                self.login_button.configure(state="normal")
                logging.warning("Login failed due to incorrect credentials.")
        except Exception as e:
            logging.error(f"Login process error: {str(e)}")
            self.show_error(f"Login failed: {str(e)}")
            self.update_status("Login failed.")
            self.login_button.configure(state="normal")
        
    def enable_search_controls(self):
        self.game_entry.configure(state="normal")
        self.search_button.configure(state="normal")
        self.refresh_button.configure(state="normal")
        self.update_status("Logged in. You can now search for games.")
        logging.info("Search controls enabled.")
        
    def search_game(self):
        game_query = self.game_entry.get()
        if not game_query:
            self.show_error("Please enter a game name or ID")
            return
            
        # Reset pagination
        self.current_page = 0
        self.servers_list.clear()
        self.clear_servers_display()
        
        # Disable search button to prevent multiple clicks
        self.search_button.configure(state="disabled")
        self.update_status(f"Searching for game: {game_query}")
        logging.info(f"Searching for game: {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_query):
        try:
            # Determine if query is name or ID
            if game_query.isdigit():
                game_id = int(game_query)
                logging.info(f"Searching for game by ID: {game_id}")
            else:
                game_id = await self.get_game_id_by_name(game_query)
                if not game_id:
                    self.show_error(f"No game found with name: {game_query}")
                    self.update_status("Game search failed.")
                    self.search_button.configure(state="normal")
                    return
                logging.info(f"Found game ID {game_id} for name: {game_query}")
            
            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.")
            
            self.search_button.configure(state="normal")
        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.")
            self.search_button.configure(state="normal")
            
    async def get_game_id_by_name(self, game_name):
        try:
            # Example endpoint, replace with actual Roblox API endpoint
            url = f"https://games.roblox.com/v1/games?universeIds=&keyword={game_name}"
            async with self.session.get(url) as resp:
                if resp.status == 200:
                    data = await resp.json()
                    if data['data']:
                        return data['data'][0]['universeId']
            return None
        except Exception as e:
            logging.error(f"Error fetching game ID by name: {str(e)}")
            return None
        
    async def get_game_servers(self, game_id):
        try:
            # Example endpoint, replace with actual Roblox API endpoint
            url = f"https://games.roblox.com/v1/games/{game_id}/servers/Public?sortOrder=Asc&limit=100"
            async with self.session.get(url) as resp:
                if resp.status == 200:
                    data = await resp.json()
                    servers = [
                        {
                            "id": server['id'],
                            "name": server['friendlyName'],
                            "player_count": server['players'],
                            "max_players": server['maxPlayers'],
                            "ping": server['ping'] if 'ping' in server else 'N/A'
                        }
                        for server in data.get('data', [])
                    ]
                    return servers
            return []
        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
            max_ping = int(self.max_ping_entry.get()) if self.max_ping_entry.get().isdigit() else 999
            sort_by = self.sort_var.get()
            
            filtered = [
                server for server in self.servers_list
                if server['player_count'] >= min_players and (server['ping'] <= max_ping if isinstance(server['ping'], int) else True)
            ]
            
            if sort_by == "Player Count":
                filtered.sort(key=lambda x: x['player_count'], reverse=True)
            elif sort_by == "Ping":
                filtered.sort(key=lambda x: x['ping'] if isinstance(x['ping'], int) else 999)
            
            self.display_servers(filtered)
            logging.info(f"Applied filters: min_players={min_players}, max_ping={max_ping}, 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"Name: {server['name']}\n"
                f"Players: {server['player_count']}/{server['max_players']}\n"
                f"Ping: {server['ping']}ms"
            )
            ctk.CTkLabel(server_frame, text=server_info, justify="left").pack(side="left", padx=10, pady=10)
            
            # Optionally, add more server details or actions here
            
        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 show_error(self, message):
        ctk.CTkMessagebox(
            title="Error",
            message=message,
            icon="cancel"
        )
        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.")
        asyncio.create_task(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() fill all the code ehnance everythign on very good updated level and all not omplemented logics implemented all real