package collector

import (
	"encoding/json"
	"log"
	"os"
	"os/exec"
	"strconv"
	"strings"
)

type ProxmoxInfo struct {
	Available     bool              `json:"available"`
	Version       string            `json:"version"`
	Node          string            `json:"node"`
	VMs           []ProxmoxVM       `json:"vms,omitempty"`
	Containers    []ProxmoxContainer `json:"containers,omitempty"`
	Storage       []ProxmoxStorage  `json:"storage,omitempty"`
	Cluster       *ProxmoxCluster   `json:"cluster,omitempty"`
	Subscription  *ProxmoxSubscription `json:"subscription,omitempty"`
	ZFSPools      []ZFSPool         `json:"zfs_pools,omitempty"`
}

type ProxmoxVM struct {
	VMID        int    `json:"vmid"`
	Name        string `json:"name"`
	Status      string `json:"status"`
	Type        string `json:"type"`
	CPUUsage    *float64 `json:"cpu_usage,omitempty"`
	MemoryUsed  *int64   `json:"memory_used,omitempty"`
	MemoryMax   *int64   `json:"memory_max,omitempty"`
	DiskUsed    *int64   `json:"disk_used,omitempty"`
	DiskMax     *int64   `json:"disk_max,omitempty"`
	Uptime      *int64   `json:"uptime,omitempty"`
	Node        string `json:"node"`
}

type ProxmoxContainer struct {
	VMID        int    `json:"vmid"`
	Name        string `json:"name"`
	Status      string `json:"status"`
	Type        string `json:"type"`
	CPUUsage    *float64 `json:"cpu_usage,omitempty"`
	MemoryUsed  *int64   `json:"memory_used,omitempty"`
	MemoryMax   *int64   `json:"memory_max,omitempty"`
	DiskUsed    *int64   `json:"disk_used,omitempty"`
	DiskMax     *int64   `json:"disk_max,omitempty"`
	Uptime      *int64   `json:"uptime,omitempty"`
	Node        string `json:"node"`
}

type ProxmoxStorage struct {
	Name      string `json:"storage"`
	Type      string `json:"type"`
	Total     int64  `json:"total"`
	Used      int64  `json:"used"`
	Available int64  `json:"available"`
	Enabled   bool   `json:"enabled"`
	Active    bool   `json:"active"`
	Content   string `json:"content"`
}

type ProxmoxCluster struct {
	Name    string             `json:"name"`
	Nodes   []ProxmoxClusterNode `json:"nodes"`
	Quorum  bool               `json:"quorum"`
}

type ProxmoxClusterNode struct {
	Name   string `json:"name"`
	Status string `json:"status"`
	Local  bool   `json:"local"`
}

type ProxmoxSubscription struct {
	Status      string `json:"status"`
	ProductName string `json:"productname,omitempty"`
	Key         string `json:"key,omitempty"`
	NextDueDate string `json:"next_due_date,omitempty"`
}

type ZFSPool struct {
	Name         string  `json:"name"`
	Size         int64   `json:"size"`
	Allocated    int64   `json:"allocated"`
	Free         int64   `json:"free"`
	Health       string  `json:"health"`
	Fragmentation string `json:"fragmentation,omitempty"`
	ScrubStatus   string `json:"scrub_status,omitempty"`
}

func CollectProxmox() *ProxmoxInfo {
	proxmox := &ProxmoxInfo{
		Available: false,
	}
	
	// Prüfen ob pvesh verfügbar ist
	// Voller Pfad fuer pvesh, da systemd reduzierten PATH hat
	if _, err := os.Stat("/usr/bin/pvesh"); err != nil {
		log.Printf("Proxmox: pvesh nicht gefunden: %v", err)
		return nil // Proxmox nicht verfügbar
	}
	
	log.Printf("Proxmox: pvesh gefunden, sammle Daten...")
	proxmox.Available = true
	
	// Version abrufen
	if data := pveshGet("/version"); data != nil {
		log.Printf("Proxmox: Version data: %v", data)
		if versionData, ok := data.(map[string]interface{}); ok {
			if version, ok := versionData["version"].(string); ok {
				proxmox.Version = version
			}
		}
	}
	
	// Node-Name bestimmen
	proxmox.Node = getLocalNodeName()
	
	// VMs sammeln
	proxmox.VMs = collectProxmoxVMs()
	
	// Container sammeln
	proxmox.Containers = collectProxmoxContainers()
	
	// Storage sammeln
	proxmox.Storage = collectProxmoxStorage()
	
	// Cluster-Info sammeln
	proxmox.Cluster = collectProxmoxCluster()
	
	// Subscription-Info sammeln
	proxmox.Subscription = collectProxmoxSubscription()
	
	// ZFS-Pools sammeln
	proxmox.ZFSPools = collectZFSPools()
	
	return proxmox
}

// pvesh get wrapper
func pveshGet(path string) interface{} {
	cmd := exec.Command("/usr/bin/pvesh", "get", path, "--output-format", "json")
	output, err := cmd.Output()
	if err != nil {
		log.Printf("Proxmox: pvesh get %s fehler: %v", path, err)
		return nil
	}
	
	log.Printf("Proxmox: pvesh get %s: %d bytes", path, len(output))
	
	var result interface{}
	if err := json.Unmarshal(output, &result); err != nil {
		log.Printf("Proxmox: JSON parse fehler fuer %s: %v", path, err)
		return nil
	}
	
	return result
}

func getLocalNodeName() string {
	// Hostname als Node-Name verwenden
	if hostname, err := os.Hostname(); err == nil {
		return hostname
	}
	return "localhost"
}

func collectProxmoxVMs() []ProxmoxVM {
	var vms []ProxmoxVM
	
	nodeName := getLocalNodeName()
	data := pveshGet("/nodes/" + nodeName + "/qemu")
	if data == nil {
		return vms
	}
	
	if vmList, ok := data.([]interface{}); ok {
		for _, item := range vmList {
			if vmData, ok := item.(map[string]interface{}); ok {
				vm := ProxmoxVM{
					Type: "qemu",
					Node: nodeName,
				}
				
				if vmid, ok := vmData["vmid"].(float64); ok {
					vm.VMID = int(vmid)
				}
				if name, ok := vmData["name"].(string); ok {
					vm.Name = name
				}
				if status, ok := vmData["status"].(string); ok {
					vm.Status = status
				}
				if cpu, ok := vmData["cpu"].(float64); ok {
					vm.CPUUsage = &cpu
				}
				if mem, ok := vmData["mem"].(float64); ok {
					memUsed := int64(mem)
					vm.MemoryUsed = &memUsed
				}
				if maxmem, ok := vmData["maxmem"].(float64); ok {
					maxMem := int64(maxmem)
					vm.MemoryMax = &maxMem
				}
				if disk, ok := vmData["disk"].(float64); ok {
					diskUsed := int64(disk)
					vm.DiskUsed = &diskUsed
				}
				if maxdisk, ok := vmData["maxdisk"].(float64); ok {
					maxDisk := int64(maxdisk)
					vm.DiskMax = &maxDisk
				}
				if uptime, ok := vmData["uptime"].(float64); ok {
					uptimeVal := int64(uptime)
					vm.Uptime = &uptimeVal
				}
				
				vms = append(vms, vm)
			}
		}
	}
	
	return vms
}

func collectProxmoxContainers() []ProxmoxContainer {
	var containers []ProxmoxContainer
	
	nodeName := getLocalNodeName()
	data := pveshGet("/nodes/" + nodeName + "/lxc")
	if data == nil {
		return containers
	}
	
	if containerList, ok := data.([]interface{}); ok {
		for _, item := range containerList {
			if containerData, ok := item.(map[string]interface{}); ok {
				container := ProxmoxContainer{
					Type: "lxc",
					Node: nodeName,
				}
				
				if vmid, ok := containerData["vmid"].(float64); ok {
					container.VMID = int(vmid)
				}
				if name, ok := containerData["name"].(string); ok {
					container.Name = name
				}
				if status, ok := containerData["status"].(string); ok {
					container.Status = status
				}
				if cpu, ok := containerData["cpu"].(float64); ok {
					container.CPUUsage = &cpu
				}
				if mem, ok := containerData["mem"].(float64); ok {
					memUsed := int64(mem)
					container.MemoryUsed = &memUsed
				}
				if maxmem, ok := containerData["maxmem"].(float64); ok {
					maxMem := int64(maxmem)
					container.MemoryMax = &maxMem
				}
				if disk, ok := containerData["disk"].(float64); ok {
					diskUsed := int64(disk)
					container.DiskUsed = &diskUsed
				}
				if maxdisk, ok := containerData["maxdisk"].(float64); ok {
					maxDisk := int64(maxdisk)
					container.DiskMax = &maxDisk
				}
				if uptime, ok := containerData["uptime"].(float64); ok {
					uptimeVal := int64(uptime)
					container.Uptime = &uptimeVal
				}
				
				containers = append(containers, container)
			}
		}
	}
	
	return containers
}

func collectProxmoxStorage() []ProxmoxStorage {
	var storages []ProxmoxStorage
	
	nodeName := getLocalNodeName()
	data := pveshGet("/nodes/" + nodeName + "/storage")
	if data == nil {
		return storages
	}
	
	if storageList, ok := data.([]interface{}); ok {
		for _, item := range storageList {
			if storageData, ok := item.(map[string]interface{}); ok {
				storage := ProxmoxStorage{}
				
				if name, ok := storageData["storage"].(string); ok {
					storage.Name = name
				}
				if storageType, ok := storageData["type"].(string); ok {
					storage.Type = storageType
				}
				if total, ok := storageData["total"].(float64); ok {
					storage.Total = int64(total)
				}
				if used, ok := storageData["used"].(float64); ok {
					storage.Used = int64(used)
				}
				if avail, ok := storageData["avail"].(float64); ok {
					storage.Available = int64(avail)
				}
				if enabled, ok := storageData["enabled"].(float64); ok {
					storage.Enabled = enabled > 0
				}
				if active, ok := storageData["active"].(float64); ok {
					storage.Active = active > 0
				}
				if content, ok := storageData["content"].(string); ok {
					storage.Content = content
				}
				
				storages = append(storages, storage)
			}
		}
	}
	
	return storages
}

func collectProxmoxCluster() *ProxmoxCluster {
	data := pveshGet("/cluster/status")
	if data == nil {
		return nil
	}
	
	cluster := &ProxmoxCluster{}
	
	if clusterData, ok := data.([]interface{}); ok {
		for _, item := range clusterData {
			if itemData, ok := item.(map[string]interface{}); ok {
				if itemType, ok := itemData["type"].(string); ok {
					if itemType == "cluster" {
						if name, ok := itemData["name"].(string); ok {
							cluster.Name = name
						}
						if quorum, ok := itemData["quorate"].(float64); ok {
							cluster.Quorum = quorum > 0
						}
					} else if itemType == "node" {
						node := ProxmoxClusterNode{}
						if name, ok := itemData["name"].(string); ok {
							node.Name = name
						}
						if online, ok := itemData["online"].(float64); ok {
							if online > 0 {
								node.Status = "online"
							} else {
								node.Status = "offline"
							}
						}
						if local, ok := itemData["local"].(float64); ok {
							node.Local = local > 0
						}
						
						cluster.Nodes = append(cluster.Nodes, node)
					}
				}
			}
		}
	}
	
	if cluster.Name == "" && len(cluster.Nodes) == 0 {
		return nil // Kein Cluster
	}
	
	return cluster
}

func collectProxmoxSubscription() *ProxmoxSubscription {
	nodeName := getLocalNodeName()
	data := pveshGet("/nodes/" + nodeName + "/subscription")
	if data == nil {
		return nil
	}
	
	subscription := &ProxmoxSubscription{}
	
	if subData, ok := data.(map[string]interface{}); ok {
		if status, ok := subData["status"].(string); ok {
			subscription.Status = status
		}
		if productName, ok := subData["productname"].(string); ok {
			subscription.ProductName = productName
		}
		if key, ok := subData["key"].(string); ok {
			subscription.Key = key
		}
		if nextDueDate, ok := subData["nextduedate"].(string); ok {
			subscription.NextDueDate = nextDueDate
		}
	}
	
	return subscription
}

func collectZFSPools() []ZFSPool {
	var pools []ZFSPool
	
	// zpool list -H -o name,size,alloc,free,health
	cmd := exec.Command("/usr/sbin/zpool", "list", "-H", "-o", "name,size,alloc,free,health")
	output, err := cmd.Output()
	if err != nil {
		return pools // ZFS nicht verfügbar oder keine Pools
	}
	
	lines := strings.Split(string(output), "\n")
	for _, line := range lines {
		line = strings.TrimSpace(line)
		if line == "" {
			continue
		}
		
		fields := strings.Fields(line)
		if len(fields) < 5 {
			continue
		}
		
		pool := ZFSPool{
			Name:   fields[0],
			Health: fields[4],
		}
		
		// Größen parsen (können Suffixe wie K, M, G, T haben)
		pool.Size = parseZFSSize(fields[1])
		pool.Allocated = parseZFSSize(fields[2])
		pool.Free = parseZFSSize(fields[3])
		
		// Zusätzliche Informationen aus zpool status
		pool.ScrubStatus = getZFSPoolScrubStatus(pool.Name)
		
		pools = append(pools, pool)
	}
	
	return pools
}

// Parst ZFS-Größen mit Einheiten (z.B. "1.2T" -> Bytes)
func parseZFSSize(sizeStr string) int64 {
	if sizeStr == "-" || sizeStr == "" {
		return 0
	}
	
	sizeStr = strings.TrimSpace(sizeStr)
	if len(sizeStr) == 0 {
		return 0
	}
	
	// Letzes Zeichen prüfen für Einheit
	lastChar := sizeStr[len(sizeStr)-1]
	var multiplier int64 = 1
	var numberStr string
	
	switch lastChar {
	case 'K':
		multiplier = 1024
		numberStr = sizeStr[:len(sizeStr)-1]
	case 'M':
		multiplier = 1024 * 1024
		numberStr = sizeStr[:len(sizeStr)-1]
	case 'G':
		multiplier = 1024 * 1024 * 1024
		numberStr = sizeStr[:len(sizeStr)-1]
	case 'T':
		multiplier = 1024 * 1024 * 1024 * 1024
		numberStr = sizeStr[:len(sizeStr)-1]
	default:
		numberStr = sizeStr
	}
	
	if value, err := strconv.ParseFloat(numberStr, 64); err == nil {
		return int64(value * float64(multiplier))
	}
	
	return 0
}

// Holt Scrub-Status für einen ZFS-Pool
func getZFSPoolScrubStatus(poolName string) string {
	cmd := exec.Command("/usr/sbin/zpool", "status", poolName)
	output, err := cmd.Output()
	if err != nil {
		return ""
	}
	
	lines := strings.Split(string(output), "\n")
	for _, line := range lines {
		line = strings.TrimSpace(line)
		if strings.Contains(line, "scrub") {
			return line
		}
	}
	
	return ""
}