package main

import (
	"flag"
	"log"
	"math"
	"os"
	"path/filepath"
	"strings"
	"time"

	"github.com/cynfo/rmm-agent/client"
	"github.com/cynfo/rmm-agent/collector"
	"github.com/cynfo/rmm-agent/config"
	"github.com/cynfo/rmm-agent/ws"
)

var Version = "dev"

func main() {
	cfgPath := flag.String("config", "config.yaml", "Pfad zur Konfigurationsdatei")
	insecure := flag.Bool("insecure", false, "TLS-Zertifikatpruefung deaktivieren")
	flag.Parse()

	cfg, err := config.Load(*cfgPath)
	if err != nil {
		log.Fatalf("Konfiguration laden fehlgeschlagen: %v", err)
	}

	if *insecure {
		cfg.Insecure = true
	}

	log.Printf("RMM Agent v%s startet: %s", Version, cfg.AgentName)
	log.Printf("Backend: %s, Intervall: %ds", cfg.BackendURL, cfg.IntervalSeconds)

	c := client.New(cfg.BackendURL, cfg.APIKey, cfg.Insecure)

	// Agent-ID persistent laden oder neu registrieren
	idFile := filepath.Join(filepath.Dir(*cfgPath), "agent_id")
	var agentID string

	if data, err := os.ReadFile(idFile); err == nil && len(strings.TrimSpace(string(data))) > 0 {
		agentID = strings.TrimSpace(string(data))
		log.Printf("Agent-ID aus Datei geladen: %s", agentID)
	}

	// Registrierung mit Retry (sendet bestehende ID mit, falls vorhanden)
	backoff := 5 * time.Second

	for {
		hostname := collector.CollectHostname()
		if hostname == "" {
			hostname, _ = os.Hostname()
		}

		regReq := map[string]string{
			"name":             cfg.AgentName,
			"hostname":         hostname,
			"ip":               getLocalIP(),
			"opnsense_version": collector.CollectOPNsenseVersion(),
			"agent_version":    Version,
			"platform":         "freebsd",
		}
		if agentID != "" {
			regReq["agent_id"] = agentID
		}

		newID, regErr := c.Register(regReq)
		if regErr != nil {
			log.Printf("Registrierung fehlgeschlagen: %v (Retry in %v)", regErr, backoff)
			time.Sleep(backoff)
			backoff = time.Duration(math.Min(float64(backoff*2), float64(5*time.Minute)))
			continue
		}
		agentID = newID
		break
	}

	// ID persistent speichern
	if err := os.WriteFile(idFile, []byte(agentID), 0600); err != nil {
		log.Printf("WARNUNG: Agent-ID konnte nicht gespeichert werden: %v", err)
	}

	log.Printf("Agent registriert mit ID: %s", agentID)

	// WebSocket-Client starten
	wsClient := ws.NewClient(cfg.BackendURL, agentID, cfg.APIKey, cfg.Insecure)
	if err := wsClient.Start(); err != nil {
		log.Printf("WebSocket-Client starten fehlgeschlagen: %v", err)
	}
	defer wsClient.Stop()

	// Heartbeat-Loop
	ticker := time.NewTicker(time.Duration(cfg.IntervalSeconds) * time.Second)
	defer ticker.Stop()

	// Ersten Heartbeat sofort senden
	sendHeartbeat(c, agentID)

	backoff = 5 * time.Second
	for range ticker.C {
		if err := sendHeartbeat(c, agentID); err != nil {
			log.Printf("Heartbeat fehlgeschlagen: %v", err)
			backoff = time.Duration(math.Min(float64(backoff*2), float64(5*time.Minute)))
		} else {
			backoff = 5 * time.Second
		}
	}
}

func sendHeartbeat(c *client.Client, agentID string) error {
	log.Println("Sammle Systemdaten...")

	hw := collector.CollectHardware()
	cpu := collector.CollectCPU()
	mem := collector.CollectMemory()
	disks := collector.CollectDisks()
	net := collector.CollectNetwork()
	svcs := collector.CollectServices()
	wg := collector.CollectWireGuard()
	dhcp := collector.CollectDHCP()
	routes := collector.CollectRoutes()
	gateways := collector.CollectGateways()
	certs := collector.CollectCertificates()
	plugins := collector.CollectPlugins()
	updates := collector.CollectUpdates()
	cronJobs := collector.CollectCron()
	license := collector.CollectLicense()
	security := collector.CollectSecurity()
	pfStates := collector.CollectPFStates()
	haproxy := collector.CollectHAProxy()
	caddy := collector.CollectCaddy()

	req := map[string]interface{}{
		"agent_id":      agentID,
		"agent_version": Version,
		"system_data": map[string]interface{}{
			"agent_id":         agentID,
			"hostname":         collector.CollectHostname(),
			"opnsense_version": collector.CollectOPNsenseVersion(),
			"freebsd_version":  collector.CollectFreeBSDVersion(),
			"uptime_seconds":   collector.CollectUptime(),
			"hardware": map[string]interface{}{
				"manufacturer": hw.Manufacturer,
				"model":        hw.Model,
				"serial":       hw.Serial,
				"bios_version": hw.BIOSVersion,
			},
			"cpu": map[string]interface{}{
				"model":         cpu.Model,
				"cores":         cpu.Cores,
				"threads":       cpu.Threads,
				"freq_mhz":     cpu.FreqMHz,
				"usage_percent": cpu.UsagePercent,
			},
			"memory": map[string]interface{}{
				"total_bytes": mem.TotalBytes,
				"used_bytes":  mem.UsedBytes,
				"free_bytes":  mem.FreeBytes,
			},
			"disks":              disks,
			"network_interfaces": net,
			"services":           svcs,
			"wireguard":          wg,
			"dhcp":               dhcp,
			"routes":             routes,
			"gateways":           gateways,
			"certificates":       certs,
			"plugins":            plugins,
			"updates":            updates,
			"cron_jobs":          cronJobs,
			"license":            license,
			"security":           security,
			"pf_states":          pfStates,
			"haproxy":            haproxy,
			"caddy":              caddy,
		},
	}

	if err := c.Heartbeat(req); err != nil {
		return err
	}

	log.Println("Heartbeat gesendet")
	return nil
}

// getLocalIP versucht die primaere IP-Adresse zu ermitteln
func getLocalIP() string {
	// Versuche ueber ifconfig die erste nicht-loopback IP
	ifaces := collector.CollectNetwork()
	for _, iface := range ifaces {
		if iface.IP != "" && iface.Status == "up" {
			return iface.IP
		}
	}
	return ""
}
