#!/usr/bin/env python3
"""
Rezerv VPN - Linux Command Line Interface (CLI)
Cross-platform, fast, resilient VPN manager for Linux.
Supports 8 languages (EN, RU, ES, ZH, DE, FR, AR, PT) and Multi-location Server Switching (NL, DE, US, RU).
"""

import os
import sys
import subprocess
import argparse
import urllib.parse
import urllib.request
import re
import time
import json
import base64
import uuid

API_BASE_URL = "http://188.137.254.130"
CONFIG_DIR = "/etc/rezerv"
CONFIG_PATH = "/etc/rezerv/vpn_client.toml"
LOG_PATH = "/var/log/rezerv.log"
PID_FILE = "/var/run/rezerv.pid"
DEVICE_FILE = "/etc/rezerv/device_id"
STATE_PATH = "/etc/rezerv/state.json"

# 8 Supported Languages matching Mobile App
SUPPORTED_LANGUAGES = ["en", "ru", "es", "zh", "de", "fr", "ar", "pt"]

def detect_language():
    env_lang = os.environ.get("REZERV_LANG", "").lower()
    if env_lang in SUPPORTED_LANGUAGES:
        return env_lang
    sys_lang = os.environ.get("LANG", "").lower()
    for code in SUPPORTED_LANGUAGES:
        if sys_lang.startswith(code):
            return code
    return "en"

LANG = detect_language()

MESSAGES = {
    "ru": {
        "title": "Rezerv VPN - Управление VPN-подключением в Linux",
        "need_sudo": "[-] Для работы с сетевым туннелем требуются права администратора.",
        "sudo_hint": "    Запустите команду через sudo: sudo rezerv {cmd}",
        "validating": "[*] Валидация ключа подписки на сервере ({token})...",
        "sub_confirmed": "[+] Подписка подтверждена! Пользователь: {user}, Сервер: {server}",
        "sub_inactive": "[!] Внимание: Подписка не активна или истекла.",
        "limit_reached": "Превышен лимит активных устройств для данной подписки.",
        "cfg_saved": "[+] Конфигурация сохранена в {path}",
        "starting": "[*] Запуск Rezerv VPN ({bin_path})...",
        "started_bg": "[+] VPN процесс запущен в фоновом режиме (PID: {pid})",
        "checking": "[*] Проверка соединения с VPN сервером...",
        "connected": "[+] Rezerv VPN успешно подключен и активен!",
        "dns_setting": "[*] Настройка системного DNS и защиты от утечек...",
        "disconnected": "[+] Rezerv VPN отключен.",
        "not_running": "[*] Rezerv VPN не запущен.",
        "status_connected": "[🟢 СТАТУС: ПОДКЛЮЧЕН] (PID: {pid})",
        "status_disconnected": "[⚪ СТАТУС: ОТКЛЮЧЕН]",
        "public_ip": "    🌍 Текущий публичный IP: {ip}",
        "err_key": "[-] Ошибка: укажите ключ подключения. Пример: rezerv connect \"rezerv://c/...\"",
        "stopping": "[*] Остановка Rezerv VPN (PID {pid})...",
        "proc_failed": "[-] Ошибка: VPN процесс завершился с кодом {code}. Проверьте логи: rezerv logs",
        "logs_empty": "[*] Файл логов {path} пока не создан.",
        "servers_title": "🌍 Доступные локации Rezerv VPN:",
        "switch_usage": "Для переключения локации используйте: rezerv switch <код_страны>",
        "switching": "[*] Переключение на локацию {flag} {name} ({code})...",
        "server_not_found": "[-] Локация '{target}' не найдена. Доступные: {available}",
        "no_config": "[-] Конфигурация не найдена. Сначала выполните: rezerv connect \"rezerv://c/...\""
    },
    "en": {
        "title": "Rezerv VPN - Linux VPN Connection Manager",
        "need_sudo": "[-] Administrator privileges (sudo) required for network tunnel configuration.",
        "sudo_hint": "    Run command using sudo: sudo rezerv {cmd}",
        "validating": "[*] Validating subscription key on server ({token})...",
        "sub_confirmed": "[+] Subscription verified! User: {user}, Server: {server}",
        "sub_inactive": "[!] Warning: Subscription is not active or expired.",
        "limit_reached": "Device limit reached for this subscription.",
        "cfg_saved": "[+] Configuration saved to {path}",
        "starting": "[*] Starting Rezerv VPN ({bin_path})...",
        "started_bg": "[+] VPN process started in background (PID: {pid})",
        "checking": "[*] Verifying connection to VPN server...",
        "connected": "[+] Rezerv VPN connected successfully!",
        "dns_setting": "[*] Configuring system DNS and leak protection...",
        "disconnected": "[+] Rezerv VPN disconnected.",
        "not_running": "[*] Rezerv VPN is not running.",
        "status_connected": "[🟢 STATUS: CONNECTED] (PID: {pid})",
        "status_disconnected": "[⚪ STATUS: DISCONNECTED]",
        "public_ip": "    🌍 Current public IP: {ip}",
        "err_key": "[-] Error: provide a connection key. Example: rezerv connect \"rezerv://c/...\"",
        "stopping": "[*] Stopping Rezerv VPN (PID {pid})...",
        "proc_failed": "[-] Error: VPN process exited with code {code}. Check logs: rezerv logs",
        "logs_empty": "[*] Log file {path} has not been created yet.",
        "servers_title": "🌍 Available Rezerv VPN Locations:",
        "switch_usage": "To switch location use: rezerv switch <country_code>",
        "switching": "[*] Switching to location {flag} {name} ({code})...",
        "server_not_found": "[-] Location '{target}' not found. Available: {available}",
        "no_config": "[-] Configuration not found. First connect with: rezerv connect \"rezerv://c/...\""
    },
    "es": {
        "title": "Rezerv VPN - Administrador de Conexión VPN para Linux",
        "need_sudo": "[-] Se requieren privilegios de administrador (sudo) para configurar el túnel de red.",
        "sudo_hint": "    Ejecute el comando con sudo: sudo rezerv {cmd}",
        "validating": "[*] Validando clave de suscripción en el servidor ({token})...",
        "sub_confirmed": "[+] ¡Suscripción verificada! Usuario: {user}, Servidor: {server}",
        "sub_inactive": "[!] Advertencia: La suscripción no está activa o ha caducado.",
        "limit_reached": "Límite de dispositivos alcanzado para esta suscripción.",
        "cfg_saved": "[+] Configuración guardada en {path}",
        "starting": "[*] Iniciando Rezerv VPN ({bin_path})...",
        "started_bg": "[+] Proceso VPN iniciado en segundo plano (PID: {pid})",
        "checking": "[*] Verificando conexión al servidor VPN...",
        "connected": "[+] ¡Rezerv VPN conectado con éxito!",
        "dns_setting": "[*] Configurando DNS del sistema y protección contra fugas...",
        "disconnected": "[+] Rezerv VPN desconectado.",
        "not_running": "[*] Rezerv VPN no se está ejecutando.",
        "status_connected": "[🟢 ESTADO: CONECTADO] (PID: {pid})",
        "status_disconnected": "[⚪ ESTADO: DESCONECTADO]",
        "public_ip": "    🌍 IP pública actual: {ip}",
        "err_key": "[-] Error: proporcione una clave. Ejemplo: rezerv connect \"rezerv://c/...\"",
        "stopping": "[*] Deteniendo Rezerv VPN (PID {pid})...",
        "proc_failed": "[-] Error: el proceso VPN finalizó con código {code}. Ver registros: rezerv logs",
        "logs_empty": "[*] El archivo de registros {path} aún no existe.",
        "servers_title": "🌍 Ubicaciones disponibles de Rezerv VPN:",
        "switch_usage": "Para cambiar de ubicación use: rezerv switch <código_país>",
        "switching": "[*] Cambiando a la ubicación {flag} {name} ({code})...",
        "server_not_found": "[-] Ubicación '{target}' no encontrada. Disponibles: {available}",
        "no_config": "[-] No se encontró configuración. Primero ejecute: rezerv connect \"rezerv://c/...\""
    },
    "zh": {
        "title": "Rezerv VPN - Linux VPN 连接管理器",
        "need_sudo": "[-] 配置网络隧道需要管理员权限 (sudo)。",
        "sudo_hint": "    请使用 sudo 运行命令: sudo rezerv {cmd}",
        "validating": "[*] 正在向服务器验证订阅密钥 ({token})...",
        "sub_confirmed": "[+] 订阅验证成功！用户: {user}，服务器: {server}",
        "sub_inactive": "[!] 警告：订阅未激活或已过期。",
        "limit_reached": "该订阅已达到设备上限。",
        "cfg_saved": "[+] 配置已保存至 {path}",
        "starting": "[*] 正在启动 Rezerv VPN ({bin_path})...",
        "started_bg": "[+] VPN 进程已在后台启动 (PID: {pid})",
        "checking": "[*] 正在验证与 VPN 服务器的连接...",
        "connected": "[+] Rezerv VPN 连接成功且已激活！",
        "dns_setting": "[*] 正在配置系统 DNS 与防泄漏保护...",
        "disconnected": "[+] Rezerv VPN 已断开。",
        "not_running": "[*] Rezerv VPN 未在运行。",
        "status_connected": "[🟢 状态: 已连接] (PID: {pid})",
        "status_disconnected": "[⚪ 状态: 未连接]",
        "public_ip": "    🌍 当前公网 IP: {ip}",
        "err_key": "[-] 错误：请提供连接密钥。示例：rezerv connect \"rezerv://c/...\"",
        "stopping": "[*] 正在停止 Rezerv VPN (PID {pid})...",
        "proc_failed": "[-] 错误：VPN 进程异常退出 (代码 {code})。查看日志：rezerv logs",
        "logs_empty": "[*] 日志文件 {path} 尚未生成。",
        "servers_title": "🌍 Rezerv VPN 可用节点列表：",
        "switch_usage": "切换节点请使用: rezerv switch <国家代码>",
        "switching": "[*] 正在切换至节点 {flag} {name} ({code})...",
        "server_not_found": "[-] 未找到节点 '{target}'。可用节点: {available}",
        "no_config": "[-] 未找到配置文件。请先运行: rezerv connect \"rezerv://c/...\""
    },
    "de": {
        "title": "Rezerv VPN - Linux VPN Verbindungs-Manager",
        "need_sudo": "[-] Administratorrechte (sudo) für die Konfiguration des Netzwerktunnels erforderlich.",
        "sudo_hint": "    Befehl mit sudo ausführen: sudo rezerv {cmd}",
        "validating": "[*] Abonnementschlüssel wird auf dem Server validiert ({token})...",
        "sub_confirmed": "[+] Abonnement verifiziert! Benutzer: {user}, Server: {server}",
        "sub_inactive": "[!] Warnung: Abonnement ist nicht aktiv oder abgelaufen.",
        "limit_reached": "Gerätelimit für dieses Abonnement erreicht.",
        "cfg_saved": "[+] Konfiguration gespeichert unter {path}",
        "starting": "[*] Rezerv VPN wird gestartet ({bin_path})...",
        "started_bg": "[+] VPN-Prozess im Hintergrund gestartet (PID: {pid})",
        "checking": "[*] Verbindung zum VPN-Server wird überprüft...",
        "connected": "[+] Rezerv VPN erfolgreich verbunden!",
        "dns_setting": "[*] System-DNS und Leak-Schutz werden konfiguriert...",
        "disconnected": "[+] Rezerv VPN getrennt.",
        "not_running": "[*] Rezerv VPN läuft nicht.",
        "status_connected": "[🟢 STATUS: VERBUNDEN] (PID: {pid})",
        "status_disconnected": "[⚪ STATUS: GETRENNT]",
        "public_ip": "    🌍 Aktuelle öffentliche IP: {ip}",
        "err_key": "[-] Fehler: Bitte Verbindungsschlüssel angeben. Beispiel: rezerv connect \"rezerv://c/...\"",
        "stopping": "[*] Rezerv VPN wird beendet (PID {pid})...",
        "proc_failed": "[-] Fehler: VPN-Prozess mit Code {code} beendet. Protokolle prüfen: rezerv logs",
        "logs_empty": "[*] Protokolldatei {path} existiert noch nicht.",
        "servers_title": "🌍 Verfügbare Rezerv VPN Standorte:",
        "switch_usage": "Zum Wechseln des Standorts: rezerv switch <ländercode>",
        "switching": "[*] Wechsel zum Standort {flag} {name} ({code})...",
        "server_not_found": "[-] Standort '{target}' nicht gefunden. Verfügbar: {available}",
        "no_config": "[-] Keine Konfiguration gefunden. Zuerst ausführen: rezerv connect \"rezerv://c/...\""
    },
    "fr": {
        "title": "Rezerv VPN - Gestionnaire de Connexion VPN Linux",
        "need_sudo": "[-] Privilèges d'administrateur (sudo) requis pour configurer le tunnel réseau.",
        "sudo_hint": "    Exécutez la commande avec sudo: sudo rezerv {cmd}",
        "validating": "[*] Validation de la clé d'abonnement sur le serveur ({token})...",
        "sub_confirmed": "[+] Abonnement vérifié ! Utilisateur: {user}, Serveur: {server}",
        "sub_inactive": "[!] Attention: L'abonnement n'est pas actif ou a expiré.",
        "limit_reached": "Limite d'appareils atteinte pour cet abonnement.",
        "cfg_saved": "[+] Configuration enregistrée dans {path}",
        "starting": "[*] Démarrage de Rezerv VPN ({bin_path})...",
        "started_bg": "[+] Processus VPN démarré en arrière-plan (PID: {pid})",
        "checking": "[*] Vérification de la connexion au serveur VPN...",
        "connected": "[+] Rezerv VPN connecté avec succès !",
        "dns_setting": "[*] Configuration du DNS système et protection contre les fuites...",
        "disconnected": "[+] Rezerv VPN déconnecté.",
        "not_running": "[*] Rezerv VPN n'est pas en cours d'exécution.",
        "status_connected": "[🟢 ÉTAT: CONNECTÉ] (PID: {pid})",
        "status_disconnected": "[⚪ ÉTAT: DÉCONNECTÉ]",
        "public_ip": "    🌍 IP publique actuelle: {ip}",
        "err_key": "[-] Erreur: veuillez fournir une clé. Exemple: rezerv connect \"rezerv://c/...\"",
        "stopping": "[*] Arrêt de Rezerv VPN (PID {pid})...",
        "proc_failed": "[-] Erreur: le processus VPN s'est arrêté avec le code {code}. Journaux: rezerv logs",
        "logs_empty": "[*] Le fichier journal {path} n'existe pas encore.",
        "servers_title": "🌍 Emplacements disponibles Rezerv VPN:",
        "switch_usage": "Pour changer d'emplacement: rezerv switch <code_pays>",
        "switching": "[*] Basculement vers l'emplacement {flag} {name} ({code})...",
        "server_not_found": "[-] Emplacement '{target}' introuvable. Disponibles: {available}",
        "no_config": "[-] Configuration introuvable. Connectez-vous d'abord avec: rezerv connect \"rezerv://c/...\""
    },
    "ar": {
        "title": "Rezerv VPN - مدير اتصال VPN لنظام Linux",
        "need_sudo": "[-] يتطلب إعداد نفق الشبكة صلاحيات المسؤول (sudo).",
        "sudo_hint": "    قم بتشغيل الأمر باستخدام sudo: sudo rezerv {cmd}",
        "validating": "[*] جاري التحقق من مفتاح الاشتراك على الخادم ({token})...",
        "sub_confirmed": "[+] تم تأكيد الاشتراك! المستخدم: {user}، الخادم: {server}",
        "sub_inactive": "[!] تحذير: الاشتراك غير نشط أو منتهي الصلاحية.",
        "limit_reached": "تم الوصول إلى الحد الأقصى للأجهزة في هذا الاشتراك.",
        "cfg_saved": "[+] تم حفظ الإعدادات في {path}",
        "starting": "[*] جاري تشغيل Rezerv VPN ({bin_path})...",
        "started_bg": "[+] تم بدء عملية VPN في الخلفية (PID: {pid})",
        "checking": "[*] جاري التحقق من الاتصال بخادم VPN...",
        "connected": "[+] تم اتصال Rezerv VPN بنجاح!",
        "dns_setting": "[*] جاري ضبط DNS النظام وحماية التسريب...",
        "disconnected": "[+] تم فصل Rezerv VPN.",
        "not_running": "[*] Rezerv VPN ليس قيد التشغيل.",
        "status_connected": "[🟢 الحالة: متصل] (PID: {pid})",
        "status_disconnected": "[⚪ الحالة: غير متصل]",
        "public_ip": "    🌍 عنوان IP العام الحالي: {ip}",
        "err_key": "[-] خطأ: يرجى تقديم مفتاح الاتصال. مثال: rezerv connect \"rezerv://c/...\"",
        "stopping": "[*] جاري إيقاف Rezerv VPN (PID {pid})...",
        "proc_failed": "[-] خطأ: انتهت العملية برمز {code}. افحص السجلات: rezerv logs",
        "logs_empty": "[*] لم يتم إنشاء ملف السجلات {path} بعد.",
        "servers_title": "🌍 مواقع خوادم Rezerv VPN المتاحة:",
        "switch_usage": "لتغيير الموقع استخدم: rezerv switch <رمز_الدولة>",
        "switching": "[*] جاري التبديل إلى الموقع {flag} {name} ({code})...",
        "server_not_found": "[-] الموقع '{target}' غير موجود. المتاح: {available}",
        "no_config": "[-] لم يتم العثور على التكوين. اتصل أولاً: rezerv connect \"rezerv://c/...\""
    },
    "pt": {
        "title": "Rezerv VPN - Gerenciador de Conexão VPN para Linux",
        "need_sudo": "[-] Privilégios de administrador (sudo) são necessários para configurar o túnel.",
        "sudo_hint": "    Execute o comando com sudo: sudo rezerv {cmd}",
        "validating": "[*] Validando chave de assinatura no servidor ({token})...",
        "sub_confirmed": "[+] Assinatura confirmada! Usuário: {user}, Servidor: {server}",
        "sub_inactive": "[!] Aviso: A assinatura não está ativa ou expirou.",
        "limit_reached": "Limite de dispositivos atingido para esta assinatura.",
        "cfg_saved": "[+] Configuração salva em {path}",
        "starting": "[*] Iniciando Rezerv VPN ({bin_path})...",
        "started_bg": "[+] Processo VPN iniciado em segundo plano (PID: {pid})",
        "checking": "[*] Verificando conexão com o servidor VPN...",
        "connected": "[+] Rezerv VPN conectado com sucesso!",
        "dns_setting": "[*] Configurando DNS do sistema e proteção contra vazamento...",
        "disconnected": "[+] Rezerv VPN desconectado.",
        "not_running": "[*] O Rezerv VPN não está em execução.",
        "status_connected": "[🟢 STATUS: CONECTADO] (PID: {pid})",
        "status_disconnected": "[⚪ STATUS: DESCONECTADO]",
        "public_ip": "    🌍 IP público atual: {ip}",
        "err_key": "[-] Erro: forneça uma chave de conexão. Exemplo: rezerv connect \"rezerv://c/...\"",
        "stopping": "[*] Parando Rezerv VPN (PID {pid})...",
        "proc_failed": "[-] Erro: processo VPN encerrou com código {code}. Veja os registros: rezerv logs",
        "logs_empty": "[*] O arquivo de registros {path} ainda não foi criado.",
        "servers_title": "🌍 Localizações disponíveis do Rezerv VPN:",
        "switch_usage": "Para alternar de localização use: rezerv switch <código_país>",
        "switching": "[*] Alternando para a localização {flag} {name} ({code})...",
        "server_not_found": "[-] Localização '{target}' não encontrada. Disponíveis: {available}",
        "no_config": "[-] Configuração não encontrada. Primeiro conecte com: rezerv connect \"rezerv://c/...\""
    }
}

def msg(key, **kwargs):
    text = MESSAGES.get(LANG, MESSAGES["en"]).get(key, "")
    return text.format(**kwargs) if kwargs else text

# Fallback known servers dictionary
KNOWN_SERVERS = {
    "nl": {"name": "Netherlands", "code": "NL", "flag": "🇳🇱", "ip": "81.91.179.92", "sni": "rezerv-vpn.duckdns.org"},
    "de": {"name": "Germany", "code": "DE", "flag": "🇩🇪", "ip": "145.63.130.137", "sni": "rezerv-de.duckdns.org"},
    "us": {"name": "United States", "code": "US", "flag": "🇺🇸", "ip": "212.43.153.251", "sni": "rezerv-us.duckdns.org"},
    "ru": {"name": "Russia (Bypass)", "code": "RU", "flag": "🇷🇺", "ip": "89.105.217.181", "sni": "rezerv-wlb.duckdns.org"},
}

def fetch_servers_list():
    try:
        url = f"{API_BASE_URL}/api/servers/available"
        req = urllib.request.Request(url, headers={"User-Agent": "Rezerv-Linux-CLI/1.0"})
        with urllib.request.urlopen(req, timeout=5) as resp:
            if resp.status == 200:
                return json.loads(resp.read().decode())
    except Exception:
        pass
    # Fallback to static list
    return [
        {"country_code": s["code"], "flag_emoji": s["flag"], "name": s["name"], "ip_address": s["ip"], "hostname": s["sni"], "load_percent": 0.0}
        for s in KNOWN_SERVERS.values()
    ]

class ByteReader:
    def __init__(self, data: bytes):
        self.data = data
        self.offset = 0

    @property
    def has_remaining(self):
        return self.offset < len(self.data)

    @property
    def remaining(self):
        return len(self.data) - self.offset

    def read_byte(self):
        b = self.data[self.offset]
        self.offset += 1
        return b

    def read_bytes(self, length):
        res = self.data[self.offset:self.offset+length]
        self.offset += length
        return res

    def read_varint(self):
        if not self.has_remaining:
            return 0
        first = self.read_byte()
        prefix = first >> 6
        if prefix == 0:
            return first & 0x3F
        elif prefix == 1:
            if not self.has_remaining: return 0
            second = self.read_byte()
            return ((first & 0x3F) << 8) | second
        elif prefix == 2:
            if self.remaining < 3: return 0
            val = first & 0x3F
            for _ in range(3):
                val = (val << 8) | self.read_byte()
            return val
        elif prefix == 3:
            if self.remaining < 7: return 0
            val = first & 0x3F
            for _ in range(7):
                val = (val << 8) | self.read_byte()
            return val
        return 0

def decode_tlv_deeplink(uri: str) -> dict:
    trimmed = uri.strip()
    if trimmed.startswith("tt://?"):
        payload = trimmed[6:]
    elif trimmed.startswith("rezerv://?"):
        payload = trimmed[10:]
    else:
        raise ValueError(f"Unknown deep link format: {trimmed[:20]}")

    payload += '=' * (-len(payload) % 4)
    data = base64.urlsafe_b64decode(payload)

    reader = ByteReader(data)
    cfg = {
        "version": 0,
        "hostname": "",
        "addresses": [],
        "custom_sni": None,
        "has_ipv6": True,
        "username": "",
        "password": "",
        "skip_verification": False,
        "upstream_protocol": "http2",
        "anti_dpi": False,
        "client_random": "",
        "name": None,
        "dns_upstreams": []
    }

    TAG_VERSION = 0x00
    TAG_HOSTNAME = 0x01
    TAG_ADDRESSES = 0x02
    TAG_CUSTOM_SNI = 0x03
    TAG_HAS_IPV6 = 0x04
    TAG_USERNAME = 0x05
    TAG_PASSWORD = 0x06
    TAG_SKIP_VERIFY = 0x07
    TAG_UPSTREAM_PROTO = 0x09
    TAG_ANTI_DPI = 0x0A
    TAG_CLIENT_RANDOM = 0x0B
    TAG_NAME = 0x0C
    TAG_DNS_UPSTREAMS = 0x0D

    while reader.has_remaining:
        tag = reader.read_varint()
        length = reader.read_varint()
        if reader.remaining < length:
            break
        val_bytes = reader.read_bytes(length)

        if tag == TAG_VERSION:
            cfg["version"] = ByteReader(val_bytes).read_varint()
        elif tag == TAG_HOSTNAME:
            cfg["hostname"] = val_bytes.decode('utf-8', errors='replace')
        elif tag == TAG_ADDRESSES:
            cfg["addresses"].append(val_bytes.decode('utf-8', errors='replace'))
        elif tag == TAG_CUSTOM_SNI:
            cfg["custom_sni"] = val_bytes.decode('utf-8', errors='replace')
        elif tag == TAG_HAS_IPV6:
            cfg["has_ipv6"] = len(val_bytes) > 0 and val_bytes[0] == 0x01
        elif tag == TAG_USERNAME:
            cfg["username"] = val_bytes.decode('utf-8', errors='replace')
        elif tag == TAG_PASSWORD:
            cfg["password"] = val_bytes.decode('utf-8', errors='replace')
        elif tag == TAG_SKIP_VERIFY:
            cfg["skip_verification"] = len(val_bytes) > 0 and val_bytes[0] == 0x01
        elif tag == TAG_UPSTREAM_PROTO:
            proto = ByteReader(val_bytes).read_varint()
            cfg["upstream_protocol"] = "http3" if proto == 0x02 else "http2"
        elif tag == TAG_ANTI_DPI:
            cfg["anti_dpi"] = len(val_bytes) > 0 and val_bytes[0] == 0x01
        elif tag == TAG_CLIENT_RANDOM:
            cfg["client_random"] = val_bytes.decode('utf-8', errors='replace')
        elif tag == TAG_NAME:
            cfg["name"] = val_bytes.decode('utf-8', errors='replace')
        elif tag == TAG_DNS_UPSTREAMS:
            dns_reader = ByteReader(val_bytes)
            while dns_reader.has_remaining:
                s_len = dns_reader.read_varint()
                if dns_reader.remaining < s_len: break
                cfg["dns_upstreams"].append(dns_reader.read_bytes(s_len).decode('utf-8', errors='replace'))

    return cfg

def build_toml_from_cfg(cfg: dict, server_ip: str = None, sni_host: str = None) -> str:
    target_hostname = sni_host or "rezerv-vpn.duckdns.org"
    if re.match(r"^[0-9.]+$", target_hostname) or ":" in target_hostname:
        target_hostname = "rezerv-vpn.duckdns.org"

    addresses = cfg.get("addresses", [])
    if server_ip:
        addresses = [f"{server_ip}:443"]
    elif not addresses:
        addresses = ["81.91.179.92:443"]

    addr_str = ", ".join(f'"{a}"' for a in addresses)
    user = cfg.get("username", "")
    pwd = cfg.get("password", "")
    c_rand = cfg.get("client_random", "")
    custom_sni = cfg.get("custom_sni") or "speedtest.net"
    anti_dpi = "true" if cfg.get("anti_dpi", True) else "false"
    skip_ver = "true" if cfg.get("skip_verification", False) else "false"

    return f"""vpn_mode = "general"
loglevel = "info"
killswitch_enabled = false
post_quantum_group_enabled = false
exclusions = []

[endpoint]
hostname = "{target_hostname}"
addresses = [{addr_str}]
has_ipv6 = false
username = "{user}"
password = "{pwd}"
client_random = "{c_rand}"
custom_sni = "{custom_sni}"
skip_verification = {skip_ver}
certificate = ""
upstream_protocol = "http2"
anti_dpi = {anti_dpi}
dns_upstreams = ["https://dns.google/dns-query", "https://cloudflare-dns.com/dns-query", "8.8.8.8", "1.1.1.1"]
name = "Rezerv-Server"

[listener.tun]
included_routes = ["0.0.0.0/0"]
excluded_routes = []
mtu_size = 1350
"""

def setup_system_dns():
    """Configures system DNS and prevents IPv6 leaks on Linux."""
    # 1. Wait for tun0 interface
    for _ in range(15):
        if os.path.exists("/sys/class/net/tun0"):
            break
        time.sleep(0.2)

    # 2. Configure systemd-resolved if present
    if os.path.exists("/sys/class/net/tun0"):
        try:
            subprocess.run(["resolvectl", "dns", "tun0", "8.8.8.8", "1.1.1.1"], stderr=subprocess.DEVNULL)
            subprocess.run(["resolvectl", "domain", "tun0", "~."], stderr=subprocess.DEVNULL)
            subprocess.run(["resolvectl", "default-route", "tun0", "true"], stderr=subprocess.DEVNULL)
            subprocess.run(["resolvectl", "flush-caches"], stderr=subprocess.DEVNULL)
        except Exception:
            pass

    # 3. Direct resolv.conf backup and update
    try:
        if not os.path.exists("/etc/rezerv/resolv.conf.backup"):
            import shutil
            if os.path.exists("/etc/resolv.conf") and not os.path.islink("/etc/resolv.conf"):
                shutil.copy2("/etc/resolv.conf", "/etc/rezerv/resolv.conf.backup")
        if not os.path.islink("/etc/resolv.conf"):
            with open("/etc/resolv.conf", "w") as f:
                f.write("nameserver 8.8.8.8\nnameserver 1.1.1.1\n")
    except Exception:
        pass

    # 4. Disable IPv6 while connected to avoid IPv6 bypass/leaks
    try:
        subprocess.run(["sysctl", "-w", "net.ipv6.conf.all.disable_ipv6=1"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
    except Exception:
        pass

def restore_system_dns():
    """Restores original system DNS and IPv6 settings."""
    try:
        subprocess.run(["resolvectl", "revert", "tun0"], stderr=subprocess.DEVNULL)
        subprocess.run(["resolvectl", "flush-caches"], stderr=subprocess.DEVNULL)
    except Exception:
        pass

    try:
        if os.path.exists("/etc/rezerv/resolv.conf.backup"):
            import shutil
            shutil.copy2("/etc/rezerv/resolv.conf.backup", "/etc/resolv.conf")
            os.remove("/etc/rezerv/resolv.conf.backup")
    except Exception:
        pass

    try:
        subprocess.run(["sysctl", "-w", "net.ipv6.conf.all.disable_ipv6=0"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
    except Exception:
        pass

def get_binary_path():
    candidates = [
        "/opt/rezerv-vpn/rezerv_runner",
        "/opt/rezerv-vpn/rezerv_client",
        "/usr/local/bin/rezerv_runner",
        "/usr/local/bin/rezerv_client",
        "/usr/bin/rezerv_client",
        os.path.join(os.path.dirname(os.path.abspath(__file__)), "rezerv_runner"),
        os.path.join(os.path.dirname(os.path.abspath(__file__)), "rezerv_client"),
        os.path.expanduser("~/.local/bin/rezerv_client"),
    ]
    for c in candidates:
        if os.path.isfile(c) and os.access(c, os.X_OK):
            return c
    return "rezerv_client"

def get_or_create_device_id():
    ensure_dirs()
    if os.path.exists(DEVICE_FILE):
        try:
            with open(DEVICE_FILE, "r") as f:
                d = f.read().strip()
                if d: return d
        except Exception:
            pass

    if os.path.exists("/etc/machine-id"):
        try:
            with open("/etc/machine-id", "r") as f:
                d = f"linux_{f.read().strip()[:16]}"
                with open(DEVICE_FILE, "w") as out:
                    out.write(d)
                return d
        except Exception:
            pass

    d = f"linux_{uuid.uuid4().hex[:16]}"
    try:
        with open(DEVICE_FILE, "w") as out:
            out.write(d)
    except Exception:
        pass
    return d

def load_state() -> dict:
    if os.path.exists(STATE_PATH):
        try:
            with open(STATE_PATH, "r", encoding="utf-8") as f:
                return json.load(f)
        except Exception:
            pass
    return {}

def save_state(updates: dict):
    ensure_dirs()
    state = load_state()
    state.update(updates)
    try:
        with open(STATE_PATH, "w", encoding="utf-8") as f:
            json.dump(state, f, indent=2, ensure_ascii=False)
    except Exception:
        pass

def ensure_dirs():
    os.makedirs(CONFIG_DIR, exist_ok=True)
    os.makedirs(os.path.dirname(LOG_PATH), exist_ok=True)
    os.makedirs(os.path.dirname(PID_FILE), exist_ok=True)

def resolve_key_to_toml(key: str, server_override: str = None) -> str:
    """Resolves subscription key / deep link to complete TOML configuration."""
    raw = key.strip().strip("'").strip('"')

    # 1. Plain TOML string or file
    if raw.startswith("[endpoint]") or "vpn_mode =" in raw:
        return raw
    if os.path.isfile(raw):
        with open(raw, "r", encoding="utf-8") as f:
            return f.read()

    # 2. Raw TLV link (rezerv://?... or tt://?...)
    if raw.startswith("rezerv://?") or raw.startswith("tt://?"):
        cfg = decode_tlv_deeplink(raw)
        return build_toml_from_cfg(cfg)

    # 3. Query string deep link (rezerv://host:port?username=...&password=...)
    if (raw.startswith("rezerv://") or raw.startswith("tt://")) and ("username=" in raw and "password=" in raw):
        u = urllib.parse.urlparse(raw)
        host = u.hostname or "81.91.179.92"
        port = u.port or 443
        qs = urllib.parse.parse_qs(u.query)
        username = qs.get("username", [""])[0]
        password = qs.get("password", [""])[0]
        sni = qs.get("sni", ["rezerv-vpn.duckdns.org"])[0]
        anti_dpi = qs.get("anti_dpi", ["true"])[0].lower() in ("1", "true", "yes")

        cfg = {
            "hostname": sni,
            "addresses": [f"{host}:{port}"],
            "username": username,
            "password": password,
            "custom_sni": "speedtest.net",
            "anti_dpi": anti_dpi,
            "skip_verification": False,
        }
        return build_toml_from_cfg(cfg, server_ip=host, sni_host=sni)

    # 4. Token / Subscription UUID link (rezerv://c/<token>, tt://c/<token>, or raw <token>)
    token = raw
    for prefix in ["rezerv://c/", "tt://c/", "rezerv://", "tt://"]:
        if token.startswith(prefix):
            token = token[len(prefix):]
            break

    print(msg("validating", token=token))
    device_id = get_or_create_device_id()
    val_url = f"{API_BASE_URL}/api/subscriptions/validate?token={urllib.parse.quote(token)}&device_id={device_id}"

    try:
        req = urllib.request.Request(val_url, headers={"User-Agent": "Rezerv-Linux-CLI/1.0"})
        with urllib.request.urlopen(req, timeout=15) as resp:
            if resp.status == 200:
                data = json.loads(resp.read().decode())
                if data.get("error") == "device_limit_reached":
                    raise ValueError(msg("limit_reached"))
                if not data.get("is_active"):
                    print(msg("sub_inactive"))

                server_ip = data.get("server_ip")
                sni_host = data.get("sni_host") or "rezerv-vpn.duckdns.org"
                conn_cfg = data.get("connection_config", "")

                if conn_cfg and (conn_cfg.startswith("rezerv://?") or conn_cfg.startswith("tt://?")):
                    cfg = decode_tlv_deeplink(conn_cfg)
                    print(msg("sub_confirmed", user=data.get('username') or cfg.get('username'), server=server_ip or 'Auto'))
                    return build_toml_from_cfg(cfg, server_ip=server_ip, sni_host=sni_host)
                elif conn_cfg:
                    return resolve_key_to_toml(conn_cfg)
            else:
                raise ValueError(f"HTTP {resp.status}")
    except Exception as e:
        raise RuntimeError(f"API Error: {e}")

    raise ValueError("Unrecognized key format.")

def is_running():
    # Check pid file
    if os.path.exists(PID_FILE):
        try:
            with open(PID_FILE, "r") as f:
                pid = int(f.read().strip())
            os.kill(pid, 0)
            return pid
        except Exception:
            pass

    # Check pidof
    try:
        out = subprocess.check_output(["pidof", "rezerv_client"]).decode().strip()
        pids = out.split()
        if pids:
            return int(pids[0])
    except Exception:
        pass
    return None

def cmd_servers(args):
    """Lists available servers with load and ping."""
    servers = fetch_servers_list()
    print("\n" + msg("servers_title"))
    print("-" * 55)
    print(f"{'CODE':<6} {'LOCATION':<22} {'LOAD':<10} {'STATUS'}")
    print("-" * 55)

    for s in servers:
        code = s.get("country_code", "??").lower()
        flag = s.get("flag_emoji") or "🌐"
        name = s.get("name") or s.get("country") or "Server"
        load = f"{s.get('load_percent', 0.0):.0f}%"
        status = "🟢 Online" if s.get("is_active", True) else "🔴 Maintenance"
        print(f"{code:<6} {flag} {name:<18} {load:<10} {status}")

    print("-" * 55)
    print(msg("switch_usage") + "\n")

def cmd_switch(args):
    """Switch to a specific server location."""
    ensure_dirs()
    if not os.path.exists(CONFIG_PATH):
        print(msg("no_config"))
        sys.exit(1)

    target = (args.target or "").strip().lower()
    servers = fetch_servers_list()
    
    match = None
    for s in servers:
        cc = (s.get("country_code") or "").lower()
        nm = (s.get("name") or "").lower()
        if target == cc or target in nm:
            match = s
            break

    if not match and target in KNOWN_SERVERS:
        match = {
            "country_code": KNOWN_SERVERS[target]["code"],
            "flag_emoji": KNOWN_SERVERS[target]["flag"],
            "name": KNOWN_SERVERS[target]["name"],
            "ip_address": KNOWN_SERVERS[target]["ip"],
            "hostname": KNOWN_SERVERS[target]["sni"]
        }

    if not match:
        avail = ", ".join(s.get("country_code", "").lower() for s in servers if s.get("country_code"))
        print(msg("server_not_found", target=target, available=avail))
        sys.exit(1)

    flag = match.get("flag_emoji") or "🌐"
    name = match.get("name") or match.get("country") or "Server"
    code = (match.get("country_code") or target).upper()
    new_ip = match.get("ip_address")
    new_sni = match.get("hostname")

    print(msg("switching", flag=flag, name=name, code=code))

    # Read existing config and replace addresses and hostname
    with open(CONFIG_PATH, "r", encoding="utf-8") as f:
        toml = f.read()

    toml = re.sub(r'addresses\s*=\s*\[[^\]]+\]', f'addresses = ["{new_ip}:443"]', toml)
    toml = re.sub(r'hostname\s*=\s*"[^"]+"', f'hostname = "{new_sni}"', toml)

    with open(CONFIG_PATH, "w", encoding="utf-8") as f:
        f.write(toml)

    # Persist last selected server in state.json
    save_state({
        "last_location": code.lower(),
        "last_location_name": name,
        "last_flag": flag,
        "last_ip": new_ip,
        "last_sni": new_sni,
    })

    # Reconnect
    cmd_connect(args)

def cmd_connect(args):
    ensure_dirs()
    key = getattr(args, "key", None)
    state = load_state()
    saved_token = state.get("last_token")

    if not key and os.path.exists(CONFIG_PATH):
        loc_name = state.get("last_location_name")
        flag = state.get("last_flag", "🌐")
        if loc_name:
            print(f"[*] Location: {flag} {loc_name}")
        print(f"[*] Config: {CONFIG_PATH}")
    elif not key and saved_token:
        key = saved_token

    if key:
        try:
            save_state({"last_token": key})
            toml = resolve_key_to_toml(key)

            # Preserve previously selected location if user switched countries
            saved_ip = state.get("last_ip")
            saved_sni = state.get("last_sni")
            saved_loc_name = state.get("last_location_name")
            saved_flag = state.get("last_flag", "🌐")
            if saved_ip and saved_sni and not getattr(args, "reset_location", False):
                toml = re.sub(r'addresses\s*=\s*\[[^\]]+\]', f'addresses = ["{saved_ip}:443"]', toml)
                toml = re.sub(r'hostname\s*=\s*"[^"]+"', f'hostname = "{saved_sni}"', toml)
                if saved_loc_name:
                    print(f"[*] Restoring saved location: {saved_flag} {saved_loc_name} ({saved_ip})")

            with open(CONFIG_PATH, "w", encoding="utf-8") as f:
                f.write(toml)
            print(msg("cfg_saved", path=CONFIG_PATH))
        except Exception as e:
            print(f"[-] Error: {e}")
            sys.exit(1)
    elif not os.path.exists(CONFIG_PATH):
        print(msg("err_key"))
        sys.exit(1)

    # Stop any existing process
    cmd_disconnect(args, quiet=True)

    bin_path = get_binary_path()
    if not os.path.exists(bin_path):
        print(f"[-] Binary not found: {bin_path}")
        sys.exit(1)

    print(msg("starting", bin_path=bin_path))
    log_file = open(LOG_PATH, "a", encoding="utf-8")
    log_file.write(f"\n--- Rezerv VPN Started at {time.strftime('%Y-%m-%d %H:%M:%S')} ---\n")
    log_file.flush()

    if getattr(args, "foreground", False):
        proc = subprocess.Popen([bin_path, "-c", CONFIG_PATH])
        try:
            proc.wait()
        except KeyboardInterrupt:
            proc.terminate()
            proc.wait()
    else:
        proc = subprocess.Popen(
            [bin_path, "-c", CONFIG_PATH],
            stdout=log_file,
            stderr=log_file,
            start_new_session=True
        )
        with open(PID_FILE, "w") as f:
            f.write(str(proc.pid))
        
        print(msg("started_bg", pid=proc.pid))
        print(f"[*] Logs: {LOG_PATH}")
        print(msg("checking"))

        connected = False
        for _ in range(10):
            time.sleep(0.5)
            if proc.poll() is not None:
                print(msg("proc_failed", code=proc.returncode))
                sys.exit(1)
            
            try:
                if os.path.exists(LOG_PATH):
                    with open(LOG_PATH, "r", encoding="utf-8", errors="replace") as lf:
                        content = lf.read()
                        if "Successfully connected to endpoint" in content or "VPN_SS_CONNECTED" in content:
                            connected = True
                            break
            except Exception:
                pass

        print(msg("connected"))
        print(msg("dns_setting"))
        setup_system_dns()
        cmd_status(args)

def cmd_disconnect(args, quiet=False):
    restore_system_dns()
    for runner in ["/opt/rezerv-vpn/rezerv_runner", "/usr/local/bin/rezerv_runner"]:
        if os.path.isfile(runner) and os.access(runner, os.X_OK):
            try:
                subprocess.run([runner, "--kill"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
            except Exception:
                pass

    pid = is_running()
    if not pid:
        if not quiet:
            print(msg("not_running"))
        return

    if not quiet:
        print(msg("stopping", pid=pid))
    try:
        os.kill(pid, 15)
        time.sleep(0.3)
    except Exception:
        pass

    try:
        subprocess.run(["pkill", "-9", "-f", "rezerv_client"], stderr=subprocess.DEVNULL)
    except Exception:
        pass

    if os.path.exists(PID_FILE):
        try:
            os.remove(PID_FILE)
        except Exception:
            pass

    if not quiet:
        print(msg("disconnected"))

def cmd_status(args):
    pid = is_running()
    state = load_state()
    if pid:
        print(msg("status_connected", pid=pid))
        loc_name = state.get("last_location_name")
        flag = state.get("last_flag", "🌐")
        if loc_name:
            print(f"    📍 Location: {flag} {loc_name}")
        try:
            ip = subprocess.check_output(["curl", "-s", "--max-time", "3", "https://api.ipify.org"]).decode().strip()
            print(msg("public_ip", ip=ip))
        except Exception:
            pass
    else:
        print(msg("status_disconnected"))
        loc_name = state.get("last_location_name")
        flag = state.get("last_flag", "🌐")
        if loc_name:
            print(f"    📍 Last location: {flag} {loc_name}")

def cmd_logs(args):
    if not os.path.exists(LOG_PATH):
        print(msg("logs_empty", path=LOG_PATH))
        return
    
    if args.follow:
        try:
            subprocess.run(["tail", "-f", "-n", "50", LOG_PATH])
        except KeyboardInterrupt:
            pass
    else:
        subprocess.run(["tail", "-n", "50", LOG_PATH])

def main():
    # If not running as root, auto-escalate with sudo for network operations
    if os.geteuid() != 0 and len(sys.argv) > 1 and sys.argv[1] in ("connect", "disconnect", "switch"):
        try:
            target_bin = "/usr/local/bin/rezerv" if os.path.exists("/usr/local/bin/rezerv") else sys.argv[0]
            cmd = ["sudo", target_bin] + sys.argv[1:]
            os.execvp("sudo", cmd)
        except Exception:
            print(msg("need_sudo"))
            print(msg("sudo_hint", cmd=" ".join(sys.argv[1:])))
            sys.exit(1)

    parser = argparse.ArgumentParser(
        prog="rezerv",
        description=msg("title"),
        formatter_class=argparse.RawDescriptionHelpFormatter,
        epilog="""Examples / Примеры / Ejemplos / 示例:
  rezerv connect "rezerv://c/..."   Connect by subscription key / deep link
  rezerv servers                    List all available VPN locations (NL, DE, US, RU)
  rezerv switch de                  Switch to Germany
  rezerv switch us                  Switch to USA
  rezerv switch nl                  Switch to Netherlands
  rezerv switch ru                  Switch to Russia (Bypass)
  rezerv status                     Show status & public IP
  rezerv disconnect                 Disconnect VPN
  rezerv logs -f                    View real-time logs
"""
    )
    subparsers = parser.add_subparsers(dest="command", help="Command")

    p_connect = subparsers.add_parser("connect", help="Connect Rezerv VPN")
    p_connect.add_argument("key", nargs="?", help="Connection key or link")
    p_connect.add_argument("-f", "--foreground", action="store_true", help="Run in foreground")

    p_switch = subparsers.add_parser("switch", help="Switch location (nl, de, us, ru)")
    p_switch.add_argument("target", help="Country code or name (e.g. nl, de, us, ru)")
    p_switch.add_argument("-f", "--foreground", action="store_true", help="Run in foreground")

    subparsers.add_parser("servers", help="List available VPN locations")
    subparsers.add_parser("disconnect", help="Disconnect Rezerv VPN")
    subparsers.add_parser("status", help="Show connection status")
    
    p_logs = subparsers.add_parser("logs", help="View logs")
    p_logs.add_argument("-f", "--follow", action="store_true", help="Follow logs (tail -f)")

    args = parser.parse_args()

    if args.command == "connect":
        cmd_connect(args)
    elif args.command == "switch":
        cmd_switch(args)
    elif args.command == "servers":
        cmd_servers(args)
    elif args.command == "disconnect":
        cmd_disconnect(args)
    elif args.command == "status":
        cmd_status(args)
    elif args.command == "logs":
        cmd_logs(args)
    else:
        parser.print_help()

if __name__ == "__main__":
    main()
