#!/usr/bin/env python3
"""
Minecraft Username Sniper GUI
- Multiple OAuth tokens support
- Namemc (windowed timing) + Labynames (exact timing) sections
- High-precision timing with multi-threading
"""

import sys
import time
import httpx
import threading
import json
import gc
from datetime import datetime, timezone
from PyQt5.QtWidgets import (
    QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout,
    QTabWidget, QLabel, QLineEdit, QTextEdit, QPushButton, QSpinBox,
    QGroupBox, QFormLayout, QMessageBox, QCheckBox, QFileDialog
)
from PyQt5.QtCore import Qt, QTimer, pyqtSignal
from PyQt5.QtGui import QFont, QColor

# === Disable garbage collection for precision timing ===
gc.disable()


class SniperWorker(threading.Thread):
    """Worker thread for firing concurrent requests"""
    log_signal = pyqtSignal(str)
    
    def __init__(self, name, tokens, drop_time_utc, threads_count, timing_mode="exact"):
        super().__init__()
        self.name = name
        self.tokens = tokens
        self.drop_time_utc = drop_time_utc
        self.threads_count = threads_count
        self.timing_mode = timing_mode  # "exact" or "windowed"
        self.stop_event = threading.Event()
    
    def run(self):
        """Main sniper logic"""
        self.log_signal.emit(f"[*] Starting sniper for: {self.name}")
        self.log_signal.emit(f"[*] Mode: {self.timing_mode}, Tokens: {len(self.tokens)}, Threads: {self.threads_count}")
        
        if self.timing_mode == "exact":
            self._exact_timing_snipe()
        else:
            self._windowed_timing_snipe()
    
    def _exact_timing_snipe(self):
        """Fire all requests at exact drop time"""
        try:
            target = datetime.fromisoformat(self.drop_time_utc.replace("Z", "+00:00"))
            target_ts = target.timestamp()
            
            self.log_signal.emit(f"[*] Waiting for exact drop time: {self.drop_time_utc} UTC")
            
            # High-precision wait loop
            while not self.stop_event.is_set():
                now = time.time()
                delta = target_ts - now
                
                if delta <= 0.0005:  # Trigger ~0.5ms before
                    break
                elif delta > 1:
                    time.sleep(delta - 0.5)
                elif delta > 0.1:
                    time.sleep(0.05)
                else:
                    time.sleep(0.0005)
            
            if self.stop_event.is_set():
                self.log_signal.emit("[!] Sniper stopped by user")
                return
            
            self.log_signal.emit(f"[!] 🎯 FIRE! Dropping at exact time!")
            self._fire_all_tokens()
            
        except Exception as e:
            self.log_signal.emit(f"[ERROR] Timing error: {e}")
    
    def _windowed_timing_snipe(self):
        """Fire requests in a window (e.g., every 1-3 seconds around drop time)"""
        try:
            target = datetime.fromisoformat(self.drop_time_utc.replace("Z", "+00:00"))
            target_ts = target.timestamp()
            
            self.log_signal.emit(f"[*] Waiting for windowed drop time: {self.drop_time_utc} UTC")
            
            # Wait until just before the window
            while not self.stop_event.is_set():
                now = time.time()
                delta = target_ts - now
                
                if delta <= 0:
                    break
                elif delta > 1:
                    time.sleep(delta - 1)
                else:
                    time.sleep(0.1)
            
            if self.stop_event.is_set():
                self.log_signal.emit("[!] Sniper stopped by user")
                return
            
            self.log_signal.emit(f"[!] 🎯 Window started! Firing every 1-3 seconds...")
            
            # Fire in windowed mode (every 1-3 seconds)
            import random
            window_duration = 60  # Fire for 60 seconds in windowed mode
            
            start_window = time.time()
            while (time.time() - start_window) < window_duration and not self.stop_event.is_set():
                self._fire_all_tokens()
                wait_time = random.uniform(1.0, 3.0)
                self.log_signal.emit(f"[*] Next burst in {wait_time:.1f}s...")
                
                # Check stop event during wait
                for _ in range(int(wait_time * 10)):
                    if self.stop_event.is_set():
                        break
                    time.sleep(0.1)
            
            self.log_signal.emit("[!] Windowed snipe complete")
            
        except Exception as e:
            self.log_signal.emit(f"[ERROR] Windowed timing error: {e}")
    
    def _fire_all_tokens(self):
        """Fire concurrent requests with all tokens"""
        threads_list = []
        
        for i, token in enumerate(self.tokens):
            for _ in range(self.threads_count):
                t = threading.Thread(
                    target=self._send_request_with_token,
                    args=(token,)
                )
                t.start()
                threads_list.append(t)
        
        # Small delay between token bursts for better control
        time.sleep(0.1)
        
        for t in threads_list:
            t.join()
    
    def _send_request_with_token(self, token):
        """Send request with specific token"""
        url = f"https://api.minecraftservices.com/minecraft/profile/name/{self.name}"
        
        client = httpx.Client(
            http2=True,
            timeout=httpx.Timeout(5.0, connect=2.0),
            headers={
                "Authorization": f"Bearer {token}",
                "Content-Type": "application/json"
            },
        )
        
        timestamp = datetime.now(timezone.utc).isoformat()
        
        try:
            r = client.put(url, json={})
            try:
                body = r.json()
            except:
                body = r.text
            
            status = "✅ SUCCESS" if r.status_code in [200, 201, 204] else "❌ FAILED"
            self.log_signal.emit(f"[{timestamp}] Token {hash(token) % 10000}: {status} - {r.status_code}")
            
        except httpx.TimeoutException:
            self.log_signal.emit(f"[{timestamp}] TIMEOUT - Token {hash(token) % 10000}")
        except Exception as e:
            self.log_signal.emit(f"[{timestamp}] ERROR - {str(e)[:50]}")
        finally:
            client.close()


class SniperGUI(QMainWindow):
    """Main GUI application"""
    
    def __init__(self):
        super().__init__()
        self.workers = []
        self.init_ui()
    
    def init_ui(self):
        """Initialize the user interface"""
        self.setWindowTitle("Minecraft Username Sniper GUI v8")
        self.setGeometry(100, 100, 900, 700)
        
        # Main widget and layout
        main_widget = QWidget()
        self.setCentralWidget(main_widget)
        main_layout = QVBoxLayout(main_widget)
        
        # Title
        title = QLabel("⚡ Minecraft Username Sniper")
        title.setFont(QFont("Arial", 18, QFont.Bold))
        title.setAlignment(Qt.AlignCenter)
        main_layout.addWidget(title)
        
        # Tabs for different sniper sections
        tabs = QTabWidget()
        main_layout.addWidget(tabs)
        
        # === NAMEMC Tab (Windowed Timing) ===
        namemc_tab = self._create_sniper_tab("NAMEMC (Windowed)", timing_mode="windowed")
        tabs.addTab(namemc_tab, "📅 NAMEMC")
        
        # === LABYNAMES Tab (Exact Timing) ===
        labynames_tab = self._create_sniper_tab("LABYNAMES (Exact)", timing_mode="exact")
        tabs.addTab(labynames_tab, "⏰ LABYNAMES")
        
        # === Log Output ===
        log_group = QGroupBox("📋 Live Log")
        log_layout = QVBoxLayout()
        self.log_output = QTextEdit()
        self.log_output.setReadOnly(True)
        self.log_output.setFont(QFont("Consolas", 9))
        log_layout.addWidget(self.log_output)
        
        clear_btn = QPushButton("🗑️ Clear Log")
        clear_btn.clicked.connect(lambda: self.log_output.clear())
        log_layout.addWidget(clear_btn)
        
        log_group.setLayout(log_layout)
        main_layout.addWidget(log_group)
    
    def _create_sniper_tab(self, title, timing_mode):
        """Create a sniper configuration tab"""
        tab = QWidget()
        layout = QVBoxLayout(tab)
        
        # Title label
        title_label = QLabel(title)
        title_label.setFont(QFont("Arial", 12, QFont.Bold))
        layout.addWidget(title_label)
        
        # Username input
        username_layout = QHBoxLayout()
        username_layout.addWidget(QLabel("Username to Snipe:"))
        self.username_input = QLineEdit()
        self.username_input.setPlaceholderText("Enter username (e.g., Eternal)")
        username_layout.addWidget(self.username_input)
        layout.addLayout(username_layout)
        
        # Drop time input
        time_layout = QHBoxLayout()
        time_layout.addWidget(QLabel("Drop Time (UTC):"))
        self.drop_time_input = QLineEdit()
        self.drop_time_input.setPlaceholderText("YYYY-MM-DDTHH:MM:SS.000Z (e.g., 2025-09-11T00:18:47.000Z)")
        time_layout.addWidget(self.drop_time_input)
        layout.addLayout(time_layout)
        
        # Tokens input (multi-line)
        tokens_group = QGroupBox("🔑 OAuth Tokens (one per line)")
        tokens_layout = QVBoxLayout()
        self.tokens_input = QTextEdit()
        self.tokens_input.setPlaceholderText("Paste tokens here, one per line...\nToken1\nToken2\nToken3")
        self.tokens_input.setFont(QFont("Consolas", 9))
        tokens_layout.addWidget(self.tokens_input)
        tokens_group.setLayout(tokens_layout)
        layout.addWidget(tokens_group)
        
        # Thread count
        threads_layout = QHBoxLayout()
        threads_layout.addWidget(QLabel("Threads per Token:"))
        self.threads_spin = QSpinBox()
        self.threads_spin.setRange(1, 100)
        self.threads_spin.setValue(10)
        threads_layout.addWidget(self.threads_spin)
        threads_layout.addStretch()
        layout.addLayout(threads_layout)
        
        # Start/Stop buttons
        buttons_layout = QHBoxLayout()
        
        self.start_btn = QPushButton("🚀 START SNIPE")
        self.start_btn.setFont(QFont("Arial", 10, QFont.Bold))
        self.start_btn.setStyleSheet("background-color: #4CAF50; color: white; padding: 10px;")
        self.start_btn.clicked.connect(lambda: self.start_sniper(timing_mode))
        buttons_layout.addWidget(self.start_btn)
        
        self.stop_btn = QPushButton("⏹️ STOP")
        self.stop_btn.setStyleSheet("background-color: #f44336; color: white; padding: 10px;")
        self.stop_btn.clicked.connect(self.stop_sniper)
        buttons_layout.addWidget(self.stop_btn)
        
        load_tokens_btn = QPushButton("📂 Load Tokens")
        load_tokens_btn.clicked.connect(self.load_tokens_from_file)
        buttons_layout.addWidget(load_tokens_btn)
        
        layout.addLayout(buttons_layout)
        
        # Store timing mode in tab
        tab.timing_mode = timing_mode
        
        return tab
    
    def _log(self, message):
        """Add message to log output"""
        timestamp = datetime.now().strftime("%H:%M:%S")
        self.log_output.append(f"[{timestamp}] {message}")
        self.log_output.verticalScrollBar().setValue(self.log_output.verticalScrollBar().maximum())
    
    def start_sniper(self, timing_mode):
        """Start the sniper with current settings"""
        # Get current tab's inputs
        current_tab = self.centralWidget().findChild(QTabWidget()).currentWidget()
        
        # Find the inputs from the active tab
        username_input = current_tab.findChild(QLineEdit(), "username_input") or self.username_input
        drop_time_input = current_tab.findChild(QLineEdit(), "drop_time_input") or self.drop_time_input
        tokens_input = current_tab.findChild(QTextEdit(), "tokens_input") or self.tokens_input
        
        username = username_input.text().strip()
        drop_time = drop_time_input.text().strip()
        tokens_text = tokens_input.toPlainText()
        threads_count = self.threads_spin.value()
        
        # Validate inputs
        if not username:
            QMessageBox.warning(self, "Error", "Please enter a username to snipe!")
            return
        
        if not drop_time:
            QMessageBox.warning(self, "Error", "Please enter a drop time (UTC)!")
            return
        
        tokens = [t.strip() for t in tokens_text.split('\n') if t.strip()]
        if not tokens:
            QMessageBox.warning(self, "Error", "Please enter at least one OAuth token!")
            return
        
        # Stop any existing workers
        self.stop_sniper()
        
        # Create and start worker
        self._log(f"🚀 Starting {timing_mode} sniper...")
        worker = SniperWorker(username, tokens, drop_time, threads_count, timing_mode)
        worker.log_signal.connect(self._log)
        worker.start()
        self.workers.append(worker)
        
        self._log(f"✅ Sniper started! Username: {username}, Tokens: {len(tokens)}, Threads: {threads_count}")
    
    def stop_sniper(self):
        """Stop all running snipers"""
        self._log("⏹️ Stopping all snipers...")
        for worker in self.workers:
            worker.stop_event.set()
            worker.join(timeout=2)
        self.workers = []
        self._log("✅ All snipers stopped")
    
    def load_tokens_from_file(self):
        """Load tokens from a text file"""
        file_path, _ = QFileDialog.getOpenFileName(self, "Load Tokens", "", "Text Files (*.txt)")
        if file_path:
            try:
                with open(file_path, 'r') as f:
                    tokens = f.read()
                self.tokens_input.setText(tokens)
                self._log(f"✅ Loaded tokens from {file_path}")
            except Exception as e:
                QMessageBox.critical(self, "Error", f"Failed to load tokens: {e}")


def main():
    app = QApplication(sys.argv)
    
    # Set application font
    font = QFont("Arial", 10)
    app.setFont(font)
    
    window = SniperGUI()
    window.show()
    
    sys.exit(app.exec_())


if __name__ == "__main__":
    main()
