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

package scaleio

import (
	"fmt"

	"github.com/netdata/netdata/go/plugins/plugin/go.d/modules/scaleio/client"

	"github.com/netdata/netdata/go/plugins/plugin/go.d/agent/module"
)

type (
	// Charts is an alias for module.Charts.
	Charts = module.Charts
	// Dims is an alias for module.Dims.
	Dims = module.Dims
	// Vars is an alias for module.Vars.
	Vars = module.Vars
)

var (
	prioStoragePool = module.Priority + len(systemCharts) + 10
	prioSdc         = prioStoragePool + len(storagePoolCharts) + 10
)

var systemCharts = Charts{
	// Capacity
	{
		ID:    "system_capacity_total",
		Title: "Total Capacity",
		Units: "KiB",
		Fam:   "capacity",
		Ctx:   "scaleio.system_capacity_total",
		Dims: Dims{
			{ID: "system_capacity_max_capacity", Name: "total"},
		},
	},
	{
		ID:    "system_capacity_in_use",
		Title: "Capacity In Use",
		Units: "KiB",
		Fam:   "capacity",
		Ctx:   "scaleio.system_capacity_in_use",
		Dims: Dims{
			{ID: "system_capacity_in_use", Name: "in_use"},
		},
	},
	{
		ID:    "system_capacity_usage",
		Title: "Capacity Usage",
		Units: "KiB",
		Fam:   "capacity",
		Type:  module.Stacked,
		Ctx:   "scaleio.system_capacity_usage",
		Dims: Dims{
			{ID: "system_capacity_thick_in_use", Name: "thick"},
			{ID: "system_capacity_decreased", Name: "decreased"},
			{ID: "system_capacity_thin_in_use", Name: "thin"},
			{ID: "system_capacity_snapshot", Name: "snapshot"},
			{ID: "system_capacity_spare", Name: "spare"},
			{ID: "system_capacity_unused", Name: "unused"},
		},
	},
	{
		ID:    "system_capacity_available_volume_allocation",
		Title: "Available For Volume Allocation",
		Units: "KiB",
		Fam:   "capacity",
		Ctx:   "scaleio.system_capacity_available_volume_allocation",
		Dims: Dims{
			{ID: "system_capacity_available_for_volume_allocation", Name: "available"},
		},
	},
	{
		ID:    "system_capacity_health_state",
		Title: "Capacity Health State",
		Units: "KiB",
		Fam:   "health",
		Type:  module.Stacked,
		Ctx:   "scaleio.system_capacity_health_state",
		Dims: Dims{
			{ID: "system_capacity_protected", Name: "protected"},
			{ID: "system_capacity_degraded", Name: "degraded"},
			{ID: "system_capacity_in_maintenance", Name: "in_maintenance"},
			{ID: "system_capacity_failed", Name: "failed"},
			{ID: "system_capacity_unreachable_unused", Name: "unavailable"},
		},
	},
	// I/O Workload BW
	{
		ID:    "system_workload_primary_bandwidth_total",
		Title: "Primary Backend Bandwidth Total (Read and Write)",
		Units: "KiB/s",
		Fam:   "workload",
		Ctx:   "scaleio.system_workload_primary_bandwidth_total",
		Dims: Dims{
			{ID: "system_backend_primary_bandwidth_read_write", Name: "total", Div: 1000},
		},
	},
	{
		ID:    "system_workload_primary_bandwidth",
		Title: "Primary Backend Bandwidth",
		Units: "KiB/s",
		Fam:   "workload",
		Ctx:   "scaleio.system_workload_primary_bandwidth",
		Type:  module.Area,
		Dims: Dims{
			{ID: "system_backend_primary_bandwidth_read", Name: "read", Div: 1000},
			{ID: "system_backend_primary_bandwidth_write", Name: "write", Mul: -1, Div: 1000},
		},
	},
	// I/O Workload IOPS
	{
		ID:    "system_workload_primary_iops_total",
		Title: "Primary Backend IOPS Total (Read and Write)",
		Units: "iops/s",
		Fam:   "workload",
		Ctx:   "scaleio.system_workload_primary_iops_total",
		Dims: Dims{
			{ID: "system_backend_primary_iops_read_write", Name: "total", Div: 1000},
		},
	},
	{
		ID:    "system_workload_primary_iops",
		Title: "Primary Backend IOPS",
		Units: "iops/s",
		Fam:   "workload",
		Ctx:   "scaleio.system_workload_primary_iops",
		Type:  module.Area,
		Dims: Dims{
			{ID: "system_backend_primary_iops_read", Name: "read", Div: 1000},
			{ID: "system_backend_primary_iops_write", Name: "write", Mul: -1, Div: 1000},
		},
	},
	{
		ID:    "system_workload_primary_io_size_total",
		Title: "Primary Backend I/O Size Total (Read and Write)",
		Units: "KiB",
		Fam:   "workload",
		Ctx:   "scaleio.system_workload_primary_io_size_total",
		Dims: Dims{
			{ID: "system_backend_primary_io_size_read_write", Name: "io_size", Div: 1000},
		},
	},
	// Rebalance
	{
		ID:    "system_rebalance",
		Title: "Rebalance",
		Units: "KiB/s",
		Fam:   "rebalance",
		Type:  module.Area,
		Ctx:   "scaleio.system_rebalance",
		Dims: Dims{
			{ID: "system_rebalance_bandwidth_read", Name: "read", Div: 1000},
			{ID: "system_rebalance_bandwidth_write", Name: "write", Mul: -1, Div: 1000},
		},
	},
	{
		ID:    "system_rebalance_left",
		Title: "Rebalance Pending Capacity",
		Units: "KiB",
		Fam:   "rebalance",
		Ctx:   "scaleio.system_rebalance_left",
		Dims: Dims{
			{ID: "system_rebalance_pending_capacity_in_Kb", Name: "left"},
		},
	},
	{
		ID:    "system_rebalance_time_until_finish",
		Title: "Rebalance Approximate Time Until Finish",
		Units: "seconds",
		Fam:   "rebalance",
		Ctx:   "scaleio.system_rebalance_time_until_finish",
		Dims: Dims{
			{ID: "system_rebalance_time_until_finish", Name: "time"},
		},
	},
	// Rebuild
	{
		ID:    "system_rebuild",
		Title: "Rebuild Bandwidth Total (Forward, Backward and Normal)",
		Units: "KiB/s",
		Fam:   "rebuild",
		Ctx:   "scaleio.system_rebuild",
		Type:  module.Area,
		Dims: Dims{
			{ID: "system_rebuild_total_bandwidth_read", Name: "read", Div: 1000},
			{ID: "system_rebuild_total_bandwidth_write", Name: "write", Mul: -1, Div: 1000},
		},
	},
	{
		ID:    "system_rebuild_left",
		Title: "Rebuild Pending Capacity Total (Forward, Backward and Normal)",
		Units: "KiB",
		Fam:   "rebuild",
		Ctx:   "scaleio.system_rebuild_left",
		Dims: Dims{
			{ID: "system_rebuild_total_pending_capacity_in_Kb", Name: "left"},
		},
	},
	// Components
	{
		ID:    "system_defined_components",
		Title: "Components",
		Units: "components",
		Fam:   "components",
		Ctx:   "scaleio.system_defined_components",
		Dims: Dims{
			{ID: "system_num_of_devices", Name: "devices"},
			{ID: "system_num_of_fault_sets", Name: "fault_sets"},
			{ID: "system_num_of_protection_domains", Name: "protection_domains"},
			{ID: "system_num_of_rfcache_devices", Name: "rfcache_devices"},
			{ID: "system_num_of_sdc", Name: "sdc"},
			{ID: "system_num_of_sds", Name: "sds"},
			{ID: "system_num_of_snapshots", Name: "snapshots"},
			{ID: "system_num_of_storage_pools", Name: "storage_pools"},
			{ID: "system_num_of_volumes", Name: "volumes"},
			{ID: "system_num_of_vtrees", Name: "vtrees"},
		},
	},
	{
		ID:    "system_components_volumes_by_type",
		Title: "Volumes By Type",
		Units: "volumes",
		Fam:   "components",
		Ctx:   "scaleio.system_components_volumes_by_type",
		Type:  module.Stacked,
		Dims: Dims{
			{ID: "system_num_of_thick_base_volumes", Name: "thick"},
			{ID: "system_num_of_thin_base_volumes", Name: "thin"},
		},
	},
	{
		ID:    "system_components_volumes_by_mapping",
		Title: "Volumes By Mapping",
		Units: "volumes",
		Fam:   "components",
		Ctx:   "scaleio.system_components_volumes_by_mapping",
		Type:  module.Stacked,
		Dims: Dims{
			{ID: "system_num_of_mapped_volumes", Name: "mapped"},
			{ID: "system_num_of_unmapped_volumes", Name: "unmapped"},
		},
	},
}

var storagePoolCharts = Charts{
	{
		ID:    "storage_pool_%s_capacity_total",
		Title: "Total Capacity",
		Units: "KiB",
		Fam:   "pool %s",
		Ctx:   "scaleio.storage_pool_capacity_total",
		Dims: Dims{
			{ID: "storage_pool_%s_capacity_max_capacity", Name: "total"},
		},
	},
	{
		ID:    "storage_pool_%s_capacity_in_use",
		Title: "Capacity In Use",
		Units: "KiB",
		Fam:   "pool %s",
		Ctx:   "scaleio.storage_pool_capacity_in_use",
		Dims: Dims{
			{ID: "storage_pool_%s_capacity_in_use", Name: "in_use"},
		},
	},
	{
		ID:    "storage_pool_%s_capacity_usage",
		Title: "Capacity Usage",
		Units: "KiB",
		Fam:   "pool %s",
		Type:  module.Stacked,
		Ctx:   "scaleio.storage_pool_capacity_usage",
		Dims: Dims{
			{ID: "storage_pool_%s_capacity_thick_in_use", Name: "thick"},
			{ID: "storage_pool_%s_capacity_decreased", Name: "decreased"},
			{ID: "storage_pool_%s_capacity_thin_in_use", Name: "thin"},
			{ID: "storage_pool_%s_capacity_snapshot", Name: "snapshot"},
			{ID: "storage_pool_%s_capacity_spare", Name: "spare"},
			{ID: "storage_pool_%s_capacity_unused", Name: "unused"},
		},
	},
	{
		ID:    "storage_pool_%s_capacity_utilization",
		Title: "Capacity Utilization",
		Units: "percentage",
		Fam:   "pool %s",
		Ctx:   "scaleio.storage_pool_capacity_utilization",
		Dims: Dims{
			{ID: "storage_pool_%s_capacity_utilization", Name: "used", Div: 100},
		},
		Vars: Vars{
			{ID: "storage_pool_%s_capacity_alert_high_threshold"},
			{ID: "storage_pool_%s_capacity_alert_critical_threshold"},
		},
	},
	{
		ID:    "storage_pool_%s_capacity_available_volume_allocation",
		Title: "Available For Volume Allocation",
		Units: "KiB",
		Fam:   "pool %s",
		Ctx:   "scaleio.storage_pool_capacity_available_volume_allocation",
		Dims: Dims{
			{ID: "storage_pool_%s_capacity_available_for_volume_allocation", Name: "available"},
		},
	},
	{
		ID:    "storage_pool_%s_capacity_health_state",
		Title: "Capacity Health State",
		Units: "KiB",
		Fam:   "pool %s",
		Type:  module.Stacked,
		Ctx:   "scaleio.storage_pool_capacity_health_state",
		Dims: Dims{
			{ID: "storage_pool_%s_capacity_protected", Name: "protected"},
			{ID: "storage_pool_%s_capacity_degraded", Name: "degraded"},
			{ID: "storage_pool_%s_capacity_in_maintenance", Name: "in_maintenance"},
			{ID: "storage_pool_%s_capacity_failed", Name: "failed"},
			{ID: "storage_pool_%s_capacity_unreachable_unused", Name: "unavailable"},
		},
	},
	{
		ID:    "storage_pool_%s_components",
		Title: "Components",
		Units: "components",
		Fam:   "pool %s",
		Ctx:   "scaleio.storage_pool_components",
		Dims: Dims{
			{ID: "storage_pool_%s_num_of_devices", Name: "devices"},
			{ID: "storage_pool_%s_num_of_snapshots", Name: "snapshots"},
			{ID: "storage_pool_%s_num_of_volumes", Name: "volumes"},
			{ID: "storage_pool_%s_num_of_vtrees", Name: "vtrees"},
		},
	},
}

func newStoragePoolCharts(pool client.StoragePool) *Charts {
	charts := storagePoolCharts.Copy()
	for i, chart := range *charts {
		chart.ID = fmt.Sprintf(chart.ID, pool.ID)
		chart.Fam = fmt.Sprintf(chart.Fam, pool.Name)
		chart.Priority = prioStoragePool + i
		for _, dim := range chart.Dims {
			dim.ID = fmt.Sprintf(dim.ID, pool.ID)
		}
		for _, v := range chart.Vars {
			v.ID = fmt.Sprintf(v.ID, pool.ID)
		}
	}
	return charts
}

var sdcCharts = Charts{
	{
		ID:    "sdc_%s_mdm_connection_state",
		Title: "MDM Connection State",
		Units: "boolean",
		Fam:   "sdc %s",
		Ctx:   "scaleio.sdc_mdm_connection_state",
		Dims: Dims{
			{ID: "sdc_%s_mdm_connection_state", Name: "connected"},
		},
	},
	{
		ID:    "sdc_%s_bandwidth",
		Title: "Bandwidth",
		Units: "KiB/s",
		Fam:   "sdc %s",
		Ctx:   "scaleio.sdc_bandwidth",
		Type:  module.Area,
		Dims: Dims{
			{ID: "sdc_%s_bandwidth_read", Name: "read", Div: 1000},
			{ID: "sdc_%s_bandwidth_write", Name: "write", Mul: -1, Div: 1000},
		},
	},
	{
		ID:    "sdc_%s_iops",
		Title: "IOPS",
		Units: "iops/s",
		Fam:   "sdc %s",
		Ctx:   "scaleio.sdc_iops",
		Type:  module.Area,
		Dims: Dims{
			{ID: "sdc_%s_iops_read", Name: "read", Div: 1000},
			{ID: "sdc_%s_iops_write", Name: "write", Mul: -1, Div: 1000},
		},
	},
	{
		ID:    "sdc_%s_io_size",
		Title: "I/O Size",
		Units: "KiB",
		Fam:   "sdc %s",
		Ctx:   "scaleio.sdc_io_size",
		Type:  module.Area,
		Dims: Dims{
			{ID: "sdc_%s_io_size_read", Name: "read", Div: 1000},
			{ID: "sdc_%s_io_size_write", Name: "write", Mul: -1, Div: 1000},
		},
	},
	{
		ID:    "sdc_%s_num_of_mapped_volumed",
		Title: "Mapped Volumes",
		Units: "volumes",
		Fam:   "sdc %s",
		Ctx:   "scaleio.sdc_num_of_mapped_volumed",
		Dims: Dims{
			{ID: "sdc_%s_num_of_mapped_volumes", Name: "mapped"},
		},
	},
}

func newSdcCharts(sdc client.Sdc) *Charts {
	charts := sdcCharts.Copy()
	for i, chart := range *charts {
		chart.ID = fmt.Sprintf(chart.ID, sdc.ID)
		chart.Fam = fmt.Sprintf(chart.Fam, sdc.SdcIp)
		chart.Priority = prioSdc + i
		for _, dim := range chart.Dims {
			dim.ID = fmt.Sprintf(dim.ID, sdc.ID)
		}
	}
	return charts
}

// TODO: remove stale charts?
func (s *ScaleIO) updateCharts() {
	s.updateStoragePoolCharts()
	s.updateSdcCharts()
}

func (s *ScaleIO) updateStoragePoolCharts() {
	for _, pool := range s.discovered.pool {
		if s.charted[pool.ID] {
			continue
		}
		s.charted[pool.ID] = true
		s.addStoragePoolCharts(pool)
	}
}

func (s *ScaleIO) updateSdcCharts() {
	for _, sdc := range s.discovered.sdc {
		if s.charted[sdc.ID] {
			continue
		}
		s.charted[sdc.ID] = true
		s.addSdcCharts(sdc)
	}
}

func (s *ScaleIO) addStoragePoolCharts(pool client.StoragePool) {
	charts := newStoragePoolCharts(pool)
	if err := s.Charts().Add(*charts...); err != nil {
		s.Warningf("couldn't add charts for storage pool '%s(%s)': %v", pool.ID, pool.Name, err)
	}
}

func (s *ScaleIO) addSdcCharts(sdc client.Sdc) {
	charts := newSdcCharts(sdc)
	if err := s.Charts().Add(*charts...); err != nil {
		s.Warningf("couldn't add charts for sdc '%s(%s)': %v", sdc.ID, sdc.SdcIp, err)
	}
}
