package config

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"os"
	"strconv"
	"strings"
	"time"

	"gopkg.in/yaml.v2"

	"github.com/guo-shaoge/tiflash-autoscale/pkg/util/log/logger"
)

// TODO delete env and use config for all items.
const (
	EnvKeySupervisorImage  string = "TIFLASH_SUPERVISOR_IMAGE"
	EnvKeyPDAddr                  = "PD_ADDR"
	EnvKeyWarmupPoolCap           = "TIFLASH_AS_WARMPOOL_CAP"
	EnvKeyEnableIstioProxy        = "TIFLASH_ENABLE_ISTIO_PROXY"
	EnvKeyEnableTLS               = "TIFLASH_ENABLE_TLS"
	EnvKeyCloudProvider           = "TIFLASH_CLOUD_PROVIDER"
	EnvKeyRegion                  = "TIFLASH_AS_REGION"
)

type configInterface interface {
	setupDefault()
	verify()
}

var _ configInterface = &Config{}
var _ configInterface = &ClusterConfig{}
var _ configInterface = &SharePoolConfig{}
var _ configInterface = &TenantConfig{}

// How to add config:
// 1. decide whether it's cluster level or tenantlevel
// 2. add init logic in ClusterConfig.setupDefault() or TenantConfig.setupDefault()/setupDefaultByParen()
// 3. add check logic in verify()
type Config struct {
	updateTs time.Time

	ClusterConfig *ClusterConfig  `json:"cluster_config,omitempty" yaml:"cluster_config,omitempty"`
	TenantConfigs []*TenantConfig `json:"tenant_configs,omitempty" yaml:"tenant_configs,omitempty"`
}

type ClusterConfig struct {
	PDAddr *string `json:"pd_addr,omitempty" yaml:"pd_addr,omitempty"`

	CoreNumPerPod *uint `json:"core_num_per_pod,omitempty" yaml:"core_num_per_pod,omitempty"`

	PodCpuRequest *string `json:"pod_cpu_request,omitempty" yaml:"pod_cpu_request,omitempty"`

	PodMemoryRequest *string `json:"pod_memory_request,omitempty" yaml:"pod_memory_request,omitempty"`
	PodMemoryLimit   *string `json:"pod_memory_limit,omitempty" yaml:"pod_memory_limit,omitempty"`

	// negative means unlimited.
	// zero is invalid.
	ClusterMaxPods *int `json:"cluster_max_pods,omitempty" yaml:"cluster_max_pods,omitempty"`

	IsMinikubeTest *bool `json:"is_minikube_test,omitempty" yaml:"is_minikube_test,omitempty"`

	WarmupIntervalSec *uint `json:"warmup_interval_sec,omitempty" yaml:"warmup_interval_sec,omitempty"`
	WarmupPoolCap     *uint `json:"warmup_pool_cap,omitempty" yaml:"warmup_pool_cap,omitempty"`

	// If a Pod keeps be Pending for x seconds,
	// AutoScaler will delete it automatically.
	PendingPodTimeoutSec *uint `json:"pending_pod_timeout,omitempty" yaml:"pending_pod_timeout,omitempty"`

	// Every WarmupPoolOnePodCoolDownSec, autoscaler will check if there are any ready pods in warmup pool.
	// If not, autoscaler will get one on pod automatically.
	WarmupPoolOnePodCoolDownSec *uint `json:"warmup_pool_one_pod_cool_down_sec,omitempty" yaml:"warmup_pool_one_pod_cool_down_sec,omitempty"`

	EnableIstioProxy *bool
	EnableTLS        *bool

	SupervisorImage *string `json:"supervisor_image,omitempty" yaml:"supervisor_image,omitempty"`

	LogPath  *string `json:"log_path,omitempty" yaml:"log_path,omitempty"`
	LogLevel *string `json:"log_level,omitempty" yaml:"log_level,omitempty"`

	CNLocalCachePath *string `json:"cn_local_cache_path,omitempty" yaml:"cn_local_cache_path,omitempty"`
	CNLocalCacheSize *string `json:"cn_local_cache_size,omitempty" yaml:"cn_local_cache_size,omitempty"`

	PromAddr            *string `json:"prom_addr,omitempty" yaml:"prom_addr,omitempty"`
	PromQueryTimeoutSec *uint   `json:"prom_query_timeout_sec,omitempty" yaml:"prom_query_timeout_sec,omitempty"`

	ConfigCheckIntervalSec *uint `json:"config_check_interval,omitempty" yaml:"config_check_interval,omitempty"`
	// To help trigger update new config.
	ConfigVersion *uint64 `json:"config_version,omitempty" yaml:"config_version,omitempty"`

	HttpServerTimeoutSec *uint   `json:"http_server_timeout_sec,omitempty" yaml:"http_server_timeout_sec,omitempty"`
	HttpPort             *uint32 `json:"http_port,omitempty" yaml:"http_port,omitempty"`

	// if the meta information of an existing pod cannot be found in the AutoScaler within
	// the LostPodWindowSec time period, the AutoScaler considers that the pod has been lost.
	LostPodWindowSec *uint `json:"lost_pod_window_sec,omitempty" yaml:"lost_pod_window_sec,omitempty"`
	// inteval of podScanner:
	// 1. podScanner(move pods from different pools)
	// 2. check lost pods.
	PodScannerIntervalSec *uint `json:"lost_pod_check_interval_sec,omitempty" yaml:"lost_pod_check_interval_sec,omitempty"`

	MetricIntervalSec  *uint `json:"metric_interval_sec,omitempty" yaml:"metric_interval_sec,omitempty"`
	MetricWindowSec    *uint `json:"scale_window_sec,omitempty" yaml:"scale_window_sec,omitempty"`
	AutoPauseWindowSec *uint `json:"auto_pause_window_sec,omitempty" yaml:"auto_pause_window_sec,omitempty"`

	MemLimitRecoveryCoolDownSec *uint `json:"mem_limit_cool_down_sec,omitempty" yaml:"mem_limit_cool_down_sec,omitempty"`

	SharePoolConfig *SharePoolConfig `json:"share_pool_config,omitempty" yaml:"share_pool_config,omitempty"`
	TenantConfig    *TenantConfig    `json:"tenant_config,omitempty" yaml:"tenant_config,omitempty"`

	CloudProvider *string `json:"cloud_provider,omitempty" yaml:"cloud_provider,omitempty"`
	Region        *string `json:"region,omitempty" yaml:"region,omitempty"`

	NodeSelector map[string]string `json:"node_selector,omitempty" yaml:"node_selector,omitempty"`
}

type SharePoolConfig struct {
	// start to merge sharing pods when total CPU of SharePool exceeds this config.
	SoftHighCoreNum *uint `json:"soft_high_core_num,omitempty" yaml:"soft_high_core_num,omitempty"`

	// TODO MergeLowCPULimit same with ScaleLowCPULimit; MergeHighCPULimit same with ScaleHighCPULimit.
	// workload of other pod will be merged into this pod if this pod's CPU is less equal to this value.
	MergeLowCPULimit *float64 `json:"merge_low_cpu_limit,omitempty" yaml:"merge_low_cpu_limit,omitempty"`
	// process of merging to this pod will stop if merged CPU is greater equal to this value.
	MergeHighCPULimit *float64 `json:"merge_high_cpu_limit,omitempty" yaml:"merge_high_cpu_limit,omitempty"`

	// a pod will skip merge other tenants if still during cooldown duration since last merge.
	MergeCoolDownSec *uint `json:"merge_cool_down_sec,omitempty" yaml:"merge_cool_down_sec,omitempty"`

	EnableMerge *bool `json:"enable_merge,omitempty" yaml:"enable_merge,omitempty"`
	EnableEvict *bool `json:"enable_evict,omitempty" yaml:"enable_evict,omitempty"`
	// a pod will skip being a share candidate if still during cooldown duration since last share.
	ShareCoolDownSec *uint `json:"share_cool_down_sec,omitempty" yaml:"share_cool_down_sec,omitempty"`

	// One pod is ok to share if its avg cpu is less equal to this value.
	ShareLowCPULimit *float64 `json:"share_low_cpu_limit,omitempty" yaml:"share_low_cpu_limit,omitempty"`
	// One pod will start to evict tenants when its avg cpu is greater equatl to this value.
	EvictHighCPULimit *float64 `json:"evict_high_cpu_limit,omitempty" yaml:"evict_high_cpu_limit,omitempty"`
}

type TenantConfig struct {
	TenantName      *string `json:"tenant_name" yaml:"tenant_name"`
	EnableAutoPause *bool   `json:"enable_auto_pause,omitempty" yaml:"enable_auto_pause,omitempty"`
	EnableShare     *bool   `json:"enable_share,omitempty" yaml:"enable_share,omitempty"`
	EnableScale     *bool   `json:"enable_scale,omitempty" yaml:"enable_scale,omitempty"`

	StillShareWhenMetricNotEnough *bool `json:"still_share_when_metric_not_enough,omitempty" yaml:"still_share_when_metric_not_enough,omitempty"`

	ScaleLowCPULimit  *float64 `json:"scale_low_cpu_limit,omitempty" yaml:"scale_low_cpu_limit,omitempty"`
	ScaleHighCPULimit *float64 `json:"scale_high_cpu_limit,omitempty" yaml:"scale_high_cpu_limit,omitempty"`

	TenantInitCoreNum *uint `json:"tenant_init_core_num,omitempty" yaml:"tenant_init_core_num,omitempty"`
	TenantMinCoreNum  *uint `json:"tenant_min_core_num,omitempty" yaml:"tenant_min_core_num,omitempty"`
	TenantMaxCoreNum  *uint `json:"tenant_max_core_num,omitempty" yaml:"tenant_max_core_num,omitempty"`
}

var (
	// ClusterConfig
	defPDAddr = "serverless-cluster-pd.tidb-serverless.svc:2379"

	defLogPath                       = "./"
	defLogLevel                      = "debug"
	defPromAddr                      = "http://as-prometheus.tiflash-autoscale.svc.cluster.local:16292"
	defPromQueryTimeoutSec    uint   = 15
	defConfigCheckIntervalSec uint   = 3
	defConfigVersion          uint64 = 0
	defIsMinikubeTest         bool   = false
	defClusterMaxPods         int    = -1

	defCoreNumPerPod         uint   = 8
	defHttpServerTimeoutSec  uint   = 30
	defHttpPort              uint32 = 8081
	defLostPodWindowSec      uint   = 30
	defPodScannerIntervalSec uint   = 5
	defWarmupIntervalSec     uint   = 5
	defWarmupPoolCap         uint   = 1

	// timeout is 6mins
	defPendingPodTimeoutSec        uint = 360
	defWarmupPoolOnePodCoolDownSec uint = 60

	defMetricWindowSec    uint = 60
	defAutoPauseWindowSec uint = 180
	defMetricIntervalSec  uint = 10

	defEnableIstioProxy = false
	defEnableTLS        = false

	defCNLocalCachePath = "/data/cache"
	defCNLocalCacheSize = "63350767616"

	// ClusterConfig.SharePoolConfig
	defEnableMerge bool = true
	defEnableEvict bool = true

	defShareLowCPULimit  float64 = 0.4
	defEvictHighCPULimit float64 = 0.9

	defSharePoolSoftHighCoreNum   uint    = defCoreNumPerPod * 15
	defSharePoolMergeCoolDownSec  uint    = 15
	defSharePoolShareCoolDownSec  uint    = 15
	defSharePoolMergeLowCPULimit  float64 = 0.2
	defSharePoolMergeHighCPULimit float64 = 0.7

	// TenantConfig
	defEnableAutoPause bool = true
	defEnableShare     bool = true
	defEnableScale     bool = true

	defStillShareWhenMetricNotEnough bool = true

	defScaleLowCPULimit  float64 = 0.2
	defScaleHighCPULimit float64 = 0.8

	defTenantInitCoreNum uint = defCoreNumPerPod
	defTenantMinCoreNum  uint = defCoreNumPerPod
	defTenantMaxCoreNum  uint = defCoreNumPerPod * 5

	defMemLimitRecoveryCoolDownSec uint = 60
)

func (c *Config) setupDefault() {
	c.updateTs = time.Now()

	if c.ClusterConfig == nil {
		panic("cluster config cannot be nil")
	}
	c.ClusterConfig.setupDefault()

	for i := 0; i < len(c.TenantConfigs); i++ {
		if c.TenantConfigs[i] == nil {
			panic(fmt.Sprintf("tenant_config[%v] is nil", i))
		}
		c.TenantConfigs[i].setupDefaultByCluster(c.ClusterConfig.TenantConfig)
	}
}

func (c *ClusterConfig) setupDefault() {
	if c.PDAddr == nil {
		c.PDAddr = &defPDAddr
	}
	if c.IsMinikubeTest == nil {
		c.IsMinikubeTest = &defIsMinikubeTest
	}
	if c.ClusterMaxPods == nil {
		c.ClusterMaxPods = &defClusterMaxPods
	}

	if c.LogPath == nil {
		c.LogPath = &defLogPath
	}
	if c.LogLevel == nil {
		c.LogLevel = &defLogLevel
	}
	if c.PromAddr == nil {
		c.PromAddr = &defPromAddr
	}
	if c.PromQueryTimeoutSec == nil {
		c.PromQueryTimeoutSec = &defPromQueryTimeoutSec
	}

	if c.CNLocalCachePath == nil {
		c.CNLocalCachePath = &defCNLocalCachePath
	}
	if c.CNLocalCacheSize == nil {
		c.CNLocalCacheSize = &defCNLocalCacheSize
	}

	if c.SupervisorImage == nil || len(*c.SupervisorImage) == 0 {
		panic("cluster.supervisor_image cannot be empty")
	}
	if c.LostPodWindowSec == nil {
		c.LostPodWindowSec = &defLostPodWindowSec
	}
	if c.EnableIstioProxy == nil {
		c.EnableIstioProxy = &defEnableIstioProxy
	}
	if c.EnableTLS == nil {
		c.EnableTLS = &defEnableTLS
	}
	if c.PodScannerIntervalSec == nil {
		c.PodScannerIntervalSec = &defPodScannerIntervalSec
	}
	if c.WarmupIntervalSec == nil {
		c.WarmupIntervalSec = &defWarmupIntervalSec
	}
	if c.WarmupPoolCap == nil {
		c.WarmupPoolCap = &defWarmupPoolCap
	}
	if c.PendingPodTimeoutSec == nil {
		c.PendingPodTimeoutSec = &defPendingPodTimeoutSec
	}
	if c.WarmupPoolOnePodCoolDownSec == nil {
		c.WarmupPoolOnePodCoolDownSec = &defWarmupPoolOnePodCoolDownSec
	}
	if c.ConfigCheckIntervalSec == nil {
		c.ConfigCheckIntervalSec = &defConfigCheckIntervalSec
	}
	if c.ConfigVersion == nil {
		c.ConfigVersion = &defConfigVersion
	}
	if c.CoreNumPerPod == nil {
		c.CoreNumPerPod = &defCoreNumPerPod
	}
	if c.HttpServerTimeoutSec == nil {
		c.HttpServerTimeoutSec = &defHttpServerTimeoutSec
	}
	if c.HttpPort == nil {
		c.HttpPort = &defHttpPort
	}
	if c.MetricIntervalSec == nil {
		c.MetricIntervalSec = &defMetricIntervalSec
	}
	if c.MetricWindowSec == nil {
		c.MetricWindowSec = &defMetricWindowSec
	}
	if c.AutoPauseWindowSec == nil {
		c.AutoPauseWindowSec = &defAutoPauseWindowSec
	}
	if c.MemLimitRecoveryCoolDownSec == nil {
		c.MemLimitRecoveryCoolDownSec = &defMemLimitRecoveryCoolDownSec
	}
	if c.SharePoolConfig == nil {
		c.SharePoolConfig = &SharePoolConfig{}
	}
	c.SharePoolConfig.setupDefault()

	if c.TenantConfig == nil {
		c.TenantConfig = &TenantConfig{}
	}
	c.TenantConfig.setupDefault()

	if c.CloudProvider == nil {
		c.CloudProvider = new(string)
	}

	if c.Region == nil {
		c.Region = new(string)
	}

	if c.NodeSelector == nil {
		c.NodeSelector = make(map[string]string)
	}
	if len(c.NodeSelector) == 0 {
		c.NodeSelector["tiflash.used-for-compute"] = "true"
	}
}

func (c *SharePoolConfig) setupDefault() {
	if c.SoftHighCoreNum == nil {
		c.SoftHighCoreNum = &defSharePoolSoftHighCoreNum
	}
	if c.MergeLowCPULimit == nil {
		c.MergeLowCPULimit = &defSharePoolMergeLowCPULimit
	}
	if c.MergeHighCPULimit == nil {
		c.MergeHighCPULimit = &defSharePoolMergeHighCPULimit
	}
	if c.MergeCoolDownSec == nil {
		c.MergeCoolDownSec = &defSharePoolMergeCoolDownSec
	}
	if c.ShareCoolDownSec == nil {
		c.ShareCoolDownSec = &defSharePoolShareCoolDownSec
	}
	if c.EnableMerge == nil {
		c.EnableMerge = &defEnableMerge
	}
	if c.EnableEvict == nil {
		c.EnableEvict = &defEnableEvict
	}
	if c.ShareLowCPULimit == nil {
		c.ShareLowCPULimit = &defShareLowCPULimit
	}
	if c.EvictHighCPULimit == nil {
		c.EvictHighCPULimit = &defEvictHighCPULimit
	}
}

// ClusterConfig's TenantConfig use this func.
func (c *TenantConfig) setupDefault() {
	// skip check c.TenantName, only check in sub TenantConfig
	if c.EnableAutoPause == nil {
		c.EnableAutoPause = &defEnableAutoPause
	}
	if c.EnableShare == nil {
		c.EnableShare = &defEnableShare
	}
	if c.EnableScale == nil {
		c.EnableScale = &defEnableScale
	}
	if c.StillShareWhenMetricNotEnough == nil {
		c.StillShareWhenMetricNotEnough = &defStillShareWhenMetricNotEnough
	}
	if c.ScaleLowCPULimit == nil {
		c.ScaleLowCPULimit = &defScaleLowCPULimit
	}
	if c.ScaleHighCPULimit == nil {
		c.ScaleHighCPULimit = &defScaleHighCPULimit
	}
	if c.TenantInitCoreNum == nil {
		c.TenantInitCoreNum = &defTenantInitCoreNum
	}
	if c.TenantMinCoreNum == nil {
		c.TenantMinCoreNum = &defTenantMinCoreNum
	}
	if c.TenantMaxCoreNum == nil {
		c.TenantMaxCoreNum = &defTenantMaxCoreNum
	}
}

// tenant's TenantConfig use this.
func (c *TenantConfig) setupDefaultByCluster(parent *TenantConfig) {
	if c.TenantName == nil {
		panic("tenant_config.tenant_name is nil")
	}
	if c.EnableAutoPause == nil {
		if parent.EnableAutoPause == nil {
			panic("parent.EnableAutoPause is nil")
		}
		c.EnableAutoPause = parent.EnableAutoPause
	}
	if c.EnableShare == nil {
		if parent.EnableShare == nil {
			panic("parent.EnableShare is nil")
		}
		c.EnableShare = parent.EnableShare
	}
	if c.EnableScale == nil {
		if parent.EnableScale == nil {
			panic("parent.EnableScale is nil")
		}
		c.EnableScale = parent.EnableScale
	}
	if c.StillShareWhenMetricNotEnough == nil {
		if parent.StillShareWhenMetricNotEnough == nil {
			panic("parent.StillShareWhenMetricNotEnough is nil")
		}
		c.StillShareWhenMetricNotEnough = parent.StillShareWhenMetricNotEnough
	}
	if c.ScaleLowCPULimit == nil {
		if parent.ScaleLowCPULimit == nil {
			panic("parent.ScaleLowCPULimit is nil")
		}
		c.ScaleLowCPULimit = parent.ScaleLowCPULimit
	}
	if c.ScaleHighCPULimit == nil {
		if parent.ScaleHighCPULimit == nil {
			panic("parent.ScaleHighCPULimit is nil")
		}
		c.ScaleHighCPULimit = parent.ScaleHighCPULimit
	}
	if c.TenantInitCoreNum == nil {
		if parent.TenantInitCoreNum == nil {
			panic("parent.TenantInitCoreNum is nil")
		}
		c.TenantInitCoreNum = parent.TenantInitCoreNum
	}
	if c.TenantMinCoreNum == nil {
		if parent.TenantMinCoreNum == nil {
			panic("parent.TenantMinCoreNum is nil")
		}
		c.TenantMinCoreNum = parent.TenantMinCoreNum
	}
	if c.TenantMaxCoreNum == nil {
		if parent.TenantMaxCoreNum == nil {
			panic("parent.TenantMaxCoreNum is nil")
		}
		c.TenantMaxCoreNum = parent.TenantMaxCoreNum
	}
}

func (c *Config) verify() {
	c.ClusterConfig.verify()
	for _, t := range c.TenantConfigs {
		t.verify()
		t.verifyCoreNum(*c.ClusterConfig.CoreNumPerPod)
	}
}

func (c *ClusterConfig) verify() {
	if c.PDAddr == nil {
		panic("cluster_config.pd_addr is nil")
	}
	if c.IsMinikubeTest == nil {
		panic("cluster_config.is_minikube_test is nil")
	}
	if c.ClusterMaxPods == nil {
		panic("cluster_config.cluster_max_pods is nil")
	}
	if *c.ClusterMaxPods == 0 {
		panic("cluster_config.cluster_max_pods cannot be zero")
	}
	if c.LogPath == nil || len(*c.LogPath) == 0 {
		panic("cluster_config.log_path is invalid")
	}
	if c.LogLevel == nil || len(*c.LogLevel) == 0 {
		panic("cluster_config.log_level is invalid")
	}

	if c.PromAddr == nil {
		panic("cluster_config.prom_addr is nil")
	}
	if c.PromQueryTimeoutSec == nil {
		panic("cluster_config.prom_query_timeout_sec is nil")
	}
	if *c.PromQueryTimeoutSec <= 0 {
		panic("cluster_config.prom_query_timeout_sec cannot be zero")
	}

	if c.CNLocalCachePath == nil {
		panic("cluster_config.cn_local_cache_path is nil")
	}
	if c.CNLocalCacheSize == nil {
		panic("cluster_config.cn_local_cache_size is nil")
	}

	if c.SupervisorImage == nil || len(*c.SupervisorImage) == 0 {
		panic("cluster_config.supervisor_image is nil or empty")
	}
	if c.ConfigCheckIntervalSec == nil {
		panic("cluster_config.config_check_interval is nil")
	}
	if c.ConfigVersion == nil {
		panic("cluster_config.config_version is nil")
	}
	if c.CoreNumPerPod == nil {
		panic("cluster_config.core_num_per_pod is nil")
	}
	if c.HttpServerTimeoutSec == nil {
		panic("cluster_config.http_server_timeout_sec is nil")
	}
	if *c.HttpServerTimeoutSec <= 0 {
		panic("cluster_config.http_server_timeout_sec cannot be zero")
	}
	if c.HttpPort == nil {
		panic("cluster_config.http_port is nil")
	}
	if c.LostPodWindowSec == nil {
		panic("cluster_config.lost_pod_window_sec is nil")
	}
	if c.PodScannerIntervalSec == nil {
		panic("cluster_config.lost_pod_check_interval_sec is nil")
	}
	if c.WarmupIntervalSec == nil {
		panic("cluster_config.warmup_interval_sec is nil")
	}
	if c.WarmupPoolCap == nil {
		panic("cluster_config.warmup_pool_cap is nil")
	}
	if c.PendingPodTimeoutSec == nil {
		panic("cluster_config.pending_pod_timeout is nil")
	}
	if c.WarmupPoolOnePodCoolDownSec == nil {
		panic("cluster_config.warmup_pool_one_pod_cool_down_sec is nil")
	}
	if c.EnableIstioProxy == nil {
		panic("cluster_config.enable_istio_proxy is nil")
	}
	if c.EnableTLS == nil {
		panic("cluster_config.enable_tls is nil")
	}
	if c.MetricIntervalSec == nil {
		panic("cluster_config.metric_interval_sec is nil")
	}
	if c.MetricWindowSec == nil {
		panic("cluster_config.metric_window_sec is nil")
	}
	if c.AutoPauseWindowSec == nil {
		panic("cluster_config.auto_pause_window_sec is nil")
	}
	if c.MemLimitRecoveryCoolDownSec == nil {
		panic("cluster_config.mem_limit_cool_down_sec is nil")
	}
	if c.SharePoolConfig == nil {
		panic("cluster_config.share_pool_config is nil")
	}
	if c.TenantConfig == nil {
		panic("cluster_config.tenant_config is nil")
	}

	if *c.MetricWindowSec%5 != 0 {
		panic(fmt.Sprintf("MetricWindowSec must be a multiple of 5, but got %d", *c.MetricWindowSec))
	}
	if *c.AutoPauseWindowSec%5 != 0 {
		panic(fmt.Sprintf("AutoPauseWindowSec must be a multiple of 5, but got %d", *c.AutoPauseWindowSec))
	}
	if *c.MetricIntervalSec%5 != 0 {
		panic(fmt.Sprintf("MetricIntervalSec must be a multiple of 5, but got %d", *c.MetricIntervalSec))
	}
	c.SharePoolConfig.verify()
}

func (s *SharePoolConfig) verify() {
	if s.SoftHighCoreNum == nil {
		panic("share_pool_config.soft_high_core_num is nil")
	}
	if s.MergeLowCPULimit == nil {
		panic("share_pool_config.merge_low_cpu_limit is nil")
	}
	if s.MergeHighCPULimit == nil {
		panic("share_pool_config.merge_high_cpu_limit is nil")
	}
	if s.MergeCoolDownSec == nil {
		panic("share_pool_config.merge_cool_down_sec is nil")
	}
	if s.ShareCoolDownSec == nil {
		panic("share_pool_config.share_cool_down_sec is nil")
	}
	if s.EnableMerge == nil {
		panic("share_pool_config.enable_merge is nil")
	}
	if s.EnableEvict == nil {
		panic("share_pool_config.enable_evict is nil")
	}
	if s.ShareLowCPULimit == nil {
		panic("share_pool_config.share_low_cpu_limit is nil")
	}
	if s.EvictHighCPULimit == nil {
		panic("share_pool_config.evict_high_cpu_limit is nil")
	}
}

func (t *TenantConfig) verify() {
	if t.TenantName == nil {
		panic("tenant_config.tenant_name is nil")
	}
	if t.EnableAutoPause == nil {
		panic("tenant_config.enable_auto_pause is nil")
	}
	if t.EnableShare == nil {
		panic("tenant_config.enable_share is nil")
	}
	if t.EnableScale == nil {
		panic("tenant_config.enable_scale is nil")
	}
	if t.StillShareWhenMetricNotEnough == nil {
		panic("tenant_config.enable_scale is nil")
	}
	if t.ScaleLowCPULimit == nil {
		panic("tenant_config.scale_low_cpu_limit is nil")
	}
	if t.ScaleHighCPULimit == nil {
		panic("tenant_config.scale_high_cpu_limit is nil")
	}
	if t.TenantInitCoreNum == nil {
		panic("tenant_config.tenant_init_core_num is nil")
	}
	if *t.TenantInitCoreNum <= 0 {
		panic(fmt.Sprintf("tenant_config.tenant_init_core_num is invalid: %v", *t.TenantInitCoreNum))
	}
	if t.TenantMinCoreNum == nil {
		panic("tenant_config.tenant_min_core_num is nil")
	}
	if t.TenantMaxCoreNum == nil {
		panic("tenant_config.tenant_max_core_num is nil")
	}
}

func (c *TenantConfig) verifyCoreNum(coreNumPerPod uint) {
	coreNumChecker := func(num *uint, msg string) {
		if *num == 0 || coreNumPerPod == 0 || (*num%coreNumPerPod) != 0 {
			panic(fmt.Sprintf("core num checker failed: %s. Expected core number to be a multiple of %d, but got %d", msg, coreNumPerPod, *num))
		}
	}
	coreNumChecker(c.TenantInitCoreNum, "TenantInitCoreNum")
	coreNumChecker(c.TenantMinCoreNum, "TenantMinCoreNum")
	coreNumChecker(c.TenantMaxCoreNum, "TenantMaxCoreNum")
}

func (c *Config) GetClusterConfig() *ClusterConfig {
	return c.ClusterConfig
}

func (c *Config) GetTenantConfig(tenantName string) *TenantConfig {
	for _, t := range c.TenantConfigs {
		if *t.TenantName == tenantName {
			return t
		}
	}
	return c.ClusterConfig.TenantConfig
}

func (c *Config) ToString() (string, error) {
	jsonBytes, err := json.Marshal(*c)
	if err != nil {
		return "", err
	}
	return string(jsonBytes), nil
}

func NewConfig(fn string) *Config {
	configData, err := ioutil.ReadFile(fn)
	if err != nil {
		panic(fmt.Errorf("fn: %v, err: %v", fn, err))
	}

	var res Config
	if err := yaml.Unmarshal(configData, &res); err != nil {
		panic(err)
	}

	res.setupDefault()
	res.updateConfigByEnv()
	res.verify()
	return &res
}

func (c *Config) GetUpdateTime() time.Time {
	return c.updateTs
}

// TODO: remove this later, because we can update this by ConfigMap, so no need to restart AutoScaler.
func (c *Config) updateConfigByEnv() {
	if logger.InTest {
		return
	}
	if strings.ToLower(os.Getenv(EnvKeyEnableIstioProxy)) == "true" {
		*c.GetClusterConfig().EnableIstioProxy = true
	} else {
		*c.GetClusterConfig().EnableIstioProxy = false
	}

	if strings.ToLower(os.Getenv(EnvKeyEnableTLS)) == "true" {
		*c.GetClusterConfig().EnableTLS = true
	} else {
		*c.GetClusterConfig().EnableTLS = false
	}

	capStr := os.Getenv(EnvKeyWarmupPoolCap)
	if size, err := strconv.Atoi(capStr); err != nil {
		panic(fmt.Sprintf("parse EnvKeyWarmupPoolCap to int failed: %v", err))
	} else {
		if size <= 0 {
			panic(fmt.Sprintf("invalid warmup pool cap: %v", size))
		}
		*c.GetClusterConfig().WarmupPoolCap = uint(size)
	}

	imageStr := os.Getenv(EnvKeySupervisorImage)
	if len(imageStr) <= 0 {
		panic("invalid empty supervisor image")
	}
	*c.GetClusterConfig().SupervisorImage = imageStr

	pdAddrStr := os.Getenv(EnvKeyPDAddr)
	if len(pdAddrStr) <= 0 {
		panic("invalid empty pd addr")
	}
	*c.GetClusterConfig().PDAddr = pdAddrStr

	cloudProvider := os.Getenv(EnvKeyCloudProvider)
	*c.GetClusterConfig().CloudProvider = cloudProvider

	region := os.Getenv(EnvKeyRegion)
	*c.GetClusterConfig().Region = region
}
