pragma Singleton

import QtQuick
import Quickshell
import Quickshell.Io
import Caelestia
import Caelestia.Config

Singleton {
    id: root

    property bool connected: false
    property var status: ({
            connected: false,
            state: "disconnected",
            reason: "",
            authUrl: ""
        })

    readonly property bool connecting: connectProc.running || disconnectProc.running
    readonly property bool enabled: GlobalConfig.utilities.vpn.provider.some(p => typeof p === "object" ? (p.enabled === true) : false)
    readonly property var providerInput: {
        const enabledProvider = GlobalConfig.utilities.vpn.provider.find(p => typeof p === "object" ? (p.enabled === true) : false);
        return enabledProvider || "wireguard";
    }
    readonly property bool isCustomProvider: typeof providerInput === "object"
    readonly property string providerName: isCustomProvider ? (providerInput.name || "custom") : String(providerInput)
    readonly property string interfaceName: isCustomProvider ? (providerInput.interface || "") : ""
    readonly property var currentConfig: {
        const name = providerName;
        const iface = interfaceName;
        const defaults = getBuiltinDefaults(name, iface);

        if (isCustomProvider) {
            const custom = providerInput;
            return {
                connectCmd: custom.connectCmd || defaults.connectCmd,
                disconnectCmd: custom.disconnectCmd || defaults.disconnectCmd,
                interface: custom.interface || defaults.interface,
                displayName: custom.displayName || defaults.displayName
            };
        }

        return defaults;
    }

    function getBuiltinDefaults(name, iface) {
        const builtins = {
            "wireguard": {
                connectCmd: ["pkexec", "wg-quick", "up", iface],
                disconnectCmd: ["pkexec", "wg-quick", "down", iface],
                interface: iface,
                displayName: iface
            },
            "warp": {
                connectCmd: ["warp-cli", "connect"],
                disconnectCmd: ["warp-cli", "disconnect"],
                interface: "CloudflareWARP",
                displayName: "Warp"
            },
            "netbird": {
                connectCmd: ["netbird", "up", "--no-browser"],
                disconnectCmd: ["netbird", "down"],
                interface: "wt0",
                displayName: "NetBird"
            },
            "tailscale": {
                connectCmd: ["tailscale", "up"],
                disconnectCmd: ["tailscale", "down"],
                interface: "tailscale0",
                displayName: "Tailscale"
            }
        };

        return builtins[name] || {
            connectCmd: [name, "up"],
            disconnectCmd: [name, "down"],
            interface: iface || name,
            displayName: name
        };
    }

    function connect(): void {
        if (status.state === "needs-auth" && status.authUrl) {
            emitStatusToast(status);
            return;
        }
        if (!connected && !connecting && root.currentConfig && root.currentConfig.connectCmd) {
            connectProc.exec(root.currentConfig.connectCmd);
        }
    }

    function disconnect(): void {
        if (connected && !connecting && root.currentConfig && root.currentConfig.disconnectCmd) {
            disconnectProc.exec(root.currentConfig.disconnectCmd);
        }
    }

    function toggle(): void {
        connected ? disconnect() : connect();
    }

    function checkStatus(): void {
        if (root.enabled) {
            statusProc.running = true;
        }
    }

    function getStatusCommand(): var {
        switch (providerName) {
        case "tailscale":
            return ["tailscale", "status", "--json"];
        case "netbird":
            return ["netbird", "status", "--json"];
        case "warp":
            return ["warp-cli", "status"];
        case "wireguard":
            return ["ip", "link", "show"];
        default:
            return ["ip", "link", "show"];
        }
    }

    function parseTailscaleStatus(output: string): var {
        const status = {
            connected: false,
            state: "disconnected",
            reason: "",
            authUrl: ""
        };

        // Handle empty or whitespace-only output
        if (!output || output.trim().length === 0) {
            return status;
        }

        // Check for common non-JSON states first
        if (output.includes("Logged out") || output.includes("Stopped") || output.includes("not running") || output.includes("Tailscale is not running")) {
            status.state = "disconnected";
            return status;
        }

        // Try to parse as JSON
        try {
            const data = JSON.parse(output);
            const backendState = data.BackendState || "";

            if (backendState === "Running") {
                status.connected = true;
                status.state = "connected";
            } else if (backendState === "Starting") {
                status.state = "connecting";
            } else if (backendState === "NeedsLogin" || backendState === "NeedsMachineAuth") {
                status.state = "needs-auth";
                status.reason = backendState === "NeedsLogin" ? "Login required" : "Machine authorization required";
                status.authUrl = data.AuthURL || "";
            }
        } catch (e) {
            // JSON parsing failed - treat as disconnected unless it looks like an error
            if (output.includes("error") || output.includes("Error") || output.includes("failed")) {
                status.state = "disconnected";
                status.reason = "Tailscale may not be running";
            } else {
                status.state = "disconnected";
            }
        }
        return status;
    }

    function parseNetBirdStatus(output: string): var {
        const status = {
            connected: false,
            state: "disconnected",
            reason: "",
            authUrl: ""
        };
        try {
            const data = JSON.parse(output);
            const mgmtConnected = data.management?.connected;
            const signalConnected = data.signal?.connected;

            if (mgmtConnected && signalConnected) {
                status.connected = true;
                status.state = "connected";
            } else if (data.management?.error) {
                const error = data.management.error;
                if (error.includes("auth") || error.includes("login")) {
                    status.state = "needs-auth";
                    status.reason = "Authentication required";
                } else {
                    status.reason = error;
                }
            }
        } catch (e) {
            status.state = "error";
            status.reason = "Failed to parse status";
        }
        return status;
    }

    function parseWarpStatus(output: string): var {
        const status = {
            connected: false,
            state: "disconnected",
            reason: "",
            authUrl: ""
        };

        if (output.includes("Connected")) {
            status.connected = true;
            status.state = "connected";
        } else if (output.includes("Connecting")) {
            status.state = "connecting";
        } else if (output.includes("Unable") || output.includes("Registration Missing") || output.includes("registration") || output.includes("register")) {
            status.state = "needs-auth";
            status.reason = "WARP registration required";
        } else if (!output.includes("Disconnected")) {
            status.state = "error";
            status.reason = "Unknown WARP status";
        }
        return status;
    }

    function parseWireGuardStatus(output: string): var {
        const status = {
            connected: false,
            state: "disconnected",
            reason: "",
            authUrl: ""
        };
        const iface = root.currentConfig?.interface || "";

        if (iface && output.includes(iface + ":")) {
            status.connected = true;
            status.state = "connected";
        }
        return status;
    }

    function parseStatusOutput(output: string): var {
        switch (providerName) {
        case "tailscale":
            return parseTailscaleStatus(output);
        case "netbird":
            return parseNetBirdStatus(output);
        case "warp":
            return parseWarpStatus(output);
        case "wireguard":
        default:
            return parseWireGuardStatus(output);
        }
    }

    function extractAuthUrl(text: string): string {
        const urlMatch = text.match(/(https?:\/\/[^\s]+)/);
        return urlMatch ? urlMatch[1] : "";
    }

    function createAuthStatus(authUrl: string): var {
        return {
            connected: false,
            state: "needs-auth",
            reason: "Authentication required",
            authUrl: authUrl
        };
    }

    function updateStatus(newStatus: var): void {
        const oldState = status.state;
        if (newStatus.state === "needs-auth" && !newStatus.authUrl && status.authUrl) {
            newStatus.authUrl = status.authUrl;
        }
        status = newStatus;
        root.connected = newStatus.connected;

        if (oldState !== newStatus.state) {
            emitStatusToast(newStatus);
        }
    }

    function emitStatusToast(statusObj: var): void {
        if (!GlobalConfig.utilities.toasts.vpnChanged)
            return;

        const displayName = root.currentConfig ? (root.currentConfig.displayName || "VPN") : "VPN";

        switch (statusObj.state) {
        case "connected":
            Toaster.toast(qsTr("VPN connected"), qsTr("Connected to %1").arg(displayName), "vpn_key");
            break;
        case "disconnected":
            Toaster.toast(qsTr("VPN disconnected"), qsTr("Disconnected from %1").arg(displayName), "vpn_key_off");
            break;
        case "needs-auth":
            const authMsg = statusObj.reason || "Authentication required";
            Toaster.toast(qsTr("VPN authentication required"), qsTr("%1: %2").arg(displayName).arg(authMsg), "vpn_lock");
            break;
        case "error":
            if (status.state === "connected" || status.state === "connecting" || status.state === "needs-auth") {
                const errMsg = statusObj.reason || "Unknown error";
                Toaster.toast(qsTr("VPN error"), qsTr("%1: %2").arg(displayName).arg(errMsg), "error");
            }
            break;
        }
    }

    onStatusChanged: {
        if (providerName === "warp" && status.state === "needs-auth" && status.reason.includes("registration")) {
            warpRegisterProc.exec(["warp-cli", "registration", "new"]);
        }
    }

    onProviderNameChanged: {
        status = {
            connected: false,
            state: "disconnected",
            reason: "",
            authUrl: ""
        };
        root.connected = false;
        statusCheckTimer.start();
    }

    Component.onCompleted: root.enabled && statusCheckTimer.start()

    Process {
        id: nmMonitor

        running: root.enabled
        command: ["nmcli", "monitor"]
        stdout: SplitParser {
            onRead: statusCheckTimer.restart()
        }
    }

    Process {
        id: statusProc

        command: root.getStatusCommand()
        // qmllint disable incompatible-type
        environment: ({
                // qmllint enable incompatible-type
                LANG: "C.UTF-8",
                LC_ALL: "C.UTF-8"
            })
        stdout: StdioCollector {
            onStreamFinished: {
                const newStatus = root.parseStatusOutput(text);
                root.updateStatus(newStatus);
            }
        }
        stderr: StdioCollector {
            onStreamFinished: {
                if (text.trim().length > 0) {
                    if (text.includes("doesn't appear to be running") || text.includes("failed to connect to local tailscaled") || text.includes("daemon is not running") || text.includes("not running") && (text.includes("netbird") || text.includes("warp"))) {
                        let cmd = "sudo systemctl start ";
                        switch (root.providerName) {
                        case "tailscale":
                            cmd += "tailscaled";
                            break;
                        case "netbird":
                            cmd += "netbird";
                            break;
                        case "warp":
                            cmd += "warp-svc";
                            break;
                        default:
                            cmd += root.providerName + "d";
                            break;
                        }
                        const errorStatus = {
                            connected: false,
                            state: "disconnected",
                            reason: `Service not running (run: ${cmd})`,
                            authUrl: ""
                        };
                        root.updateStatus(errorStatus);
                    }
                }
            }
        }
    }

    Process {
        id: connectProc

        onExited: exitCode => { // qmllint disable signal-handler-parameters
            if (exitCode !== 0) {
                return;
            }

            if (root.providerName === "tailscale") {
                Qt.callLater(() => {
                    if (root.status.state !== "needs-auth") {
                        statusCheckTimer.start();
                    }
                });
            } else if (root.status.state !== "needs-auth") {
                statusCheckTimer.start();
            }
        }
        stdout: SplitParser {
            onRead: data => {
                const authUrl = root.extractAuthUrl(data);
                if (authUrl) {
                    root.updateStatus(root.createAuthStatus(authUrl));
                }
            }
        }
        stderr: StdioCollector {
            onStreamFinished: {
                const error = text.trim();

                if (error.includes("Access denied") || error.includes("checkprefs access denied")) {
                    const errorStatus = {
                        connected: false,
                        state: "disconnected",
                        reason: "Permission denied. Run in terminal: sudo tailscale set --operator=$USER",
                        authUrl: ""
                    };
                    root.updateStatus(errorStatus);
                    return;
                }

                if (error.includes("Unknown device type") || error.includes("Protocol not supported")) {
                    const errorStatus = {
                        connected: false,
                        state: "disconnected",
                        reason: "WireGuard module not loaded. Run: sudo modprobe wireguard",
                        authUrl: ""
                    };
                    root.updateStatus(errorStatus);
                    return;
                }

                const authUrl = root.extractAuthUrl(error);

                if (authUrl) {
                    root.updateStatus(root.createAuthStatus(authUrl));
                } else if (error.includes("already exists")) {
                    root.connected = true;
                }
            }
        }
    }

    Process {
        id: disconnectProc

        onExited: statusCheckTimer.start() // qmllint disable signal-handler-parameters
        stderr: StdioCollector {
            onStreamFinished: {
                const error = text.trim();
                if (error && !error.includes("[#]")) {
                    console.warn(lc, "Disconnection error:", error);
                }
            }
        }
    }

    Process {
        id: warpRegisterProc

        onExited: exitCode => { // qmllint disable signal-handler-parameters
            if (exitCode === 0) {
                statusCheckTimer.start();
            }
        }
    }

    Timer {
        id: statusCheckTimer

        interval: 500
        onTriggered: root.checkStatus()
    }

    LoggingCategory {
        id: lc

        name: "caelestia.qml.services.vpn"
        defaultLogLevel: LoggingCategory.Info
    }
}
