// SPDX-License-Identifier: GPL-3.0-or-later

package vsphere

import (
	"fmt"
	"strings"

	"github.com/netdata/netdata/go/go.d.plugin/agent/module"
	rs "github.com/netdata/netdata/go/go.d.plugin/modules/vsphere/resources"
)

const (
	prioVMCPUUtilization = module.Priority + iota
	prioVmMemoryUtilization
	prioVmMemoryUsage
	prioVmMemorySwapUsage
	prioVmMemorySwapIO
	prioVmDiskIO
	prioVmDiskMaxLatency
	prioVmNetworkTraffic
	prioVmNetworkPackets
	prioVmNetworkDrops
	prioVmOverallStatus
	prioVmSystemUptime

	prioHostCPUUtilization
	prioHostMemoryUtilization
	prioHostMemoryUsage
	prioHostMemorySwapIO
	prioHostDiskIO
	prioHostDiskMaxLatency
	prioHostNetworkTraffic
	prioHostNetworkPackets
	prioHostNetworkDrops
	prioHostNetworkErrors
	prioHostOverallStatus
	prioHostSystemUptime
)

var (
	vmChartsTmpl = module.Charts{
		vmCPUUtilizationChartTmpl.Copy(),

		vmMemoryUtilizationChartTmpl.Copy(),
		vmMemoryUsageChartTmpl.Copy(),
		vmMemorySwapUsageChartTmpl.Copy(),
		vmMemorySwapIOChartTmpl.Copy(),

		vmDiskIOChartTmpl.Copy(),
		vmDiskMaxLatencyChartTmpl.Copy(),

		vmNetworkTrafficChartTmpl.Copy(),
		vmNetworkPacketsChartTmpl.Copy(),
		vmNetworkDropsChartTmpl.Copy(),

		vmOverallStatusChartTmpl.Copy(),

		vmSystemUptimeChartTmpl.Copy(),
	}

	vmCPUUtilizationChartTmpl = module.Chart{
		ID:       "%s_cpu_utilization",
		Title:    "Virtual Machine CPU utilization",
		Units:    "percentage",
		Fam:      "vms cpu",
		Ctx:      "vsphere.vm_cpu_utilization",
		Priority: prioVMCPUUtilization,
		Dims: module.Dims{
			{ID: "%s_cpu.usage.average", Name: "used", Div: 100},
		},
	}

	// Ref: https://www.vmware.com/support/developer/converter-sdk/conv51_apireference/memory_counters.html
	vmMemoryUtilizationChartTmpl = module.Chart{
		ID:       "%s_mem_utilization",
		Title:    "Virtual Machine memory utilization",
		Units:    "percentage",
		Fam:      "vms mem",
		Ctx:      "vsphere.vm_mem_utilization",
		Priority: prioVmMemoryUtilization,
		Dims: module.Dims{
			{ID: "%s_mem.usage.average", Name: "used", Div: 100},
		},
	}
	vmMemoryUsageChartTmpl = module.Chart{
		ID:       "%s_mem_usage",
		Title:    "Virtual Machine memory usage",
		Units:    "KiB",
		Fam:      "vms mem",
		Ctx:      "vsphere.vm_mem_usage",
		Priority: prioVmMemoryUsage,
		Dims: module.Dims{
			{ID: "%s_mem.granted.average", Name: "granted"},
			{ID: "%s_mem.consumed.average", Name: "consumed"},
			{ID: "%s_mem.active.average", Name: "active"},
			{ID: "%s_mem.shared.average", Name: "shared"},
		},
	}
	vmMemorySwapUsageChartTmpl = module.Chart{
		ID:       "%s_mem_swap_usage",
		Title:    "Virtual Machine VMKernel memory swap usage",
		Units:    "KiB",
		Fam:      "vms mem",
		Ctx:      "vsphere.vm_mem_swap_usage",
		Priority: prioVmMemorySwapUsage,
		Dims: module.Dims{
			{ID: "%s_mem.swapped.average", Name: "swapped"},
		},
	}
	vmMemorySwapIOChartTmpl = module.Chart{
		ID:       "%s_mem_swap_io_rate",
		Title:    "Virtual Machine VMKernel memory swap IO",
		Units:    "KiB/s",
		Fam:      "vms mem",
		Ctx:      "vsphere.vm_mem_swap_io",
		Type:     module.Area,
		Priority: prioVmMemorySwapIO,
		Dims: module.Dims{
			{ID: "%s_mem.swapinRate.average", Name: "in"},
			{ID: "%s_mem.swapoutRate.average", Name: "out"},
		},
	}

	vmDiskIOChartTmpl = module.Chart{
		ID:       "%s_disk_io",
		Title:    "Virtual Machine disk IO",
		Units:    "KiB/s",
		Fam:      "vms disk",
		Ctx:      "vsphere.vm_disk_io",
		Type:     module.Area,
		Priority: prioVmDiskIO,
		Dims: module.Dims{
			{ID: "%s_disk.read.average", Name: "read"},
			{ID: "%s_disk.write.average", Name: "write", Mul: -1},
		},
	}
	vmDiskMaxLatencyChartTmpl = module.Chart{
		ID:       "%s_disk_max_latency",
		Title:    "Virtual Machine disk max latency",
		Units:    "milliseconds",
		Fam:      "vms disk",
		Ctx:      "vsphere.vm_disk_max_latency",
		Priority: prioVmDiskMaxLatency,
		Dims: module.Dims{
			{ID: "%s_disk.maxTotalLatency.latest", Name: "latency"},
		},
	}

	vmNetworkTrafficChartTmpl = module.Chart{
		ID:       "%s_net_traffic",
		Title:    "Virtual Machine network traffic",
		Units:    "KiB/s",
		Fam:      "vms net",
		Ctx:      "vsphere.vm_net_traffic",
		Type:     module.Area,
		Priority: prioVmNetworkTraffic,
		Dims: module.Dims{
			{ID: "%s_net.bytesRx.average", Name: "received"},
			{ID: "%s_net.bytesTx.average", Name: "sent", Mul: -1},
		},
	}
	vmNetworkPacketsChartTmpl = module.Chart{
		ID:       "%s_net_packets",
		Title:    "Virtual Machine network packets",
		Units:    "packets",
		Fam:      "vms net",
		Ctx:      "vsphere.vm_net_packets",
		Priority: prioVmNetworkPackets,
		Dims: module.Dims{
			{ID: "%s_net.packetsRx.summation", Name: "received"},
			{ID: "%s_net.packetsTx.summation", Name: "sent", Mul: -1},
		},
	}
	vmNetworkDropsChartTmpl = module.Chart{
		ID:       "%s_net_drops",
		Title:    "Virtual Machine network dropped packets",
		Units:    "drops",
		Fam:      "vms net",
		Ctx:      "vsphere.vm_net_drops",
		Priority: prioVmNetworkDrops,
		Dims: module.Dims{
			{ID: "%s_net.droppedRx.summation", Name: "received"},
			{ID: "%s_net.droppedTx.summation", Name: "sent", Mul: -1},
		},
	}

	vmOverallStatusChartTmpl = module.Chart{
		ID:       "%s_overall_status",
		Title:    "Virtual Machine overall alarm status",
		Units:    "status",
		Fam:      "vms status",
		Ctx:      "vsphere.vm_overall_status",
		Priority: prioVmOverallStatus,
		Dims: module.Dims{
			{ID: "%s_overall.status.green", Name: "green"},
			{ID: "%s_overall.status.red", Name: "red"},
			{ID: "%s_overall.status.yellow", Name: "yellow"},
			{ID: "%s_overall.status.gray", Name: "gray"},
		},
	}

	vmSystemUptimeChartTmpl = module.Chart{
		ID:       "%s_system_uptime",
		Title:    "Virtual Machine system uptime",
		Units:    "seconds",
		Fam:      "vms uptime",
		Ctx:      "vsphere.vm_system_uptime",
		Priority: prioVmSystemUptime,
		Dims: module.Dims{
			{ID: "%s_sys.uptime.latest", Name: "uptime"},
		},
	}
)

var (
	hostChartsTmpl = module.Charts{
		hostCPUUtilizationChartTmpl.Copy(),

		hostMemUtilizationChartTmpl.Copy(),
		hostMemUsageChartTmpl.Copy(),
		hostMemSwapIOChartTmpl.Copy(),

		hostDiskIOChartTmpl.Copy(),
		hostDiskMaxLatencyChartTmpl.Copy(),

		hostNetworkTraffic.Copy(),
		hostNetworkPacketsChartTmpl.Copy(),
		hostNetworkDropsChartTmpl.Copy(),
		hostNetworkErrorsChartTmpl.Copy(),

		hostOverallStatusChartTmpl.Copy(),

		hostSystemUptimeChartTmpl.Copy(),
	}
	hostCPUUtilizationChartTmpl = module.Chart{
		ID:       "%s_cpu_usage_total",
		Title:    "ESXi Host CPU utilization",
		Units:    "percentage",
		Fam:      "hosts cpu",
		Ctx:      "vsphere.host_cpu_utilization",
		Priority: prioHostCPUUtilization,
		Dims: module.Dims{
			{ID: "%s_cpu.usage.average", Name: "used", Div: 100},
		},
	}
	hostMemUtilizationChartTmpl = module.Chart{
		ID:       "%s_mem_utilization",
		Title:    "ESXi Host memory utilization",
		Units:    "percentage",
		Fam:      "hosts mem",
		Ctx:      "vsphere.host_mem_utilization",
		Priority: prioHostMemoryUtilization,
		Dims: module.Dims{
			{ID: "%s_mem.usage.average", Name: "used", Div: 100},
		},
	}
	hostMemUsageChartTmpl = module.Chart{
		ID:       "%s_mem_usage",
		Title:    "ESXi Host memory usage",
		Units:    "KiB",
		Fam:      "hosts mem",
		Ctx:      "vsphere.host_mem_usage",
		Priority: prioHostMemoryUsage,
		Dims: module.Dims{
			{ID: "%s_mem.granted.average", Name: "granted"},
			{ID: "%s_mem.consumed.average", Name: "consumed"},
			{ID: "%s_mem.active.average", Name: "active"},
			{ID: "%s_mem.shared.average", Name: "shared"},
			{ID: "%s_mem.sharedcommon.average", Name: "sharedcommon"},
		},
	}
	hostMemSwapIOChartTmpl = module.Chart{
		ID:       "%s_mem_swap_rate",
		Title:    "ESXi Host VMKernel memory swap IO",
		Units:    "KiB/s",
		Fam:      "hosts mem",
		Ctx:      "vsphere.host_mem_swap_io",
		Type:     module.Area,
		Priority: prioHostMemorySwapIO,
		Dims: module.Dims{
			{ID: "%s_mem.swapinRate.average", Name: "in"},
			{ID: "%s_mem.swapoutRate.average", Name: "out"},
		},
	}

	hostDiskIOChartTmpl = module.Chart{
		ID:       "%s_disk_io",
		Title:    "ESXi Host disk IO",
		Units:    "KiB/s",
		Fam:      "hosts disk",
		Ctx:      "vsphere.host_disk_io",
		Type:     module.Area,
		Priority: prioHostDiskIO,
		Dims: module.Dims{
			{ID: "%s_disk.read.average", Name: "read"},
			{ID: "%s_disk.write.average", Name: "write", Mul: -1},
		},
	}
	hostDiskMaxLatencyChartTmpl = module.Chart{
		ID:       "%s_disk_max_latency",
		Title:    "ESXi Host disk max latency",
		Units:    "milliseconds",
		Fam:      "hosts disk",
		Ctx:      "vsphere.host_disk_max_latency",
		Priority: prioHostDiskMaxLatency,
		Dims: module.Dims{
			{ID: "%s_disk.maxTotalLatency.latest", Name: "latency"},
		},
	}

	hostNetworkTraffic = module.Chart{
		ID:       "%s_net_traffic",
		Title:    "ESXi Host network traffic",
		Units:    "KiB/s",
		Fam:      "hosts net",
		Ctx:      "vsphere.host_net_traffic",
		Type:     module.Area,
		Priority: prioHostNetworkTraffic,
		Dims: module.Dims{
			{ID: "%s_net.bytesRx.average", Name: "received"},
			{ID: "%s_net.bytesTx.average", Name: "sent", Mul: -1},
		},
	}
	hostNetworkPacketsChartTmpl = module.Chart{
		ID:       "%s_net_packets",
		Title:    "ESXi Host network packets",
		Units:    "packets",
		Fam:      "hosts net",
		Ctx:      "vsphere.host_net_packets",
		Priority: prioHostNetworkPackets,
		Dims: module.Dims{
			{ID: "%s_net.packetsRx.summation", Name: "received"},
			{ID: "%s_net.packetsTx.summation", Name: "sent", Mul: -1},
		},
	}
	hostNetworkDropsChartTmpl = module.Chart{
		ID:       "%s_net_drops_total",
		Title:    "ESXi Host network drops",
		Units:    "drops",
		Fam:      "hosts net",
		Ctx:      "vsphere.host_net_drops",
		Priority: prioHostNetworkDrops,
		Dims: module.Dims{
			{ID: "%s_net.droppedRx.summation", Name: "received"},
			{ID: "%s_net.droppedTx.summation", Name: "sent", Mul: -1},
		},
	}
	hostNetworkErrorsChartTmpl = module.Chart{
		ID:       "%s_net_errors",
		Title:    "ESXi Host network errors",
		Units:    "errors",
		Fam:      "hosts net",
		Ctx:      "vsphere.host_net_errors",
		Priority: prioHostNetworkErrors,
		Dims: module.Dims{
			{ID: "%s_net.errorsRx.summation", Name: "received"},
			{ID: "%s_net.errorsTx.summation", Name: "sent", Mul: -1},
		},
	}

	hostOverallStatusChartTmpl = module.Chart{
		ID:       "%s_overall_status",
		Title:    "ESXi Host overall alarm status",
		Units:    "status",
		Fam:      "hosts status",
		Ctx:      "vsphere.host_overall_status",
		Priority: prioHostOverallStatus,
		Dims: module.Dims{
			{ID: "%s_overall.status.green", Name: "green"},
			{ID: "%s_overall.status.red", Name: "red"},
			{ID: "%s_overall.status.yellow", Name: "yellow"},
			{ID: "%s_overall.status.gray", Name: "gray"},
		},
	}
	hostSystemUptimeChartTmpl = module.Chart{
		ID:       "%s_system_uptime",
		Title:    "ESXi Host system uptime",
		Units:    "seconds",
		Fam:      "hosts uptime",
		Ctx:      "vsphere.host_system_uptime",
		Priority: prioHostSystemUptime,
		Dims: module.Dims{
			{ID: "%s_sys.uptime.latest", Name: "uptime"},
		},
	}
)

const failedUpdatesLimit = 10

func (vs *VSphere) updateCharts() {
	for id, fails := range vs.discoveredHosts {
		if fails >= failedUpdatesLimit {
			vs.removeFromCharts(id)
			delete(vs.charted, id)
			delete(vs.discoveredHosts, id)
			continue
		}

		host := vs.resources.Hosts.Get(id)
		if host == nil || vs.charted[id] || fails != 0 {
			continue
		}

		vs.charted[id] = true
		charts := newHostCharts(host)
		if err := vs.Charts().Add(*charts...); err != nil {
			vs.Error(err)
		}
	}

	for id, fails := range vs.discoveredVMs {
		if fails >= failedUpdatesLimit {
			vs.removeFromCharts(id)
			delete(vs.charted, id)
			delete(vs.discoveredVMs, id)
			continue
		}

		vm := vs.resources.VMs.Get(id)
		if vm == nil || vs.charted[id] || fails != 0 {
			continue
		}

		vs.charted[id] = true
		charts := newVMCHarts(vm)
		if err := vs.Charts().Add(*charts...); err != nil {
			vs.Error(err)
		}
	}
}

func newVMCHarts(vm *rs.VM) *module.Charts {
	charts := vmChartsTmpl.Copy()

	for _, chart := range *charts {
		chart.ID = fmt.Sprintf(chart.ID, vm.ID)
		chart.Labels = []module.Label{
			{Key: "datacenter", Value: vm.Hier.DC.Name},
			{Key: "cluster", Value: getVMClusterName(vm)},
			{Key: "host", Value: vm.Hier.Host.Name},
			{Key: "vm", Value: vm.Name},
		}
		for _, dim := range chart.Dims {
			dim.ID = fmt.Sprintf(dim.ID, vm.ID)
		}
	}

	return charts
}

func getVMClusterName(vm *rs.VM) string {
	if vm.Hier.Cluster.Name == vm.Hier.Host.Name {
		return ""
	}
	return vm.Hier.Cluster.Name
}

func newHostCharts(host *rs.Host) *module.Charts {
	charts := hostChartsTmpl.Copy()

	for _, chart := range *charts {
		chart.ID = fmt.Sprintf(chart.ID, host.ID)
		chart.Labels = []module.Label{
			{Key: "datacenter", Value: host.Hier.DC.Name},
			{Key: "cluster", Value: getHostClusterName(host)},
			{Key: "host", Value: host.Name},
		}

		for _, dim := range chart.Dims {
			dim.ID = fmt.Sprintf(dim.ID, host.ID)
		}
	}

	return charts
}

func getHostClusterName(host *rs.Host) string {
	if host.Hier.Cluster.Name == host.Name {
		return ""
	}
	return host.Hier.Cluster.Name
}

func (vs *VSphere) removeFromCharts(prefix string) {
	for _, c := range *vs.Charts() {
		if strings.HasPrefix(c.ID, prefix) {
			c.MarkRemove()
			c.MarkNotCreated()
		}
	}
}

//func findMetricSeriesByPrefix(ms []performance.MetricSeries, prefix string) []performance.MetricSeries {
//	from := sort.Search(len(ms), func(i int) bool { return ms[i].Name >= prefix })
//
//	if from == len(ms) || !strings.HasPrefix(ms[from].Name, prefix) {
//		return nil
//	}
//
//	until := from + 1
//	for until < len(ms) && strings.HasPrefix(ms[until].Name, prefix) {
//		until++
//	}
//	return ms[from:until]
//}
