#!/usr/bin/env python3
"""Small dependency-free ZellnsVPN Linux desktop client with capability-gated protocols."""
import json, os, pathlib, subprocess, threading, tkinter as tk, webbrowser
from tkinter import messagebox, ttk
from urllib.request import Request, urlopen

API=os.environ.get("ZELLNS_API","https://vpn.zellns.online/api/index.php")
STORE=pathlib.Path.home()/".config"/"zellnsvpn"; SESSION=STORE/"session.json"; PROFILE=STORE/"zellnsvpn.conf"

class App:
    def __init__(self,root):
        self.root=root;root.title("ZellnsVPN 3.3.0");root.minsize(520,620);root.geometry("620x760");root.configure(bg="#06111f")
        self.token="";self.uuid="";self.status=tk.StringVar(value="Odpojeno");self.protocol=tk.StringVar(value="WireGuard");self.protocol_ids={"WireGuard":"wireguard"}
        self.frame=tk.Frame(root,bg="#06111f",padx=38,pady=32);self.frame.pack(fill="both",expand=True)
        self.load();self.login_view() if not self.token else self.dashboard()
    def clear(self):
        for w in self.frame.winfo_children():w.destroy()
    def label(self,text,size=12,color="#d9e8f5"):
        w=tk.Label(self.frame,text=text,font=("Sans",size),fg=color,bg="#06111f",wraplength=540,justify="left",anchor="w");w.pack(fill="x",pady=5);return w
    def entry(self,secret=False):w=tk.Entry(self.frame,show="*" if secret else "",font=("Sans",12),bg="#10243a",fg="white",insertbackground="white",relief="flat");w.pack(fill="x",ipady=10,pady=6);return w
    def button(self,text,fn):
        w=tk.Button(self.frame,text=text,command=fn,bg="#2addc1",activebackground="#62ead5",fg="#052128",activeforeground="#052128",relief="flat",bd=0,font=("Sans",11,"bold"),pady=12,cursor="hand2");w.pack(fill="x",pady=7);return w
    def nav(self,active="vpn"):
        row=tk.Frame(self.frame,bg="#06111f");row.pack(fill="x",pady=(4,16))
        tabs=(("VPN","vpn",self.dashboard),("E-mail","mail",lambda:self.section_view("mail")),("Focus","focus",lambda:self.section_view("focus")),("Další nástroje","tools",lambda:self.section_view("tools")),("Nastavení","settings",lambda:self.section_view("settings")))
        for text,tab,command in tabs:tk.Button(row,text=text,command=command,bg="#2addc1" if tab==active else "#102a43",fg="#052128" if tab==active else "white",relief="flat",bd=0,padx=10,pady=8,cursor="hand2").pack(side="left",padx=(0,5))
    def section_view(self,tab):
        self.clear();self.label("ZellnsVPN",32,"white");self.nav(tab)
        content={"mail":("E-mail","Správa běžných i dočasných adres, doručených, odeslaných, hromadných zpráv a spamu je v zabezpečeném Zellns portálu."),"focus":("Focus","Plány soustředění a síťový hard-stop se synchronizují s tvým účtem."),"tools":("Další nástroje","Diagnostika, virtuální LAN, historie a další nástroje účtu."),"settings":("Nastavení","Aktualizace, soukromí a DNS adblock profil spravuje Zellns portál.")}[tab]
        self.label(content[0],24,"white");self.label(content[1],12,"#9bb5cc");self.button("Otevřít zabezpečený portál",lambda:webbrowser.open("https://vpn.zellns.online/portal/"));self.button("Zpět na VPN",self.dashboard)
    def request(self,action,**values):
        data=json.dumps({"action":action,**values}).encode();headers={"Content-Type":"application/json"}
        if self.token:headers["Authorization"]="Bearer "+self.token
        with urlopen(Request(API,data=data,headers=headers),timeout=15) as response: result=json.load(response)
        if not result.get("ok"):raise RuntimeError(result.get("error","API error"))
        return result
    def load(self):
        try:d=json.loads(SESSION.read_text());self.token=d.get("token","");self.uuid=d.get("uuid","")
        except Exception:pass
    def save(self):
        STORE.mkdir(parents=True,exist_ok=True);SESSION.write_text(json.dumps({"token":self.token,"uuid":self.uuid}));os.chmod(SESSION,0o600)
    def login_view(self):
        self.clear();self.label("ZellnsVPN",32,"white");self.label("Linux klient · v3.3.0 · ověřované síťové protokoly","12","#86a2bd")
        self.email=self.entry();self.email.insert(0,"E-mail");self.password=self.entry(True);self.button("Přihlásit",lambda:self.async_run(self.login))
    def login(self):
        result=self.request("login",email=self.email.get().strip(),password=self.password.get());self.token=result["token"];self.save();self.root.after(0,self.dashboard)
    def dashboard(self):
        self.clear();self.label("ZellnsVPN",32,"white");self.label("Linux desktop · bezpečné ovládání přes pkexec","12","#86a2bd");self.nav("vpn")
        card=tk.Frame(self.frame,bg="#0d2033",highlightbackground="#28445f",highlightthickness=1,padx=20,pady=18);card.pack(fill="x",pady=(18,14));self.label_var=tk.Label(card,text=self.status.get(),font=("Sans",18,"bold"),fg="#2addc1",bg="#0d2033",anchor="w");self.label_var.pack(fill="x")
        self.label("VPN PROTOKOL",10,"#2addc1");self.protocol_box=ttk.Combobox(self.frame,textvariable=self.protocol,state="readonly",values=["WireGuard"]);self.protocol_box.pack(fill="x",ipady=7,pady=6)
        self.button("Připojit",lambda:self.async_run(self.connect));self.button("Odpojit",lambda:self.async_run(self.disconnect));self.button("Obnovit stav",lambda:self.async_run(self.poll));self.button("Odhlásit",self.logout);self.async_run(self.load_protocols);self.async_run(self.poll)
    def load_protocols(self):
        result=self.request("server_list");items=result.get("protocols",[]);labels=[];ids={}
        for item in items:
            if item.get("id")=="auto":continue
            label=f"{item.get('name',item.get('id'))} · {'aktivní' if item.get('available') else 'server nepřipraven'}";labels.append(label);ids[label]=item.get("id")
        def apply():
            self.protocol_ids=ids or {"WireGuard":"wireguard"};self.protocol_box.configure(values=labels or ["WireGuard"]);self.protocol.set((labels or ["WireGuard"])[0])
        self.root.after(0,apply)
    def ensure_profile(self):
        if PROFILE.exists():return
        private=subprocess.run(["wg","genkey"],check=True,capture_output=True,text=True).stdout.strip();public=subprocess.run(["wg","pubkey"],input=private,check=True,capture_output=True,text=True).stdout.strip()
        import uuid;self.uuid=self.uuid or str(uuid.uuid4());device=self.request("device_enroll",device_uuid=self.uuid,device_name=os.uname().nodename,public_key=public,quick_connect=True)["device"]
        dns=device.get("adBlockDns") or ", ".join(device["dns"]);text=f"[Interface]\nPrivateKey = {private}\nAddress = {device['address']}\nDNS = {dns}\nMTU = {device['mtu']}\n\n[Peer]\nPublicKey = {device['serverPublicKey']}\nEndpoint = {device['endpoint']}\nAllowedIPs = 0.0.0.0/0, ::/0\nPersistentKeepalive = 25\n"
        STORE.mkdir(parents=True,exist_ok=True);PROFILE.write_text(text);os.chmod(PROFILE,0o600);self.save()
    def connect(self):
        protocol=self.protocol_ids.get(self.protocol.get(),"wireguard")
        if protocol!="wireguard":raise RuntimeError("Tento protokol server zatím neověřil pro Linux. Vyber WireGuard; IKEv2, SSTP a proxy se aktivují až po serverovém health-checku a klientském profilu.")
        self.ensure_profile();subprocess.run(["pkexec","wg-quick","up",str(PROFILE)],check=True);self.status.set("Připojeno přes WireGuard");self.update_status()
    def disconnect(self):subprocess.run(["pkexec","wg-quick","down",str(PROFILE)],check=False);self.status.set("Odpojeno");self.update_status()
    def poll(self):
        if self.uuid:
            d=self.request("device_status",device_uuid=self.uuid,ad_placement="linux_main")["device"]
            if d.get("status") not in ("active",):self.disconnect()
            for card in d.get("focusCards",[]):
                if card.get("activeNow") and card.get("actionType") in ("disconnect_vpn","block_network"):self.disconnect()
            self.status.set("Online" if d.get("online") else "Připraveno")
        self.update_status()
    def update_status(self):self.root.after(0,lambda:self.label_var.config(text=self.status.get()))
    def logout(self):self.token="";SESSION.unlink(missing_ok=True);self.login_view()
    def async_run(self,fn):
        def run():
            try:fn()
            except Exception as e:self.root.after(0,lambda:messagebox.showerror("ZellnsVPN",str(e)))
        threading.Thread(target=run,daemon=True).start()

root=tk.Tk();App(root);root.mainloop()
