#!/usr/bin/env python3
"""VPN Paris — client Linux officiel (WireGuard).

Commandes :
  vpnparis signup <e-mail>   crée l'essai gratuit et configure le tunnel
  vpnparis login  <e-mail>   récupère la configuration d'un compte existant
  vpnparis up                active le VPN
  vpnparis down              coupe le VPN
  vpnparis status            état du tunnel et du compte
  vpnparis logout            coupe le VPN et efface le compte de cette machine
"""
import hashlib
import json
import os
import shutil
import subprocess
import sys
import time
import urllib.error
import urllib.request

API = "https://api.vpnparis.com"
CONF = os.environ.get("VPNPARIS_CONF", "/etc/wireguard/vpnparis.conf")
STATE = os.environ.get("VPNPARIS_STATE", "/etc/vpnparis")
TUN = os.path.splitext(os.path.basename(CONF))[0]
TESTING = "VPNPARIS_CONF" in os.environ


def die(msg):
    print(msg, file=sys.stderr)
    sys.exit(1)


def need_root():
    if not TESTING and os.geteuid() != 0:
        die("Cette commande doit être lancée avec sudo, ex. : sudo vpnparis " +
            " ".join(sys.argv[1:2]))


def api(route, data):
    req = urllib.request.Request(API + route, json.dumps(data).encode(),
                                 {"Content-Type": "application/json"})
    try:
        with urllib.request.urlopen(req, timeout=25) as r:
            return json.load(r)
    except urllib.error.HTTPError as e:
        try:
            msg = json.load(e).get("error")
        except Exception:
            msg = None
        die("Erreur : " + (msg or f"HTTP {e.code}"))
    except Exception:
        die("Erreur : connexion impossible. Vérifiez votre accès Internet.")


def device_id():
    try:
        mid = open("/etc/machine-id").read().strip()
    except Exception:
        mid = "linux-inconnu"
    return hashlib.sha256(("vpnparis:" + mid).encode()).hexdigest()[:32]


def save(data):
    os.makedirs(STATE, mode=0o700, exist_ok=True)
    acc = os.path.join(STATE, "account.json")
    with open(acc, "w") as f:
        json.dump({k: data.get(k) for k in ("token", "email", "plan", "expires_at")}, f)
    os.chmod(acc, 0o600)
    if os.path.exists(CONF):
        shutil.copy2(CONF, CONF + ".bak")
        print(f"(ancienne configuration sauvegardée : {CONF}.bak)")
    old = os.umask(0o077)
    try:
        os.makedirs(os.path.dirname(CONF), exist_ok=True)
        with open(CONF, "w") as f:
            f.write(data["config"])
    finally:
        os.umask(old)


def account():
    try:
        return json.load(open(os.path.join(STATE, "account.json")))
    except Exception:
        return None


def cmd_account(email, signup):
    need_root()
    if "@" not in email or len(email) < 6:
        die("Adresse e-mail invalide.")
    body = {"email": email.strip().lower()}
    if signup:
        body["device"] = device_id()
    data = api("/app/signup" if signup else "/app/login", body)
    save(data)
    print(f"✔ Compte {data['email']} ({data['plan']}) configuré.")
    print("Activez le VPN avec : sudo vpnparis up")


def public_ip():
    try:
        with urllib.request.urlopen("https://api.ipify.org", timeout=8) as r:
            return r.read().decode().strip()
    except Exception:
        return None


def show_ip():
    ip = public_ip()
    if ip:
        fr = " (France ✓)" if ip.startswith("217.182.") else ""
        print(f"IP publique : {ip}{fr}")


def cmd_up():
    need_root()
    if not os.path.exists(CONF):
        die("Aucun compte configuré. Commencez par : sudo vpnparis signup <e-mail>")
    r = subprocess.run(["wg-quick", "up", TUN])
    if r.returncode == 0:
        print("✔ VPN Paris activé — votre trafic sort par la France.")
        show_ip()
    else:
        die("Échec de l'activation (déjà actif ? essayez : sudo vpnparis down)")


def cmd_down():
    need_root()
    r = subprocess.run(["wg-quick", "down", TUN])
    print("✔ VPN coupé." if r.returncode == 0 else "Le VPN n'était pas actif.")
    show_ip()


def cmd_status():
    need_root()
    r = subprocess.run(["wg", "show", TUN], capture_output=True, text=True)
    if r.returncode == 0 and r.stdout.strip():
        print("● VPN ACTIF\n")
        print(r.stdout.strip())
    else:
        print("○ VPN inactif")
    show_ip()
    acc = account()
    if acc:
        print(f"\nCompte : {acc.get('email')} — {acc.get('plan')}")
        exp = acc.get("expires_at")
        if exp:
            days = (exp - time.time()) / 86400
            print("Expire : " + (f"dans {days:.0f} jour(s)" if days > 0 else "EXPIRÉ"))


def cmd_logout():
    need_root()
    subprocess.run(["wg-quick", "down", TUN], capture_output=True)
    for p in (CONF, os.path.join(STATE, "account.json")):
        if os.path.exists(p):
            os.remove(p)
    print("✔ Compte effacé de cette machine, VPN coupé.")


def main():
    args = sys.argv[1:]
    if not args or args[0] in ("-h", "--help", "help"):
        print(__doc__.strip())
        return
    cmd = args[0]
    if cmd in ("signup", "login"):
        if len(args) < 2:
            die(f"usage : sudo vpnparis {cmd} <e-mail>")
        cmd_account(args[1], cmd == "signup")
    elif cmd == "up":
        cmd_up()
    elif cmd == "down":
        cmd_down()
    elif cmd == "status":
        cmd_status()
    elif cmd == "logout":
        cmd_logout()
    else:
        die("Commande inconnue. Aide : vpnparis --help")


if __name__ == "__main__":
    main()
