import tkinter as tk
from tkinter import ttk, messagebox
import threading
import time
import json
import os
import subprocess
import re
import psutil
from pythonosc import udp_client
import dbus
import GPUtil
from datetime import datetime

class LinuxVRChatStatusSystem:
    def __init__(self):
        self.osc_client = udp_client.SimpleUDPClient("127.0.0.1", 9000)
        self.config_file = "vrc_status_config.json"
        self.load_config()
        
    def load_config(self):
        self.config = {
            'show_window': True,
            'show_music': True,
            'show_song_details': True,
            'show_system_info': True,
            'show_cpu_usage': True,
            'show_gpu_usage': True,
            'show_ram_usage': True,
            'max_length': 70,
            'update_interval': 5.0,
            'system_update_interval': 10.0
        }
        if os.path.exists(self.config_file):
            with open(self.config_file, 'r') as f:
                self.config.update(json.load(f))
    
    def save_config(self):
        with open(self.config_file, 'w') as f:
            json.dump(self.config, f, indent=2)
    
    def get_system_info(self):
        """Получаем информацию о системе"""
        info = {}
        
        try:
            # CPU информация
            cpu_percent = psutil.cpu_percent(interval=0.1)
            cpu_freq = psutil.cpu_freq()
            cpu_cores = psutil.cpu_count(logical=False)
            cpu_threads = psutil.cpu_count(logical=True)
            
            info['cpu'] = {
                'usage': cpu_percent,
                'freq': f"{cpu_freq.current:.0f}MHz" if cpu_freq else "N/A",
                'cores': cpu_cores,
                'threads': cpu_threads
            }
            
            # Память
            memory = psutil.virtual_memory()
            info['memory'] = {
                'total': memory.total // (1024**3),
                'used': memory.used // (1024**3),
                'percent': memory.percent
            }
            
            # GPU информация
            try:
                gpus = GPUtil.getGPUs()
                if gpus:
                    gpu = gpus[0]  # Берем первую GPU
                    info['gpu'] = {
                        'name': gpu.name,
                        'usage': gpu.load * 100,
                        'memory_used': gpu.memoryUsed,
                        'memory_total': gpu.memoryTotal,
                        'temperature': gpu.temperature
                    }
                else:
                    # Альтернативный метод для NVIDIA
                    try:
                        result = subprocess.run(['nvidia-smi', '--query-gpu=utilization.gpu,memory.used,memory.total,temperature.gpu,name', '--format=csv,noheader,nounits'], 
                                              capture_output=True, text=True, timeout=3)
                        if result.returncode == 0:
                            gpu_data = result.stdout.strip().split(', ')
                            if len(gpu_data) >= 4:
                                info['gpu'] = {
                                    'name': gpu_data[4] if len(gpu_data) > 4 else "NVIDIA GPU",
                                    'usage': float(gpu_data[0]),
                                    'memory_used': float(gpu_data[1]),
                                    'memory_total': float(gpu_data[2]),
                                    'temperature': float(gpu_data[3])
                                }
                    except:
                        info['gpu'] = None
            except:
                info['gpu'] = None
            
            # Диски
            disks = []
            for partition in psutil.disk_partitions():
                try:
                    usage = psutil.disk_usage(partition.mountpoint)
                    
                    if usage.total > 0:  # Игнорируем специальные файловые системы
                        disks.append({
                            'device': partition.device,
                            'mountpoint': partition.mountpoint,
                            'total': usage.total // (1024**3),
                            'used': usage.used // (1024**3),
                            'percent': usage.percent
                        })
                except:
                    continue
            info['disks'] = disks
            
            # Сеть
            net_io = psutil.net_io_counters()
            info['network'] = {
                'bytes_sent': net_io.bytes_sent // (1024**2),  # MB
                'bytes_recv': net_io.bytes_recv // (1024**2)   # MB
            }
            
            # Температуры (если доступно)
            try:
                temps = psutil.sensors_temperatures()
                if temps:
                    for name, entries in temps.items():
                        if entries:
                            info['temperature'] = entries[0].current
                            break
            except:
                info['temperature'] = None
                
        except Exception as e:
            print(f"Ошибка получения системной информации: {e}")
            
        return info
    
    def get_active_window(self):
        try:
            result = subprocess.run(
                ['xdotool', 'getactivewindow', 'getwindowname'], 
                capture_output=True, text=True, timeout=2
            )
            if result.returncode == 0:
                window_title = result.stdout.strip()
                if ' - ' in window_title:
                    parts = window_title.split(' - ')
                    if len(parts) > 1:
                        return parts[-1]
                return window_title[:25]
        except Exception as e:
            print(f"Ошибка получения окна: {e}")
        return "Рабочий стол"
    
    def get_media_players(self):
        try:
            result = subprocess.run(
                ['playerctl', '-l'], 
                capture_output=True, text=True, timeout=2
            )
            if result.returncode == 0:
                players = result.stdout.strip().split('\n')
                return [p for p in players if p]
        except Exception as e:
            print(f"Ошибка playerctl: {e}")
        return []
    
    def get_current_track(self, player):
        try:
            metadata_cmd = ['playerctl', '-p', player, 'metadata']
            result = subprocess.run(metadata_cmd, capture_output=True, text=True, timeout=2)
            
            if result.returncode == 0:
                metadata = result.stdout
                artist = None
                title = None
                album = None
                
                for line in metadata.split('\n'):
                    if 'xesam:artist' in line and not artist:
                        artist = line.split('xesam:artist')[1].strip().strip('"')
                    elif 'xesam:title' in line and not title:
                        title = line.split('xesam:title')[1].strip().strip('"')
                    elif 'xesam:album' in line and not album:
                        album = line.split('xesam:album')[1].strip().strip('"')
                
                status_cmd = ['playerctl', '-p', player, 'status']
                status_result = subprocess.run(status_cmd, capture_output=True, text=True, timeout=2)
                status = status_result.stdout.strip() if status_result.returncode == 0 else "Unknown"
                
                return {
                    'artist': artist or 'Неизвестный исполнитель',
                    'title': title or 'Без названия',
                    'album': album or '',
                    'status': status,
                    'player': player
                }
                
        except Exception as e:
            print(f"Ошибка получения трека {player}: {e}")
        return None
    
    def get_browser_youtube_tab(self):
        try:
            bus = dbus.SessionBus()
            firefox = bus.get_object('org.mozilla.firefox', '/org/mozilla/firefox')
            tabs = firefox.get_tabs()
            for tab in tabs:
                url = tab.get_url()
                if 'youtube.com/watch' in url:
                    title = tab.get_title()
                    if ' - YouTube' in title:
                        song_info = title.replace(' - YouTube', '')
                        return f"🎵 {song_info}"
        except:
            pass
        
        try:
            window_title = self.get_active_window()
            if 'YouTube' in window_title and ' - ' in window_title:
                parts = window_title.split(' - ')
                if len(parts) >= 2:
                    song_info = ' - '.join(parts[:-1])
                    return f"🎵 {song_info}"
        except:
            pass
        
        return None
    
    def get_audio_sinks(self):
        try:
            result = subprocess.run(
                ['pactl', 'list', 'sink-inputs'], 
                capture_output=True, text=True, timeout=3
            )
            if result.returncode == 0:
                sink_inputs = result.stdout
                browsers = ['firefox', 'chromium', 'chrome', 'brave']
                for browser in browsers:
                    if browser in sink_inputs.lower():
                        return f"🎵 Музыка в {browser.capitalize()}"
        except Exception as e:
            print(f"Ошибка анализа аудио: {e}")
        return None
    
    def get_music_info(self):
        youtube_info = self.get_browser_youtube_tab()
        if youtube_info:
            return youtube_info
        
        players = self.get_media_players()
        for player in players:
            track = self.get_current_track(player)
            if track and track['status'] == 'Playing':
                if self.config['show_song_details']:
                    return f"🎵 {track['artist']} - {track['title']}"
                else:
                    player_name = player.split('.')[-1].capitalize()
                    return f"🎵 Слушаю {player_name}"
        
        audio_info = self.get_audio_sinks()
        if audio_info:
            return audio_info
        
        return "🎵 Музыка не играет"
    
    def get_system_status_compact(self):
        """Компактный статус системы для VRChat"""
        try:
            system_info = self.get_system_info()
            parts = []
            
            if self.config['show_cpu_usage'] and 'cpu' in system_info:
                cpu_usage = system_info['cpu']['usage']
                parts.append(f"CPU:{cpu_usage:.0f}%")
            
            if self.config['show_ram_usage'] and 'memory' in system_info:
                ram_usage = system_info['memory']['percent']
                parts.append(f"RAM:{ram_usage:.0f}%")
            
            if self.config['show_gpu_usage'] and 'gpu' in system_info and system_info['gpu']:
                gpu_usage = system_info['gpu']['usage']
                parts.append(f"GPU:{gpu_usage:.0f}%")
            
            if parts:
                return f"⚙️ {' '.join(parts)}"
            else:
                return "⚙️ Система"
                
        except Exception as e:
            print(f"Ошибка системного статуса: {e}")
            return "⚙️ Система"
    
    def format_status(self):
        status_parts = []
        
        if self.config['show_window']:
            window = self.get_active_window()
            status_parts.append(f"🖥️ {window}")
            
        if self.config['show_music']:
            music = self.get_music_info()
            status_parts.append(music)
        
        if self.config['show_system_info']:
            system_status = self.get_system_status_compact()
            status_parts.append(system_status)
        
        status = " | ".join(status_parts)
        return status[:self.config['max_length']]
    
    def send_to_vrchat(self, text):
        try:
            self.osc_client.send_message("/chatbox/input", [text, True])
            print(f"Отправлено в VRChat: {text}")
            return True
        except Exception as e:
            print(f"Ошибка отправки OSC: {e}")
            return False

class LinuxStatusGUI:
    def __init__(self):
        self.system = LinuxVRChatStatusSystem()
        self.is_running = False
        self.update_thread = None
        self.last_system_update = 0
        
        self.setup_gui()
    
    def setup_gui(self):
        self.root = tk.Tk()
        self.root.title("VRChat Status Display - Linux Pro")
        self.root.geometry("700x700")
        self.root.protocol("WM_DELETE_WINDOW", self.on_closing)
        
        # Стиль
        style = ttk.Style()
        style.configure('TFrame', background='#f0f0f0')
        style.configure('TLabel', background='#f0f0f0')
        style.configure('Header.TLabel', font=('Arial', 12, 'bold'))
        
        main_frame = ttk.Frame(self.root, padding="15")
        main_frame.pack(fill=tk.BOTH, expand=True)
        
        # Заголовок
        title = ttk.Label(main_frame, text="VRChat Status Display - Linux Pro", 
                         font=('Arial', 16, 'bold'))
        title.pack(pady=10)
        
        # Создаем notebook для вкладок
        notebook = ttk.Notebook(main_frame)
        notebook.pack(fill=tk.BOTH, expand=True, pady=10)
        
        # Вкладка настроек
        settings_frame = ttk.Frame(notebook, padding="10")
        notebook.add(settings_frame, text="Настройки")
        
        # Настройки отображения
        display_frame = ttk.LabelFrame(settings_frame, text="Отображение в статусе", padding="10")
        display_frame.pack(fill=tk.X, pady=5)
        
        self.show_window = tk.BooleanVar(value=self.system.config['show_window'])
        self.show_music = tk.BooleanVar(value=self.system.config['show_music'])
        self.show_song_details = tk.BooleanVar(value=self.system.config.get('show_song_details', True))
        self.show_system_info = tk.BooleanVar(value=self.system.config.get('show_system_info', True))
        
        ttk.Checkbutton(display_frame, text="Активное окно", 
                       variable=self.show_window).pack(anchor=tk.W)
        ttk.Checkbutton(display_frame, text="Музыка", 
                       variable=self.show_music).pack(anchor=tk.W)
        ttk.Checkbutton(display_frame, text="Детали трека", 
                       variable=self.show_song_details).pack(anchor=tk.W)
        ttk.Checkbutton(display_frame, text="Системная информация", 
                       variable=self.show_system_info).pack(anchor=tk.W)
        
        # Настройки системы
        system_frame = ttk.LabelFrame(settings_frame, text="Системные метрики", padding="10")
        system_frame.pack(fill=tk.X, pady=5)
        
        self.show_cpu_usage = tk.BooleanVar(value=self.system.config.get('show_cpu_usage', True))
        self.show_gpu_usage = tk.BooleanVar(value=self.system.config.get('show_gpu_usage', True))
        self.show_ram_usage = tk.BooleanVar(value=self.system.config.get('show_ram_usage', True))
        
        ttk.Checkbutton(system_frame, text="Загрузка CPU", 
                       variable=self.show_cpu_usage).pack(anchor=tk.W)
        ttk.Checkbutton(system_frame, text="Загрузка GPU", 
                       variable=self.show_gpu_usage).pack(anchor=tk.W)
        ttk.Checkbutton(system_frame, text="Использование RAM", 
                       variable=self.show_ram_usage).pack(anchor=tk.W)
        
        # Интервалы
        interval_frame = ttk.LabelFrame(settings_frame, text="Интервалы обновления", padding="10")
        interval_frame.pack(fill=tk.X, pady=5)
        
        ttk.Label(interval_frame, text="Основной интервал (сек):").grid(row=0, column=0, sticky=tk.W)
        self.interval_var = tk.StringVar(value=str(self.system.config['update_interval']))
        ttk.Entry(interval_frame, textvariable=self.interval_var, width=8).grid(row=0, column=1, padx=5)
        
        ttk.Label(interval_frame, text="Системный интервал (сек):").grid(row=1, column=0, sticky=tk.W)

        self.system_interval_var = tk.StringVar(value=str(self.system.config.get('system_update_interval', 10.0)))
        ttk.Entry(interval_frame, textvariable=self.system_interval_var, width=8).grid(row=1, column=1, padx=5)
        
        ttk.Label(interval_frame, text="Макс. длина текста:").grid(row=2, column=0, sticky=tk.W)
        self.length_var = tk.StringVar(value=str(self.system.config['max_length']))
        ttk.Entry(interval_frame, textvariable=self.length_var, width=8).grid(row=2, column=1, padx=5)
        
        # Вкладка мониторинга системы
        monitor_frame = ttk.Frame(notebook, padding="10")
        notebook.add(monitor_frame, text="Мониторинг системы")
        
        # Системная информация
        sys_info_frame = ttk.LabelFrame(monitor_frame, text="Системная информация в реальном времени", padding="10")
        sys_info_frame.pack(fill=tk.BOTH, expand=True)
        
        self.system_text = tk.Text(sys_info_frame, height=15, width=80, font=('Consolas', 9))
        scrollbar = ttk.Scrollbar(sys_info_frame, orient="vertical", command=self.system_text.yview)
        self.system_text.configure(yscrollcommand=scrollbar.set)
        self.system_text.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
        scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
        
        # Вкладка статуса
        status_frame = ttk.Frame(notebook, padding="10")
        notebook.add(status_frame, text="Статус VRChat")
        
        # Статус
        self.status_frame = ttk.LabelFrame(status_frame, text="Текущий статус для VRChat", padding="10")
        self.status_frame.pack(fill=tk.BOTH, expand=True)
        
        self.status_text = tk.Text(self.status_frame, height=8, width=80, font=('Consolas', 10))
        self.status_text.pack(fill=tk.BOTH, expand=True)
        
        # Примеры вывода
        examples_frame = ttk.LabelFrame(status_frame, text="Примеры вывода в игру", padding="10")
        examples_frame.pack(fill=tk.X, pady=10)
        
        examples = """Примеры статусов в VRChat:
🎮 Firefox | 🎵 Alan Walker - Faded | ⚙️ CPU:15% RAM:45% GPU:28%
🎮 VLC | 🎵 Слушаю VLC | ⚙️ CPU:8% RAM:32% GPU:5%
🎮 Рабочий стол | 🎵 Музыка не играет | ⚙️ CPU:2% RAM:25% GPU:1%
🎮 Blender | 🎵 YouTube Music | ⚙️ CPU:85% RAM:78% GPU:95%"""
        
        ttk.Label(examples_frame, text=examples, justify=tk.LEFT, font=('Consolas', 8)).pack(anchor=tk.W)
        
        # Управление
        control_frame = ttk.Frame(main_frame)
        control_frame.pack(pady=10)
        
        self.start_button = ttk.Button(control_frame, text="Запустить авто-обновление", 
                                      command=self.toggle_auto_update)
        self.start_button.pack(side=tk.LEFT, padx=5)
        
        ttk.Button(control_frame, text="Обновить сейчас", 
                  command=self.manual_update).pack(side=tk.LEFT, padx=5)
        
        ttk.Button(control_frame, text="Тест OSC", 
                  command=self.test_osc).pack(side=tk.LEFT, padx=5)
        
        ttk.Button(control_frame, text="Обновить системную инфо", 
                  command=self.update_system_display).pack(side=tk.LEFT, padx=5)
        
        # Лог
        log_frame = ttk.LabelFrame(main_frame, text="Лог событий", padding="10")
        log_frame.pack(fill=tk.BOTH, expand=True)
        
        self.log_text = tk.Text(log_frame, height=8, width=80, font=('Consolas', 9))
        log_scrollbar = ttk.Scrollbar(log_frame, orient="vertical", command=self.log_text.yview)
        self.log_text.configure(yscrollcommand=log_scrollbar.set)
        self.log_text.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
        log_scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
        
        self.update_display()
        self.update_system_display()
        self.log("Система запущена. Проверьте настройки.")
    
    def log(self, message):
        self.log_text.insert(tk.END, f"{time.strftime('%H:%M:%S')} - {message}\n")
        self.log_text.see(tk.END)
    
    def update_display(self):
        status = self.system.format_status()
        self.status_text.delete(1.0, tk.END)
        self.status_text.insert(1.0, status)
    
    def update_system_display(self):
        try:
            system_info = self.system.get_system_info()
            display_text = ""
            
            # CPU
            if 'cpu' in system_info:
                cpu = system_info['cpu']
                display_text += f"=== CPU ===\n"
                display_text += f"Использование: {cpu['usage']:.1f}%\n"
                display_text += f"Частота: {cpu['freq']}\n"
                display_text += f"Ядра: {cpu['cores']} / Потоки: {cpu['threads']}\n\n"
            
            # Память
            if 'memory' in system_info:
                mem = system_info['memory']
                display_text += f"=== Память ===\n"
                display_text += f"Использовано: {mem['used']}GB / {mem['total']}GB ({mem['percent']:.1f}%)\n\n"
            
            # GPU
            if 'gpu' in system_info and system_info['gpu']:
                gpu = system_info['gpu']
                display_text += f"=== GPU ===\n"
                display_text += f"Модель: {gpu['name']}\n"
                display_text += f"Использование: {gpu['usage']:.1f}%\n"
                display_text += f"Память: {gpu['memory_used']:.1f}GB / {gpu['memory_total']:.1f}GB\n"
                display_text += f"Температура: {gpu['temperature']:.1f}°C\n\n"
            
            # Диски
            if 'disks' in system_info and system_info['disks']:
                display_text += f"=== Диски ===\n"
                for disk in system_info['disks'][:3]:  # Показываем первые 3 диска
                    display_text += f"{disk['mountpoint']}: {disk['used']}GB / {disk['total']}GB ({disk['percent']:.1f}%)\n"
                display_text += "\n"
            
            # Сеть
            if 'network' in system_info:
                net = system_info['network']
                display_text += f"=== Сеть ===\n"
                display_text += f"Отправлено: {net['bytes_sent']}MB\n"
                display_text += f"Получено: {net['bytes_recv']}MB\n"
            
            self.system_text.delete(1.0, tk.END)
            self.system_text.insert(1.0, display_text)
            self.last_system_update = time.time()
            
        except Exception as e:
            self.log(f"Ошибка обновления системной информации: {e}")
    
    def save_settings(self):
        self.system.config.update({
            'show_window': self.show_window.get(),
            'show_music': self.show_music.get(),
            'show_song_details': self.show_song_details.get(),
            'show_system_info': self.show_system_info.get(),
            'show_cpu_usage': self.show_cpu_usage.get(),
            'show_gpu_usage': self.show_gpu_usage.get(),
            'show_ram_usage': self.show_ram_usage.get(),
            'max_length': int(self.length_var.get()),
            'update_interval': float(self.interval_var.get()),
            'system_update_interval': float(self.system_interval_var.get())
        })
        self.system.save_config()
    
    def manual_update(self):
        self.save_settings()
        status = self.system.format_status()
        success = self.system.send_to_vrchat(status)
        self.update_display()
        if success:
            self.log("Статус обновлен вручную")
        else:
            self.log("Ошибка отправки статуса")
    
    def test_osc(self):
        try:
            self.system.send_to_vrchat("✅ OSC соединение работает! Проверка системы...")
            self.log("Тест OSC: отправлено сообщение")
        except Exception as e:
            self.log(f"Ошибка OSC: {e}")
            messagebox.showerror("Ошибка", f"Не удалось отправить OSC: {e}")
    
    def auto_update_loop(self):
        while self.is_running:
            try:
                self.save_settings()
                
                # Обновляем системную информацию реже
                current_time = time.time()
                if current_time - self.last_system_update >= self.system.config['system_update_interval']:
                    self.root.after(0, self.update_system_display)
                
                # Отправляем статус
                status = self.system.format_status()
                success = self.system.send_to_vrchat(status)
                
                self.root.after(0, self.update_display)
                if success:
                    self.root.after(0, lambda: self.log("Авто-обновление"))
                else:
                    self.root.after(0, lambda: self.log("Ошибка отправки"))
                
            except Exception as e:
                self.root.after(0, lambda: self.log(f"Ошибка: {e}"))
            
            time.sleep(self.system.config['update_interval'])
    
    def toggle_auto_update(self):
        if not self.is_running:
            self.is_running = True
            self.start_button.config(text="Остановить авто-обновление")
            self.update_thread = threading.Thread(target=self.auto_update_loop, daemon=True)
            self.update_thread.start()
            self.log("Авто-обновление запущено")
        else:
            self.is_running = False
            self.start_button.config(text="Запустить авто-обновление")
            self.log("Авто-обновление остановлено")
    
    def on_closing(self):
        self.is_running = False
        self.system.save_config()
        self.root.destroy()

if __name__ == "__main__":
    # Проверка дополнительных зависимостей
    try:
        import psutil
        import GPUtil
    except ImportError:
        print("Установите дополнительные зависимости:")
        print("pip install psutil GPUtil")
        exit(1)
    
    def check_dependency(cmd):
        try:
            subprocess.run(cmd, capture_output=True, timeout=1)
            return True
        except:
            return False
    
    dependencies = {
        'xdotool': ['xdotool', '--version'],
        'playerctl': ['playerctl', '--version'],
        'pactl': ['pactl', '--version']
    }
    
    missing = []
    for name, cmd in dependencies.items():
        if not check_dependency(cmd):
            missing.append(name)
    
    if missing:
        print(f"ВНИМАНИЕ: Отсутствуют зависимости: {', '.join(missing)}")
        print("Установите их:")
        print("sudo pacman -S", " ".join(missing))
        print("Программа может работать неполноценно.")
    
    app = LinuxStatusGUI()
    app.root.mainloop()

