package mgmt

import (
	"context"
	"encoding/json"
	"fmt"
	"strconv"
	"strings"

	"github.com/tidbcloud/metering/pkg/common/constants"
	"github.com/tidbcloud/metering/pkg/common/metrics"

	log "github.com/sirupsen/logrus"
	"github.com/tidbcloud/metering/pkg/common/config"
	"github.com/tidbcloud/metering/pkg/common/models"
	dedicatedv1 "github.com/tidbcloud/tidb-management-service/api/spec/global/dedicated/v1"
	tidb_mgmt_servicev1 "github.com/tidbcloud/tidb-management-service/api/spec/tidb_mgmt_service/v1"
)

type TiDBMgmtGrpcService struct {
	grpcCli tidb_mgmt_servicev1.ClusterServiceClient
}

func NewTiDBMgmtGrpcService(ctx context.Context, serviceClient tidb_mgmt_servicev1.ClusterServiceClient) TiDBMgmtGrpcService {
	return TiDBMgmtGrpcService{
		grpcCli: serviceClient,
	}
}

func (m TiDBMgmtGrpcService) List(ctx context.Context, timestamp int64) ([]*models.ClusterMeta, error) {
	metricslabels := map[string]string{
		constants.MetricsAPIName:         "ListClusters",
		constants.MetricsAPIResult:       "success",
		constants.MetricsLblProviderType: config.GetConfig().Vendor,
	}
	defer func() {
		metrics.MetricsRquestAPICount.With(metricslabels).Inc()
	}()

	var clusterMetaList []*models.ClusterMeta

	deleted := false
	deployType := tidb_mgmt_servicev1.DeployType_DEPLOY_TYPE_DEDICATED
	// TODO page
	req := &tidb_mgmt_servicev1.ListClustersRequest{
		Filter: &tidb_mgmt_servicev1.ClusterFilter{
			ClusterType:    &deployType,
			Deleted:        &deleted,
			View:           tidb_mgmt_servicev1.ClusterFilter_CLUSTER_VIEW_FULL,
			CloudProviders: config.GetConfig().InstanceMeteringConfig.CloudProviders,
			ExcludeStates:  models.ExcludeClusterStates,
		},
		PageSize: 1000,
	}
	reqJson, err := json.Marshal(req)
	if err != nil {
		return nil, err
	}
	log.Infof("send request to ListClusters,  %s", string(reqJson))
	response, err := m.grpcCli.ListClusters(ctx, req)
	if err != nil {
		metricslabels[constants.MetricsAPIResult] = "failed"
		return nil, err
	}
	respJson, err := json.Marshal(response)
	if err != nil {
		return nil, err
	}
	log.Infof("get response from ListClusters,  %s", string(respJson))

	if len(response.GetClusters()) > 0 {
		for _, cluster := range response.GetClusters() {
			log.Infof("cluster id: %d, state: %s", cluster.ClusterId, cluster.GetDedicatedCluster().State)
			if !models.IfClusterStateNeedMetering(cluster.GetDedicatedCluster().State) {
				log.Warnf("ignore cluster %d because state is %d", cluster.ClusterId, int32(cluster.GetDedicatedCluster().State))
				continue
			}
			clustermeta, err := ConvertTiDBClusterMeta(*cluster, timestamp)
			if err != nil {
				log.Errorf("ConvertTiDBClusterMeta failed %s", err.Error())
				return nil, err
			}
			clusterMetaList = append(clusterMetaList, clustermeta)
		}
		if len(response.GetClusters()) > 1000 {
			log.Errorf("list clusters response more than 1000")
		}
	}

	log.Infof("ListClusters response total = %d", len(response.GetClusters()))

	return clusterMetaList, nil
}

func (m TiDBMgmtGrpcService) Detail(ctx context.Context, timestamp int64, clusterID uint64) ([]*models.ComponentMeta, error) {
	metricslabels := map[string]string{
		constants.MetricsAPIName:         "ShowComponentInfo",
		constants.MetricsAPIResult:       "success",
		constants.MetricsLblProviderType: config.GetConfig().Vendor,
	}
	defer func() {
		metrics.MetricsRquestAPICount.With(metricslabels).Inc()
	}()

	req := &tidb_mgmt_servicev1.ShowComponentNodeInfoRequest{
		ClusterId: clusterID,
	}
	reqJson, err := json.Marshal(req)
	if err != nil {
		return nil, err
	}
	log.Infof("send request to ShowComponentNodeInfo,  %s", string(reqJson))
	response, err := m.grpcCli.ShowComponentNodeInfo(ctx, req)
	if err != nil {
		metricslabels[constants.MetricsAPIResult] = "failed"
		return nil, err
	}

	respJson, err := json.Marshal(response)
	if err != nil {
		return nil, err
	}
	log.Infof("get response from ShowComponentNodeInfo,  %s", string(respJson))
	componentMetaList, err := ConvertShowComponentNodeInfoResponse(response)
	return componentMetaList, err
}

func ConvertTiDBClusterMeta(tidbCluster tidb_mgmt_servicev1.Cluster, timestamp int64) (*models.ClusterMeta, error) {
	meta := &models.ClusterMeta{
		TenantID:  tostring(tidbCluster.GetOrganizationId()),
		ProjectID: tostring(tidbCluster.GetProjectId()),
		ClusterID: tostring(tidbCluster.GetClusterId()),
		DevTier:   false,
		Provider:  tidbCluster.GetCloudProvider(),
		Region:    tidbCluster.GetRegion(),
		Cluster:   tidbCluster.GetClusterName(),
		IsScaling: models.IfClusterScaling(tidbCluster.GetDedicatedCluster().GetState()),
		State:     int32(tidbCluster.GetDedicatedCluster().GetState()),
		Timestamp: timestamp,
	}

	components := make([]*models.ComponentMeta, 0)
	for _, tidbComponent := range tidbCluster.GetDedicatedCluster().ClusterComponents {
		if models.IfComponentTypeNeedMetering(tidbComponent.GetComponentType()) {
			componentMeta := &models.ComponentMeta{
				Component:     strings.ToLower(dedicatedv1.ComponentType_name[int32(tidbComponent.GetComponentType())]),
				ComponentType: fmt.Sprintf("COMPONENT_TYPE_%s", dedicatedv1.ComponentType_name[int32(tidbComponent.GetComponentType())]),
				Count:         tidbComponent.GetCount(),
				TiDBGroupName: tidbComponent.GetTidbGroupDisplayName(),

				InstanceType: tidbComponent.GetInstanceType(),
				CpuNum:       tidbComponent.GetCpuNum(),
				MemoryGiB:    tidbComponent.GetMemorySizeGi(),
			}
			//deal sku feild
			var nodeskus []*models.SKUJson
			for k, v := range tidbComponent.GetMeteringSkuKeys() {
				nodeskus = append(nodeskus, &models.SKUJson{
					SKUName:  k,
					SKUValue: strconv.FormatUint(uint64(v), 10),
					Count:    tidbComponent.GetCount(),
				})
			}
			var err error
			componentMeta.NodeSKU, err = models.SKUJsonToString(nodeskus)
			if err != nil {
				log.Errorf("build node sku to json failed, %s", err.Error())
				return nil, err
			}
			if storage, ok := tidbComponent.GetStorages()[models.MeteredStorageKey]; ok {
				componentMeta.StorageID = tostring(storage.GetStorageId())
				componentMeta.VolumeType = dedicatedv1.StorageMu_VolumeType_name[int32(storage.GetVolumeType())]
				componentMeta.StorageSizeGi = storage.GetSizeGi()
				componentMeta.Iops = storage.GetIops()
				componentMeta.ThroughputMibps = storage.GetThroughputMibps()
			}
			//deal storages sku
			var storageskumap map[string]*models.SKUJson = make(map[string]*models.SKUJson)
			for _, storage := range tidbComponent.GetStorages() {
				for k, v := range storage.GetMeteringSkuKeys() {
					// key is skuname+skuvalue
					key := fmt.Sprintf("%s%s", k, strconv.FormatUint(uint64(v), 10))
					if _, ok := storageskumap[key]; ok {
						storageskumap[key].Count = storageskumap[key].Count + 1
						log.Debugf("key is %s append again", key)
					} else {
						log.Debugf("key is %s,fisrt append", key)
						storageskumap[key] = &models.SKUJson{
							SKUName:  k,
							SKUValue: strconv.FormatUint(uint64(v), 10),
							Count:    1,
						}
					}
				}
			}
			var storageskus []*models.SKUJson
			for _, v := range storageskumap {
				// count is the sum of all storages and node count
				v.Count = v.Count * tidbComponent.GetCount()
				storageskus = append(storageskus, v)
			}
			// for tidb's storage may be empty
			if len(storageskus) != 0 {
				componentMeta.StoragesSKU, err = models.SKUJsonToString(storageskus)
				if err != nil {
					log.Errorf("build storage sku to json failed, %s", err.Error())
					return nil, err
				}
			}
			components = append(components, componentMeta)
		} else {
			log.Infof("ignore component %s for metering", strings.ToLower(dedicatedv1.ComponentType_name[int32(tidbComponent.GetComponentType())]))
			continue
		}
	}

	meta.Components = components
	return meta, nil
}

func getNodeInstanceType(node *dedicatedv1.ComponentNodeInfo_Component_Node) string {
	instance := node.GetInstanceType()
	if instance == "" && node.GetState() == dedicatedv1.ComponentNodeInfo_Component_Node_UNAVAILABLE {
		instance = constants.EmptyNodeInstanceType
	}
	return instance
}

func ConvertTiDBComponent(c *dedicatedv1.ComponentNodeInfo_Component, componentType dedicatedv1.ComponentType) ([]*models.ComponentMeta, error) {
	componentsMap := map[string]*models.ComponentMeta{}
	componentMetas := []*models.ComponentMeta{}
	componentsSKUMap := map[string][]*models.SKUJson{}
	componentsStorageMapSKU := map[string]map[string]*models.SKUJson{}
	for _, node := range c.Nodes {
		if models.IfNodeStateNeedMetering((*node).GetState()) {
			key := getNodeInstanceType(node)
			if _, ok := componentsMap[key]; ok {
				componentsMap[key].Count = componentsMap[key].Count + 1
				//deal sku feild
				if nodeskus, ok := componentsSKUMap[key]; ok {
					for _, v := range nodeskus {
						v.Count = v.Count + 1
					}
				} else {
					// nodeskus should not be nil
					log.Errorf("nodeskus is nil in keys %s", key)
				}
				//deal storages sku if need metering
				if storageskumap, ok := componentsStorageMapSKU[key]; ok && models.IfComponentTypeNeedMeteringForDisk(componentType) {
					for _, storage := range node.GetStorages() {
						for k, v := range storage.GetMeteringSkuKeys() {
							// key is skuname+skuvalue
							key := fmt.Sprintf("%s%s", k, strconv.FormatUint(uint64(v), 10))
							if _, ok := storageskumap[key]; ok {
								storageskumap[key].Count = storageskumap[key].Count + 1
								log.Debugf("key is %s append again", key)
							} else {
								log.Debugf("key is %s,fisrt append", key)
								storageskumap[key] = &models.SKUJson{
									SKUName:  k,
									SKUValue: strconv.FormatUint(uint64(v), 10),
									Count:    1,
								}
							}
						}
					}
				}
			} else {
				componentMeta := &models.ComponentMeta{
					Component:     strings.ToLower(dedicatedv1.ComponentType_name[int32(componentType)]),
					ComponentType: fmt.Sprintf("COMPONENT_TYPE_%s", dedicatedv1.ComponentType_name[int32(componentType)]),
					Count:         1,
					TiDBGroupName: c.GetTidbGroupDisplayName(),

					InstanceType: getNodeInstanceType(node),
					CpuNum:       node.GetCpu(),
					MemoryGiB:    node.GetMemorySizeGi(),
				}
				//deal sku feild
				var nodeskus []*models.SKUJson
				var storageskumap map[string]*models.SKUJson = make(map[string]*models.SKUJson)
				for k, v := range node.GetMeteringSkuKeys() {
					nodeskus = append(nodeskus, &models.SKUJson{
						SKUName:  k,
						SKUValue: strconv.FormatUint(uint64(v), 10),
						Count:    1,
					})
				}

				if models.IfComponentTypeNeedMeteringForDisk(componentType) {
					componentMeta.VolumeType = dedicatedv1.StorageMu_VolumeType_name[int32(node.VolumeType)]
					componentMeta.StorageSizeGi = node.StorageSizeGi
					componentMeta.Iops = node.StorageIops
					componentMeta.ThroughputMibps = node.StorageThroughputMibps
					//deal storages sku
					for _, storage := range node.GetStorages() {
						for k, v := range storage.GetMeteringSkuKeys() {
							// key is skuname+skuvalue
							key := fmt.Sprintf("%s%s", k, strconv.FormatUint(uint64(v), 10))
							if _, ok := storageskumap[key]; ok {
								storageskumap[key].Count = storageskumap[key].Count + 1
								log.Debugf("key is %s append again", key)
							} else {
								log.Debugf("key is %s,fisrt append", key)
								storageskumap[key] = &models.SKUJson{
									SKUName:  k,
									SKUValue: strconv.FormatUint(uint64(v), 10),
									Count:    1,
								}
							}
						}
					}
				}
				//for logic there three's key is same
				componentsMap[key] = componentMeta
				componentsSKUMap[key] = nodeskus
				componentsStorageMapSKU[key] = storageskumap
			}
		} else {
			log.Infof("ignore %s node %s for metering because state is %d ", strings.ToLower(dedicatedv1.ComponentType_name[int32(componentType)]), node.InstanceName, (*node).GetState())
		}
	}
	for k, v := range componentsMap {
		var err error
		//deal sku feild
		v.NodeSKU, err = models.SKUJsonToString(componentsSKUMap[k])
		if err != nil {
			log.Errorf("build node sku to json failed, %s", err.Error())
			return nil, err
		}
		//deal storages sku
		var storageskus []*models.SKUJson
		for _, v := range componentsStorageMapSKU[k] {
			storageskus = append(storageskus, v)
		}
		if len(storageskus) != 0 {
			v.StoragesSKU, err = models.SKUJsonToString(storageskus)
			if err != nil {
				log.Errorf("build storage sku to json failed, %s", err.Error())
				return nil, err
			}
		}
		componentMetas = append(componentMetas, v)
	}
	return componentMetas, nil
}

func ConvertShowComponentNodeInfoResponse(response *tidb_mgmt_servicev1.ShowComponentNodeInfoResponse) ([]*models.ComponentMeta, error) {
	var componentMetaList []*models.ComponentMeta

	tidbComponents := response.GetComponentNodeInfo().GetTidb()
	for _, tidb := range tidbComponents {
		tidbMetas, err := ConvertTiDBComponent(tidb, dedicatedv1.ComponentType_TIDB)
		if err != nil {
			log.Errorf("ConvertTiDBComponent TiDB failed. err = %s", err.Error())
			return nil, err
		}
		componentMetaList = append(componentMetaList, tidbMetas...)
	}

	tikv := response.GetComponentNodeInfo().GetTikv()
	if tikv == nil {
		log.Errorf("tikv is nil")
	} else {
		tikvMetas, err := ConvertTiDBComponent(tikv, dedicatedv1.ComponentType_TIKV)
		if err != nil {
			log.Errorf("ConvertTiDBComponent TiKV failed. err = %s", err.Error())
			return nil, err
		}
		componentMetaList = append(componentMetaList, tikvMetas...)
	}

	tiflash := response.GetComponentNodeInfo().GetTiflash()

	if tiflash == nil {
		log.Infof("tiflash is nil")
	} else {
		tiflashMetas, err := ConvertTiDBComponent(response.GetComponentNodeInfo().GetTiflash(), dedicatedv1.ComponentType_TIFLASH)
		if err != nil {
			log.Errorf("ConvertTiDBComponent TiFlash failed. err = %s", err.Error())
			return nil, err
		}
		componentMetaList = append(componentMetaList, tiflashMetas...)
	}

	tiproxyComponents := response.GetComponentNodeInfo().GetTiproxy()
	for _, tiproxy := range tiproxyComponents {
		tiproxyMetas, err := ConvertTiDBComponent(tiproxy, dedicatedv1.ComponentType_TIPROXY)
		if err != nil {
			log.Errorf("ConvertTiDBComponent TiProxy failed. err = %s", err.Error())
			return nil, err
		}
		componentMetaList = append(componentMetaList, tiproxyMetas...)
	}

	return componentMetaList, nil
}

func tostring(u uint64) string {
	return strconv.FormatUint(u, 10)
}
