#!/usr/bin/env python3
"""
SchoolFucker v2.4

Corrected flow:
- Double-clicking the .exe → automatically runs install
- install = only sets up dependencies + PATH (no config/startup)
- evaluate = finds best config + starts everything
"""

import argparse
import json
import os
import shutil
import subprocess
import sys
import time
import urllib.parse
import zipfile
from pathlib import Path

import requests

# ==================== CONFIG ====================
APP_NAME = "SchoolFucker"
VERSION = "2.4.0"

INSTALL_DIR = Path(os.getenv("LOCALAPPDATA")) / APP_NAME
XRAY_DIR = INSTALL_DIR / "xray"
CONFIG_DIR = INSTALL_DIR / "configs"
WORKING_CONFIG = CONFIG_DIR / "best.json"
SOURCE_FILE = CONFIG_DIR / "source.txt"
XRAY_EXE = XRAY_DIR / "xray.exe"

# Update check domain (change this to your Cloudflare Pages domain later)
UPDATE_CHECK_DOMAIN = "schoolfucker.pages.dev"

DEFAULT_SUB_URL = "https://raw.githubusercontent.com/Epodonios/v2ray-configs/raw/main/All_Configs_Sub.txt"

TEST_URL = "https://poki.com"
LOCAL_PORT = 10808
MAX_EVALUATE = 30
# ===============================================


def print_status(msg): print(f"[*] {msg}")
def print_success(msg): print(f"[+] {msg}")
def print_error(msg): print(f"[-] {msg}")
def print_warning(msg): print(f"[!] {msg}")

def print_banner():
    print(r"""
   _____      _                 _ ______          _             
  / ____|    | |               | |  ____|        | |            
 | (___   ___| |__   ___   ___ | | |__ _   _  ___| | _____ _ __ 
  \___ \ / __| '_ \ / _ \ / _ \| |  __| | | |/ __| |/ / _ \ '__|
  ____) | (__| | | | (_) | (_) | | |  | |_| | (__|   <  __/ |   
 |_____/ \___|_| |_|\___/ \___/|_|_|   \__,_|\___|_|\_\___|_|   
    """)
    print("SchoolFucker v2.3  •  Bypass school & enterprise firewalls\n")


def ensure_dirs():
    INSTALL_DIR.mkdir(parents=True, exist_ok=True)
    XRAY_DIR.mkdir(parents=True, exist_ok=True)
    CONFIG_DIR.mkdir(parents=True, exist_ok=True)


def get_config_source():
    if SOURCE_FILE.exists():
        content = SOURCE_FILE.read_text().strip()
        return content if content and content != "default" else None
    return None


def set_config_source(url: str):
    SOURCE_FILE.write_text(url.strip())
    print_success("Custom config source set.")


def reset_config_source():
    if SOURCE_FILE.exists(): SOURCE_FILE.unlink()
    if WORKING_CONFIG.exists(): WORKING_CONFIG.unlink()
    print_success("Reset to default.")


def get_config_urls():
    custom = get_config_source()
    return [custom] if custom else [DEFAULT_SUB_URL]


def is_frozen():
    return getattr(sys, 'frozen', False)


def get_self_path():
    return Path(sys.executable) if is_frozen() else Path(__file__).resolve()


def kill_xray():
    subprocess.run(["taskkill", "/IM", "xray.exe", "/F"], capture_output=True)
    time.sleep(1)


def parse_link(link: str):
    if not link.startswith(("trojan://", "vless://")): return None
    try:
        link = link.split("#")[0]
        p = urllib.parse.urlparse(link)
        q = urllib.parse.parse_qs(p.query)
        proto = "trojan" if link.startswith("trojan://") else "vless"
        return {
            "protocol": proto, "address": p.hostname, "port": p.port or 443,
            "password": p.username or "",
            "sni": q.get("sni", [p.hostname])[0],
            "fp": q.get("fp", ["chrome"])[0],
            "alpn": q.get("alpn", ["h2,http/1.1"])[0].split(","),
        }
    except:
        return None


def build_config(data):
    return {
        "log": {"loglevel": "warning"},
        "inbounds": [{"port": LOCAL_PORT, "listen": "127.0.0.1", "protocol": "socks",
                      "settings": {"auth": "noauth", "udp": True}}],
        "outbounds": [{
            "protocol": data["protocol"],
            "settings": {"servers": [{"address": data["address"], "port": data["port"], "password": data["password"]}]},
            "streamSettings": {
                "network": "tcp", "security": "tls",
                "tlsSettings": {"serverName": data["sni"], "fingerprint": data.get("fp", "chrome"),
                                "alpn": data.get("alpn", ["h2", "http/1.1"])}
            }
        }]
    }


def test_config(data, timeout=8):
    cfg = build_config(data)
    tmp = CONFIG_DIR / "test_temp.json"
    tmp.write_text(json.dumps(cfg))
    kill_xray()
    proc = subprocess.Popen([str(XRAY_EXE), "run", "-config", str(tmp)],
                            stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL,
                            creationflags=subprocess.CREATE_NO_WINDOW)
    time.sleep(3)
    proxies = {"http": f"http://127.0.0.1:{LOCAL_PORT}", "https": f"http://127.0.0.1:{LOCAL_PORT}"}
    start = time.time()
    try:
        r = requests.get(TEST_URL, proxies=proxies, timeout=timeout)
        latency = time.time() - start
        # More robust check: status 200 + some content
        success = r.status_code == 200 and len(r.text) > 500
    except:
        success, latency = False, 999
    proc.terminate()
    tmp.unlink(missing_ok=True)
    return success, latency


def find_best_config():
    urls = get_config_urls()
    source = "Custom" if get_config_source() else "Default"
    print_status(f"Using {source} source...")

    all_links = []
    for url in urls:
        try:
            r = requests.get(url, timeout=25)
            all_links.extend([l.strip() for l in r.text.splitlines()
                              if l.strip().startswith(("trojan://", "vless://"))])
        except: continue

    if not all_links:
        print_error("No configs found.")
        return False

    print_status(f"Testing up to {min(len(all_links), MAX_EVALUATE)} configs...")
    results = []
    for i, link in enumerate(all_links[:MAX_EVALUATE], 1):
        data = parse_link(link)
        if not data: continue
        print_status(f"[{i}] Testing {data['address']}...")
        success, latency = test_config(data)
        if success:
            results.append((latency, data))
            print_success(f"   Working ({latency:.2f}s)")

    if not results:
        print_error("No working configs found.")
        return False

    results.sort(key=lambda x: x[0])
    best_latency, best_data = results[0]
    print_success(f"Best: {best_data['protocol']} @ {best_data['address']} ({best_latency:.2f}s)")
    WORKING_CONFIG.write_text(json.dumps(build_config(best_data), indent=2))
    return True


def cleanup_old_installation():
    """Remove old traces before installing"""
    print_status("Checking for old installations...")
    try:
        if INSTALL_DIR.exists():
            print_warning("Found old installation. Cleaning up...")
            kill_xray()
            shutil.rmtree(INSTALL_DIR, ignore_errors=True)
            print_success("Old installation removed.")
    except Exception as e:
        print_warning(f"Could not fully clean old installation: {e}")


def perform_install():
    """Install only handles dependencies + PATH. No auto config/startup."""
    print_status("Starting SchoolFucker installation...\n")

    cleanup_old_installation()
    ensure_dirs()

    # Download Xray
    if not XRAY_EXE.exists():
        print_status("Downloading Xray-core (this may take a moment)...")
        if download_xray():
            print_success("Xray-core downloaded successfully.")
        else:
            print_error("Failed to download Xray-core.")
            input("\nPress Enter to exit...")
            return False

    # Self copy
    self_path = get_self_path()
    target = INSTALL_DIR / "SchoolFucker.exe"
    if self_path != target:
        try:
            shutil.copy2(self_path, target)
            print_success(f"Installed to: {target}")
        except Exception as e:
            print_warning(f"Self-copy failed: {e}")

    # Add to PATH (case-insensitive check + update current session)
    path_added = False
    folder = str(INSTALL_DIR)

    try:
        import winreg
        key = winreg.OpenKey(winreg.HKEY_CURRENT_USER, r"Environment", 0, winreg.KEY_READ | winreg.KEY_WRITE)
        current, _ = winreg.QueryValueEx(key, "Path")

        # Case-insensitive check
        if folder.lower() not in current.lower():
            new_path = f"{current};{folder}" if current else folder
            winreg.SetValueEx(key, "Path", 0, winreg.REG_EXPAND_SZ, new_path)
            print_success("Added to user PATH in registry.")
            path_added = True
        else:
            print_status("Path already exists in registry (case-insensitive check).")
            path_added = True

        winreg.CloseKey(key)

        # Also update current process PATH so it works immediately in this session
        if folder not in os.environ.get("PATH", ""):
            os.environ["PATH"] = os.environ.get("PATH", "") + ";" + folder

    except Exception as e:
        print_error(f"Failed to update PATH: {e}")
        print_warning("Try restarting your computer or add it manually.")

    print("\n" + "="*50)
    if path_added:
        print_success("SchoolFucker installed successfully!")
        print("→ Close and reopen PowerShell/Terminal, then try:")
        print("   SchoolFucker evaluate")
        print("\n   If it still doesn't work, restart your PC (common on school laptops).")
    else:
        print_warning("Installation finished with PATH issues.")
        print(f"→ You can run it directly with:")
        print(f"   {target} evaluate")
    print("="*50)

    input("\nPress Enter to close this window...")
    return True


def download_xray():
    try:
        r = requests.get("https://api.github.com/repos/XTLS/Xray-core/releases/latest", timeout=30)
        for asset in r.json().get("assets", []):
            if "windows" in asset["name"].lower() and "64" in asset["name"] and asset["name"].endswith(".zip"):
                zip_path = INSTALL_DIR / asset["name"]
                with requests.get(asset["browser_download_url"], stream=True, timeout=180) as resp:
                    with open(zip_path, "wb") as f:
                        shutil.copyfileobj(resp.raw, f)
                with zipfile.ZipFile(zip_path) as z:
                    z.extractall(XRAY_DIR)
                zip_path.unlink(missing_ok=True)
                return True
    except:
        pass
    return False


def create_startup_entry():
    startup = Path(os.getenv("APPDATA")) / r"Microsoft\Windows\Start Menu\Programs\Startup"
    startup.mkdir(parents=True, exist_ok=True)
    (startup / "SchoolFucker.bat").write_text(
        f'@echo off\ncd /d "{XRAY_DIR}"\nstart "" /min "{XRAY_EXE}" run -config "{WORKING_CONFIG}"'
    )


def start_xray(silent=False):
    if not WORKING_CONFIG.exists():
        print_error("No config. Run evaluate first.")
        return
    kill_xray()
    flags = subprocess.CREATE_NO_WINDOW if silent else 0
    subprocess.Popen([str(XRAY_EXE), "run", "-config", str(WORKING_CONFIG)],
                     stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, creationflags=flags)
    if not silent:
        print_success(f"Started on port {LOCAL_PORT}")


def evaluate_command(silent=False):
    ensure_dirs()
    if not XRAY_EXE.exists():
        print_error("Xray not installed. Please run the exe at least once first.")
        return
    if find_best_config():
        start_xray(silent=silent)
        try:
            import winreg
            key = winreg.OpenKey(winreg.HKEY_CURRENT_USER,
                                 r"Software\Microsoft\Windows\CurrentVersion\Internet Settings", 0, winreg.KEY_WRITE)
            winreg.SetValueEx(key, "ProxyEnable", 0, winreg.REG_DWORD, 1)
            winreg.SetValueEx(key, "ProxyServer", 0, winreg.REG_SZ, f"127.0.0.1:{LOCAL_PORT}")
            winreg.CloseKey(key)
            if not silent: print_success("System proxy enabled automatically")
        except:
            pass


def check_update():
    print_status("Checking for updates...")
    try:
        r = requests.get(GITHUB_API, timeout=15)
        latest = r.json().get("tag_name", "")
        if latest and latest != f"v{VERSION}":
            print_warning(f"New version available: {latest}")
        else:
            print_success("You are on the latest version.")
    except:
        print_warning("Could not check for updates.")


def uninstall_command():
    print_warning("This will completely remove SchoolFucker and all traces.")
    if input("Type 'yes' to continue: ").strip().lower() != "yes":
        print_status("Cancelled.")
        return

    kill_xray()

    # Remove startup
    try:
        bat = Path(os.getenv("APPDATA")) / r"Microsoft\Windows\Start Menu\Programs\Startup\SchoolFucker.bat"
        if bat.exists(): bat.unlink()
    except: pass

    # Remove from PATH
    try:
        import winreg
        key = winreg.OpenKey(winreg.HKEY_CURRENT_USER, r"Environment", 0, winreg.KEY_READ | winreg.KEY_WRITE)
        current, _ = winreg.QueryValueEx(key, "Path")
        folder = str(INSTALL_DIR)
        if folder.lower() in current.lower():
            new_path = ";".join([p for p in current.split(";") if p.strip().lower() != folder.lower()])
            winreg.SetValueEx(key, "Path", 0, winreg.REG_EXPAND_SZ, new_path)
    except: pass

    # Disable proxy
    try:
        import winreg
        key = winreg.OpenKey(winreg.HKEY_CURRENT_USER, r"Software\Microsoft\Windows\CurrentVersion\Internet Settings", 0, winreg.KEY_WRITE)
        winreg.SetValueEx(key, "ProxyEnable", 0, winreg.REG_DWORD, 0)
        winreg.CloseKey(key)
    except: pass

    # Delete installed folder (includes Xray + configs)
    try:
        if INSTALL_DIR.exists():
            shutil.rmtree(INSTALL_DIR, ignore_errors=True)
            print_success("Removed SchoolFucker folder (including Xray).")
    except: pass

    print("\n" + "="*50)
    print_success("SchoolFucker has been uninstalled.")
    print_warning("Manually delete the original SchoolFucker.exe file if it still exists.")
    print("Common locations: Downloads folder or Desktop.")
    print("="*50)


def handle_config_command(action=None, url=None):
    if action == "reset":
        reset_config_source()
    elif action == "show" or (action is None and url is None):
        current = get_config_source()
        if current:
            print(f"Current config source: {current}")
        else:
            print(f"Current config source: {DEFAULT_SUB_URL} (default)")
    elif url:
        set_config_source(url)
    else:
        print(f"Current source: {get_config_source() or DEFAULT_SUB_URL}")


def main():
    print_banner()
    parser = argparse.ArgumentParser(description="SchoolFucker")
    sub = parser.add_subparsers(dest="cmd")

    sub.add_parser("install", help="Install dependencies and add to PATH")
    sub.add_parser("start", help="Start Xray")
    sub.add_parser("stop", help="Stop Xray")
    sub.add_parser("update", help="Check for updates")
    sub.add_parser("uninstall", help="Completely remove SchoolFucker")
    sub.add_parser("status", help="Show status")

    config_p = sub.add_parser("config", help="Manage custom config source")
    config_p.add_argument("action", nargs="?", help="'reset' or subscription URL")
    config_p.add_argument("url", nargs="?", help="Custom subscription URL")

    eval_p = sub.add_parser("evaluate", help="Find best config + start + enable proxy")
    eval_p.add_argument("--silent", action="store_true", help="Run in background")

    args = parser.parse_args()

    # No arguments → show help
    if args.cmd is None:
        parser.print_help()
        return

    ensure_dirs()

    if args.cmd == "install":
        perform_install()
    elif args.cmd == "evaluate":
        evaluate_command(silent=getattr(args, "silent", False))
    elif args.cmd == "start":
        start_xray()
    elif args.cmd == "stop":
        kill_xray()
    elif args.cmd == "update":
        check_update()
    elif args.cmd == "uninstall":
        uninstall_command()
    elif args.cmd == "status":
        print(f"SchoolFucker v{VERSION}")
        current = get_config_source()
        if current:
            print(f"Config source: {current}")
        else:
            print(f"Config source: {DEFAULT_SUB_URL} (default)")
        print(f"Working config exists: {WORKING_CONFIG.exists()}")
    elif args.cmd == "config":
        handle_config_command(args.action, args.url)


if __name__ == "__main__":
    main()