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

package discover

import (
	"time"

	rs "github.com/netdata/netdata/go/plugins/plugin/go.d/collector/vsphere/resources"

	"github.com/vmware/govmomi/vim25/mo"
)

func (d Discoverer) build(raw *resources) *rs.Resources {
	d.Debug("discovering : building : starting building resources process")
	t := time.Now()

	var res rs.Resources
	res.DataCenters = d.buildDatacenters(raw.dcs)
	res.Folders = d.buildFolders(raw.folders)
	res.Clusters = d.buildClusters(raw.clusters)
	fixClustersParentID(&res)
	res.Hosts = d.buildHosts(raw.hosts)
	res.VMs = d.buildVMs(raw.vms)
	res.Datastores = d.buildDatastores(raw.datastores)
	res.ResourcePools = d.buildResourcePools(raw.resourcePools, res.Clusters)

	d.Infof("discovering : building : built %d/%d dcs, %d/%d folders, %d/%d clusters, %d/%d hosts, %d/%d vms, %d/%d datastores, %d/%d resource pools, process took %s",
		len(res.DataCenters),
		len(raw.dcs),
		len(res.Folders),
		len(raw.folders),
		len(res.Clusters),
		len(raw.clusters),
		len(res.Hosts),
		len(raw.hosts),
		len(res.VMs),
		len(raw.vms),
		len(res.Datastores),
		len(raw.datastores),
		len(res.ResourcePools),
		len(raw.resourcePools),
		time.Since(t),
	)
	return &res
}

// cluster parent is folder by default
// should be called after buildDatacenters, buildFolders and buildClusters
func fixClustersParentID(res *rs.Resources) {
	for _, c := range res.Clusters {
		c.ParentID = findClusterDcID(c.ParentID, res.Folders)
	}
}

func findClusterDcID(parentID string, folders rs.Folders) string {
	f := folders.Get(parentID)
	if f == nil {
		return parentID
	}
	return findClusterDcID(f.ParentID, folders)
}

func (Discoverer) buildDatacenters(raw []mo.Datacenter) rs.DataCenters {
	dcs := make(rs.DataCenters)
	for _, d := range raw {
		dcs.Put(newDC(d))
	}
	return dcs
}

func newDC(raw mo.Datacenter) *rs.Datacenter {
	// Datacenter1 datacenter-2 group-h4 group-v3
	return &rs.Datacenter{
		Name: raw.Name,
		ID:   raw.Reference().Value,
	}
}

func (Discoverer) buildFolders(raw []mo.Folder) rs.Folders {
	fs := make(rs.Folders)
	for _, d := range raw {
		fs.Put(newFolder(d))
	}
	return fs
}

func newFolder(raw mo.Folder) *rs.Folder {
	// vm group-v55 datacenter-54
	// host group-h56 datacenter-54
	// datastore group-s57 datacenter-54
	// network group-n58 datacenter-54
	return &rs.Folder{
		Name:     raw.Name,
		ID:       raw.Reference().Value,
		ParentID: raw.Parent.Value,
	}
}

func (Discoverer) buildClusters(raw []mo.ComputeResource) rs.Clusters {
	clusters := make(rs.Clusters)
	for _, c := range raw {
		clusters.Put(newCluster(c))
	}
	return clusters
}

func newCluster(raw mo.ComputeResource) *rs.Cluster {
	// s - dummy cluster, c - created by user cluster
	// 192.168.0.201 domain-s61 group-h4
	// New Cluster1 domain-c52 group-h67
	return &rs.Cluster{
		Name:     raw.Name,
		ID:       raw.Reference().Value,
		ParentID: raw.Parent.Value,
		Ref:      raw.Reference(),
	}
}

const (
	poweredOn = "poweredOn"
)

func (d Discoverer) buildHosts(raw []mo.HostSystem) rs.Hosts {
	var num int
	hosts := make(rs.Hosts)
	for _, h := range raw {
		//	poweredOn | poweredOff | standBy | unknown
		if h.Runtime.PowerState != poweredOn {
			num++
			continue
		}
		// connected | notResponding | disconnected
		//if v.Runtime.ConnectionState == "" {
		//
		//}
		hosts.Put(newHost(h))
	}
	if num > 0 {
		d.Infof("discovering : building : removed %d hosts (not powered on)", num)
	}
	return hosts
}

func newHost(raw mo.HostSystem) *rs.Host {
	// 192.168.0.201 host-22 domain-s61
	// 192.168.0.202 host-28 domain-c52
	// 192.168.0.203 host-33 domain-c52
	return &rs.Host{
		Name:          raw.Name,
		ID:            raw.Reference().Value,
		ParentID:      raw.Parent.Value,
		OverallStatus: string(raw.Summary.OverallStatus),
		Ref:           raw.Reference(),
	}
}

func (d Discoverer) buildVMs(raw []mo.VirtualMachine) rs.VMs {
	var num int
	vms := make(rs.VMs)
	for _, v := range raw {
		//  poweredOff | poweredOn | suspended
		if v.Runtime.PowerState != poweredOn {
			num++
			continue
		}
		// connected | disconnected | orphaned | inaccessible | invalid
		//if v.Runtime.ConnectionState == "" {
		//
		//}
		vms.Put(newVM(v))
	}
	if num > 0 {
		d.Infof("discovering : building : removed %d vms (not powered on)", num)
	}
	return vms
}

func newVM(raw mo.VirtualMachine) *rs.VM {
	// deb91 vm-25 group-v3 host-22
	return &rs.VM{
		Name:          raw.Name,
		ID:            raw.Reference().Value,
		ParentID:      raw.Runtime.Host.Value,
		OverallStatus: string(raw.Summary.OverallStatus),
		Ref:           raw.Reference(),
	}
}

func (d Discoverer) buildDatastores(raw []mo.Datastore) rs.Datastores {
	var num int
	datastores := make(rs.Datastores)
	for _, ds := range raw {
		if !ds.Summary.Accessible {
			num++
			continue
		}
		datastores.Put(newDatastore(ds))
	}
	if num > 0 {
		d.Infof("discovering : building : removed %d datastores (not accessible)", num)
	}
	return datastores
}

func newDatastore(raw mo.Datastore) *rs.Datastore {
	return &rs.Datastore{
		Name:          raw.Name,
		ID:            raw.Reference().Value,
		ParentID:      raw.Parent.Value,
		OverallStatus: string(raw.OverallStatus),
		Type:          raw.Summary.Type,
		Capacity:      raw.Summary.Capacity,
		FreeSpace:     raw.Summary.FreeSpace,
		Accessible:    raw.Summary.Accessible,
		Ref:           raw.Reference(),
	}
}

func (d Discoverer) buildResourcePools(raw []mo.ResourcePool, clusters rs.Clusters) rs.ResourcePools {
	pools := make(rs.ResourcePools)
	for _, rp := range raw {
		// owner is the cluster that owns this pool
		ownerID := rp.Owner.Value
		// skip pools whose owner is a dummy cluster (standalone host)
		if isDummyCluster(ownerID) {
			continue
		}
		// skip pools whose owner cluster wasn't discovered
		if clusters.Get(ownerID) == nil {
			continue
		}
		pools.Put(newResourcePool(rp))
	}
	return pools
}

func newResourcePool(raw mo.ResourcePool) *rs.ResourcePool {
	return &rs.ResourcePool{
		Name:     raw.Name,
		ID:       raw.Reference().Value,
		ParentID: raw.Owner.Value, // owner cluster ref
		Ref:      raw.Reference(),
	}
}
