#!/usr/bin/env python3
import os
import subprocess
import sys
import threading
from dataclasses import dataclass

import gi

gi.require_version("Gtk", "3.0")
gi.require_version("AyatanaAppIndicator3", "0.1")
from gi.repository import AyatanaAppIndicator3 as AppIndicator
from gi.repository import GLib, Gtk


PROFILE_NAME = "Wellau SSL VPN"


def load_runtime_env():
    path = os.environ.get(
        "WELLAU_SSLVPN_RUNTIME_ENV_FILE",
        os.path.expanduser("~/.config/wellau-sslvpn/runtime.env"),
    )
    values = {}
    try:
        with open(path, "r", encoding="utf-8") as handle:
            for raw_line in handle:
                line = raw_line.strip()
                if not line or line.startswith("#") or "=" not in line:
                    continue
                key, value = line.split("=", 1)
                values[key] = value.strip().strip("'\"")
    except FileNotFoundError:
        pass
    return values


RUNTIME_ENV = load_runtime_env()
REPO_DIR = os.environ.get("SSLVPN_REPO_DIR") or RUNTIME_ENV.get("SSLVPN_REPO_DIR") or os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
STATUS_CMD = os.path.expanduser("~/.local/bin/wellau-vpn-status")
ENV_CONNECT_CMD = os.path.expanduser("~/.local/bin/wellau-vpn-connect-env")
LEGACY_CMD = os.path.expanduser("~/.local/bin/wellau-vpn-legacy-reconnect")
DISCONNECT_CMD = "sudo kill $(cat /tmp/wellau-vpn.pid) && echo 'Disconnected Wellau SSL VPN' || echo 'No active /tmp/wellau-vpn.pid tunnel to disconnect'; read -r -p 'Press Enter to close...'"
ICON_DIR = os.path.join(REPO_DIR, "assets", "icons")
ONLINE_ICON = "wellau-vpn-online"
OFFLINE_ICON = "wellau-vpn-offline"


@dataclass
class Health:
    ok: bool
    text: str
    summary: str


class WellauVpnGui:
    def __init__(self):
        self.indicator = AppIndicator.Indicator.new(
            "wellau-sslvpn",
            OFFLINE_ICON,
            AppIndicator.IndicatorCategory.SYSTEM_SERVICES,
        )
        if os.path.isdir(ICON_DIR):
            self.indicator.set_icon_theme_path(ICON_DIR)
        self.indicator.set_status(AppIndicator.IndicatorStatus.ACTIVE)

        self.status_item = Gtk.MenuItem(label="Checking VPN status...")
        self.status_item.set_sensitive(False)
        self.last_checked_item = Gtk.MenuItem(label="Last check: never")
        self.last_checked_item.set_sensitive(False)

        self.menu = Gtk.Menu()
        self.menu.append(self.status_item)
        self.menu.append(self.last_checked_item)
        self.menu.append(Gtk.SeparatorMenuItem())

        self.refresh_item = Gtk.MenuItem(label="Refresh status")
        self.refresh_item.connect("activate", lambda _item: self.refresh_async(show_window=True))
        self.menu.append(self.refresh_item)

        self.connect_item = Gtk.MenuItem(label="Connect from .env")
        self.connect_item.connect("activate", lambda _item: self.open_terminal(ENV_CONNECT_CMD))
        self.menu.append(self.connect_item)

        self.env_connect_item = Gtk.MenuItem(label="Connect from .env (terminal)")
        self.env_connect_item.connect("activate", lambda _item: self.open_terminal(ENV_CONNECT_CMD))
        self.menu.append(self.env_connect_item)

        self.disconnect_item = Gtk.MenuItem(label="Disconnect .env tunnel")
        self.disconnect_item.connect("activate", lambda _item: self.open_terminal(f"bash -lc {self.shell_quote(DISCONNECT_CMD)}"))
        self.menu.append(self.disconnect_item)

        self.reconnect_item = Gtk.MenuItem(label="Reconnect from .env")
        self.reconnect_item.connect("activate", lambda _item: self.open_terminal(ENV_CONNECT_CMD))
        self.menu.append(self.reconnect_item)

        self.legacy_item = Gtk.MenuItem(label="Legacy terminal reconnect")
        self.legacy_item.connect("activate", lambda _item: self.open_terminal(LEGACY_CMD))
        self.menu.append(self.legacy_item)

        self.editor_item = Gtk.MenuItem(label="Open NetworkManager editor")
        self.editor_item.connect("activate", lambda _item: self.spawn(["nm-connection-editor"]))
        self.menu.append(self.editor_item)

        self.menu.append(Gtk.SeparatorMenuItem())
        self.status_window_item = Gtk.MenuItem(label="Open status window")
        self.status_window_item.connect("activate", lambda _item: self.show_status_window())
        self.menu.append(self.status_window_item)

        self.install_item = Gtk.MenuItem(label="Reinstall desktop entry")
        self.install_item.connect("activate", lambda _item: self.spawn(["bash", f"{REPO_DIR}/scripts/install-desktop-entry.sh"]))
        self.menu.append(self.install_item)

        self.quit_item = Gtk.MenuItem(label="Quit")
        self.quit_item.connect("activate", lambda _item: Gtk.main_quit())
        self.menu.append(self.quit_item)

        self.menu.show_all()
        self.indicator.set_menu(self.menu)
        self.last_health = Health(False, "No status yet", "SUMMARY UNKNOWN")
        self.refresh_in_progress = False
        self.refresh_async(show_window=False)
        GLib.timeout_add_seconds(60, self.periodic_refresh)

    def periodic_refresh(self):
        self.refresh_async(show_window=False)
        return True

    def spawn(self, args):
        try:
            subprocess.Popen(args)
        except Exception as exc:
            self.message("Command failed", str(exc), Gtk.MessageType.ERROR)

    def open_terminal(self, command):
        terminals = [
            ["x-terminal-emulator", "-e", command],
            ["gnome-terminal", "--", command],
            ["xfce4-terminal", "-e", command],
        ]
        for args in terminals:
            try:
                subprocess.Popen(args)
                return
            except FileNotFoundError:
                continue
        self.message("Terminal not found", command, Gtk.MessageType.ERROR)

    def shell_quote(self, value):
        return "'" + value.replace("'", "'\\''") + "'"

    def run_nm_action(self, action):
        self.set_busy(True)
        def worker():
            result = subprocess.run(
                ["nmcli", "connection", action, PROFILE_NAME],
                text=True,
                stdout=subprocess.PIPE,
                stderr=subprocess.STDOUT,
            )
            GLib.idle_add(self.after_action, action, result.returncode, result.stdout)
        threading.Thread(target=worker, daemon=True).start()

    def reconnect(self):
        self.set_busy(True)
        def worker():
            subprocess.run(["nmcli", "connection", "down", PROFILE_NAME], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
            result = subprocess.run(
                ["nmcli", "connection", "up", PROFILE_NAME],
                text=True,
                stdout=subprocess.PIPE,
                stderr=subprocess.STDOUT,
            )
            GLib.idle_add(self.after_action, "reconnect", result.returncode, result.stdout)
        threading.Thread(target=worker, daemon=True).start()

    def after_action(self, action, returncode, output):
        self.set_busy(False)
        if returncode != 0:
            self.message(f"VPN {action} failed", output.strip() or "No output", Gtk.MessageType.ERROR)
        self.refresh_async(show_window=True)
        return False

    def refresh_async(self, show_window):
        if self.refresh_in_progress:
            return
        self.refresh_in_progress = True
        self.set_busy(True)
        def worker():
            health = self.check_health()
            GLib.idle_add(self.update_health, health, show_window)
        threading.Thread(target=worker, daemon=True).start()

    def check_health(self):
        result = subprocess.run(
            [STATUS_CMD],
            text=True,
            stdout=subprocess.PIPE,
            stderr=subprocess.STDOUT,
        )
        text = result.stdout.strip()
        summary = self.extract_summary(text)
        summary_ok = summary.startswith("SUMMARY OK")
        return Health(summary_ok, text, summary)

    def update_health(self, health, show_window):
        self.last_health = health
        label = self.label_from_summary(health.summary)
        icon = ONLINE_ICON if self.icon_should_be_online(health.summary) else OFFLINE_ICON
        self.status_item.set_label(f"Status: {label}")
        self.last_checked_item.set_label(f"Last check: {self.now_text()}")
        self.indicator.set_icon_full(icon, label)
        self.refresh_in_progress = False
        self.set_busy(False)
        if show_window:
            self.show_status_window()
        return False

    def extract_summary(self, text):
        for line in text.splitlines():
            if line.startswith("SUMMARY "):
                return line
        return "SUMMARY UNKNOWN"

    def summary_value(self, summary, key):
        prefix = f"{key}="
        for part in summary.split():
            if part.startswith(prefix):
                return part[len(prefix):]
        return "unknown"

    def icon_should_be_online(self, summary):
        control = self.summary_value(summary, "control")
        data = self.summary_value(summary, "data")
        return control == "ok" and data in {"ok", "partial"}

    def label_from_summary(self, summary):
        control = self.summary_value(summary, "control")
        data = self.summary_value(summary, "data")
        service = self.summary_value(summary, "service")
        if summary.startswith("SUMMARY OK"):
            return "Online: VPN data ok, services ok"
        if control == "ok" and data == "ok" and service == "degraded":
            return "VPN online: service probe degraded"
        if control == "ok" and data == "partial":
            return "VPN partial: some private probes failed"
        if control == "ok" and data == "fail":
            return "Data-plane down: tunnel process still up"
        if control == "fail":
            return "Offline: VPN control plane down"
        return "Unknown: status probe incomplete"

    def set_busy(self, busy):
        for item in [self.refresh_item, self.connect_item, self.env_connect_item, self.disconnect_item, self.reconnect_item]:
            item.set_sensitive(not busy)

    def now_text(self):
        return GLib.DateTime.new_now_local().format("%H:%M:%S")

    def show_status_window(self):
        dialog = Gtk.Dialog(title="Wellau SSL VPN Status")
        dialog.set_default_size(820, 520)
        dialog.add_button("Close", Gtk.ResponseType.CLOSE)
        box = dialog.get_content_area()
        header = Gtk.Label(label="Wellau SSL VPN maintenance panel")
        header.set_xalign(0)
        box.pack_start(header, False, False, 8)
        text = Gtk.TextView()
        text.set_editable(False)
        text.set_monospace(True)
        text.get_buffer().set_text(self.last_health.text)
        scroll = Gtk.ScrolledWindow()
        scroll.add(text)
        box.pack_start(scroll, True, True, 8)
        dialog.show_all()
        dialog.run()
        dialog.destroy()

    def message(self, title, text, message_type):
        dialog = Gtk.MessageDialog(
            transient_for=None,
            flags=0,
            message_type=message_type,
            buttons=Gtk.ButtonsType.OK,
            text=title,
        )
        dialog.format_secondary_text(text)
        dialog.run()
        dialog.destroy()


def main():
    if not os.path.exists(STATUS_CMD):
        print(f"Missing {STATUS_CMD}. Run scripts/install-desktop-entry.sh first.", file=sys.stderr)
        return 1
    WellauVpnGui()
    Gtk.main()
    return 0


if __name__ == "__main__":
    raise SystemExit(main())
