#!/usr/bin/env python3
"""
VPN Status Monitor Node — Reports Tailscale VPN status to MQTT

Reports:
- VPN connectivity status (online/offline/connecting)
- Assigned Tailnet IP address
- Hostname on tailnet
- Connection type (relay/direct)
- Last connection attempt

MQTT Topics:
- saltylab/jetson/vpn/status -> online|offline|connecting
- saltylab/jetson/vpn/ip -> 100.x.x.x
- saltylab/jetson/vpn/hostname -> saltylab-orin.tail12345.ts.net
- saltylab/jetson/vpn/direct -> true|false (direct connection vs relay)
- saltylab/jetson/vpn/last_status -> timestamp
"""

import json
import subprocess
import time
from datetime import datetime
from typing import Optional, Dict, Any

import rclpy
from rclpy.node import Node


class VPNStatusNode(Node):
    """Monitor and report Tailscale VPN status to MQTT broker."""

    def __init__(self):
        super().__init__("vpn_status_node")

        # Declare ROS2 parameters
        self.declare_parameter("mqtt_host", "mqtt.local")
        self.declare_parameter("mqtt_port", 1883)
        self.declare_parameter("mqtt_topic_prefix", "saltylab/jetson/vpn")
        self.declare_parameter("poll_interval_sec", 30)

        # Get parameters
        self.mqtt_host = self.get_parameter("mqtt_host").value
        self.mqtt_port = self.get_parameter("mqtt_port").value
        self.mqtt_topic_prefix = self.get_parameter("mqtt_topic_prefix").value
        poll_interval = self.get_parameter("poll_interval_sec").value

        self.get_logger().info(f"VPN Status Node initialized")
        self.get_logger().info(f"MQTT: {self.mqtt_host}:{self.mqtt_port}")
        self.get_logger().info(f"Topic prefix: {self.mqtt_topic_prefix}")

        # Try to import MQTT client
        try:
            import paho.mqtt.client as mqtt

            self.mqtt_client = mqtt.Client(client_id="saltybot-vpn-status")
            self.mqtt_client.on_connect = self._on_mqtt_connect
            self.mqtt_client.on_disconnect = self._on_mqtt_disconnect
            self.mqtt_client.connect(self.mqtt_host, self.mqtt_port, keepalive=60)
            self.mqtt_client.loop_start()
        except ImportError:
            self.get_logger().error("paho-mqtt not installed")
            self.mqtt_client = None
        except Exception as e:
            self.get_logger().error(f"MQTT connection failed: {e}")
            self.mqtt_client = None

        # State tracking
        self.last_status: Optional[Dict[str, Any]] = None
        self.last_report_time = 0
        self.report_interval = 60

        # Create timer for polling
        self.timer = self.create_timer(poll_interval, self._poll_vpn_status)

        self.get_logger().info("Starting VPN status monitoring...")

    def _on_mqtt_connect(self, client, userdata, flags, rc):
        """MQTT connection callback."""
        if rc == 0:
            self.get_logger().info(f"MQTT connected")
        else:
            self.get_logger().warn(f"MQTT connection failed: {rc}")

    def _on_mqtt_disconnect(self, client, userdata, rc):
        """MQTT disconnection callback."""
        if rc != 0:
            self.get_logger().warn(f"MQTT disconnected: {rc}")

    def _poll_vpn_status(self) -> None:
        """Poll Tailscale status and report via MQTT."""
        try:
            status = self._get_tailscale_status()
            current_time = time.time()

            if status != self.last_status or (current_time - self.last_report_time > self.report_interval):
                self._publish_status(status)
                self.last_status = status
                self.last_report_time = current_time

        except Exception as e:
            self.get_logger().error(f"VPN status check failed: {e}")

    def _get_tailscale_status(self) -> Dict[str, Any]:
        """Get Tailscale status."""
        try:
            result = subprocess.run(
                ["sudo", "tailscale", "status", "--json"],
                capture_output=True,
                text=True,
                timeout=10,
            )

            if result.returncode != 0:
                return {"status": "offline"}

            data = json.loads(result.stdout)
            status_data = {"status": "unknown", "ip": None, "hostname": None, "direct": False}

            if not data.get("Self"):
                status_data["status"] = "offline"
                return status_data

            self_info = data["Self"]
            status_data["status"] = "online"
            status_data["hostname"] = self_info.get("HostName", "saltylab-orin")

            ips = self_info.get("TailscaleIPs", [])
            if ips:
                status_data["ip"] = ips[0]

            status_data["direct"] = bool(self_info.get("IsOnline"))
            return status_data

        except subprocess.TimeoutExpired:
            return {"status": "timeout"}
        except Exception:
            return {"status": "error"}

    def _publish_status(self, status: Dict[str, Any]) -> None:
        """Publish status to MQTT."""
        if not self.mqtt_client:
            return

        try:
            vpn_status = status.get("status", "unknown")

            self.mqtt_client.publish(
                f"{self.mqtt_topic_prefix}/status",
                vpn_status,
                qos=1,
                retain=True,
            )

            if status.get("ip"):
                self.mqtt_client.publish(
                    f"{self.mqtt_topic_prefix}/ip",
                    status["ip"],
                    qos=1,
                    retain=True,
                )

            if status.get("hostname"):
                self.mqtt_client.publish(
                    f"{self.mqtt_topic_prefix}/hostname",
                    status["hostname"],
                    qos=1,
                    retain=True,
                )

            self.mqtt_client.publish(
                f"{self.mqtt_topic_prefix}/direct",
                "true" if status.get("direct") else "false",
                qos=1,
                retain=True,
            )

            timestamp = datetime.now().isoformat()
            self.mqtt_client.publish(
                f"{self.mqtt_topic_prefix}/last_status",
                timestamp,
                qos=1,
                retain=True,
            )

            self.get_logger().info(f"VPN: {vpn_status} IP: {status.get('ip', 'N/A')}")

        except Exception as e:
            self.get_logger().error(f"MQTT publish failed: {e}")

    def destroy_node(self):
        """Clean up on shutdown."""
        if self.mqtt_client:
            self.mqtt_client.loop_stop()
            self.mqtt_client.disconnect()
        super().destroy_node()


def main(args=None):
    """Main entry point."""
    rclpy.init(args=args)
    node = VPNStatusNode()

    try:
        rclpy.spin(node)
    except KeyboardInterrupt:
        pass
    finally:
        node.destroy_node()
        rclpy.shutdown()


if __name__ == "__main__":
    main()
