package userbehavior

import (
	"fmt"
	"reflect"
	"strconv"
	"strings"

	larkcard "github.com/larksuite/oapi-sdk-go/v3/card"
	"github.com/tidbcloud/devops/pkg/infra/utils/pointer"
	dedicatedv1 "github.com/tidbcloud/tidb-management-service/api/spec/global/dedicated/v1"
	mgmtv1 "github.com/tidbcloud/tidb-management-service/api/spec/tidb_mgmt_service/v1"
	mgmtevent "github.com/tidbcloud/tidb-management-service/utilities/clusterevent"
	"github.com/tidbcloud/tidb-management-service/utilities/spu/storagetype"
)

func (s *svc) buildCard(text string, cardUrl string) *larkcard.MessageCard {
	// config
	config := larkcard.NewMessageCardConfig().
		WideScreenMode(true).
		EnableForward(true).
		UpdateMulti(false).
		Build()

	// CardUrl
	cardLink := larkcard.NewMessageCardURL().
		Url(cardUrl).
		Build()

	// header
	header := larkcard.NewMessageCardHeader().
		Template("turquoise").
		Title(larkcard.NewMessageCardPlainText().
			Content("User Behavior Event").
			Build()).
		Build()

	// Elements

	divElement := larkcard.NewMessageCardDiv().
		Fields([]*larkcard.MessageCardField{larkcard.NewMessageCardField().
			Text(larkcard.NewMessageCardLarkMd().
				Content(text).
				Build()).
			IsShort(true).
			Build()}).
		Build()

	messageCard := larkcard.NewMessageCard().
		Config(config).
		Header(header).
		Elements([]larkcard.MessageCardElement{divElement}).
		CardLink(cardLink).
		Build()

	return messageCard
}

func generateBeforeAfterTopologyText(topologyBefore *mgmtevent.ClusterTopology, topologyAfter *mgmtevent.ClusterTopology) string {
	if topologyBefore == nil || topologyBefore.Components == nil || topologyAfter == nil || topologyAfter.Components == nil {
		return ""
	}

	allComponents := make(map[string]struct{})
	beforeComponentMap := make(map[string]*mgmtevent.ClusterComponent)
	afterComponentMap := make(map[string]*mgmtevent.ClusterComponent)
	nodeGroupIdMap := make(map[uint64]struct{})
	beforeNodeGroupMap := make(map[uint64]*mgmtevent.TidbNodeGroup)
	afterNodeGroupMap := make(map[uint64]*mgmtevent.TidbNodeGroup)

	for componentType, componentInfo := range topologyBefore.Components {
		beforeComponentMap[componentType] = componentInfo
		allComponents[componentType] = struct{}{}
	}
	for _, groupInfo := range topologyBefore.ExTidbNodeGroups {
		beforeNodeGroupMap[groupInfo.ID] = groupInfo
		nodeGroupIdMap[groupInfo.ID] = struct{}{}
	}
	for _, groupInfo := range topologyAfter.ExTidbNodeGroups {
		afterNodeGroupMap[groupInfo.ID] = groupInfo
		nodeGroupIdMap[groupInfo.ID] = struct{}{}
	}

	for componentType, componentInfo := range topologyAfter.Components {
		afterComponentMap[componentType] = componentInfo
		allComponents[componentType] = struct{}{}
	}

	var builder strings.Builder

	// shared components and default group
	for componentType := range allComponents {
		displayType := componentType
		switch componentType {
		case "tidb":
			displayType = "**TiDB**(DefaultGroup)"
		case "tiproxy":
			displayType = "**TiProxy**(DefaultGroup)"
		case "pd":
			displayType = "**PD**"
		case "tikv":
			displayType = "**TiKV**"
		case "tiflash":
			displayType = "**TiFlash**"
		}
		if reflect.DeepEqual(beforeComponentMap[componentType], afterComponentMap[componentType]) {
			continue
		}
		beforeSpec := renderComponentSpec(beforeComponentMap[componentType])
		afterSpec := renderComponentSpec(afterComponentMap[componentType])

		builder.WriteString(fmt.Sprintf("%s: %s -> %s\n", displayType, beforeSpec, afterSpec))
	}

	// deal with other node groups
	for id := range nodeGroupIdMap {
		beforeGroup := beforeNodeGroupMap[id]
		afterGroup := afterNodeGroupMap[id]

		if reflect.DeepEqual(beforeGroup, afterGroup) {
			continue
		}

		groupName := strconv.FormatUint(id, 10)
		if beforeGroup != nil && beforeGroup.DisplayName != "" {
			groupName = beforeGroup.DisplayName
		}
		if afterGroup != nil && afterGroup.DisplayName != "" {
			groupName = afterGroup.DisplayName
		}

		builder.WriteString(fmt.Sprintf("**Extra Group**(%s): %s -> %s\n", groupName, renderTiDBGroup(beforeGroup), renderTiDBGroup(afterGroup)))
	}

	return builder.String()
}

func GetStorageTypeFromEventComponent(component *mgmtevent.ClusterComponent) string {
	dataVT := dedicatedv1.StorageMu_VolumeType(
		dedicatedv1.StorageMu_VolumeType_value[strings.ToUpper(component.VolumeType)],
	)
	raftVT := dedicatedv1.StorageMu_VolumeType(
		dedicatedv1.StorageMu_VolumeType_value[strings.ToUpper(component.RaftLogVolumeType)],
	)
	return storagetype.BuildCurrentTikvStorageType(dataVT, raftVT).String()
}

func (s *svc) generateTopologyFromCluster(cluster *mgmtv1.Cluster) string {
	var builder strings.Builder

	apiCluster := cluster.GetDedicatedCluster()
	if apiCluster == nil {
		s.logger.Info("get dedicated cluster failed, the 3layer dedicated cluster is null", "cluster_id", cluster.GetClusterId())
		return ""
	}
	components := apiCluster.ClusterComponents
	for _, component := range components {
		var storageGi int
		if component.ComponentType == dedicatedv1.ComponentType_PD ||
			component.ComponentType == dedicatedv1.ComponentType_TIKV ||
			component.ComponentType == dedicatedv1.ComponentType_TIFLASH {
			if _, ok := component.Storages[dedicatedv1.Sku_DATA.String()]; ok {
				storageGi = int(component.Storages[dedicatedv1.Sku_DATA.String()].SizeGi)
			}
		}
		if storageGi > 0 {
			builder.WriteString(
				fmt.Sprintf("**%s**: %d X %dC%dG-%dG\n",
					component.ComponentType.String(),
					pointer.Deref(component.Count, 0),
					component.CpuNum,
					component.MemorySizeGi,
					storageGi,
				))
		} else {
			builder.WriteString(
				fmt.Sprintf("**%s**: %d X %dC%dG\n",
					component.ComponentType.String(),
					pointer.Deref(component.Count, 0),
					component.CpuNum,
					component.MemorySizeGi,
				))
		}
	}

	return builder.String()
}

func renderComponentSpec(component *mgmtevent.ClusterComponent) string {
	if component == nil {
		return "None"
	}

	switch component.ComponentType {
	case "tikv":
		return renderTikvTopologyStr(
			component.Quantity,
			component.StorageSizeGi,
			component.SpuName,
			GetStorageTypeFromEventComponent(component),
			component.RaftLogIops,
			component.AdditionalIops,
		)
	case "tiflash":
		return renderTiflashTopologyStr(
			component.Quantity,
			component.StorageSizeGi,
			component.SpuName,
			GetStorageTypeFromEventComponent(component),
		)
	default:
		return renderNoStorageTopologyStr(
			component.Quantity,
			component.SpuName,
		)
	}
}

func renderTiDBGroup(group *mgmtevent.TidbNodeGroup) string {
	if group == nil {
		return "None"
	}
	return fmt.Sprintf("%d x TiDB Nodes, %d x TiProxy Nodes", group.TidbNodeQuantity, group.TiProxyQuantity)
}

// copy from https://github.com/tidbcloud/tidb-management-service/blob/main/tidb-mgmt-service/infra/eventbus/clustereventconsumer/display_event.go
// modify to remove componment names
// todo: replace this duplicate code by directly use display event
var (
	renderNoStorageTopologyStr = func(count uint32, spuName string) string {
		return fmt.Sprintf(`%d x (%s)`, count, spuName)
	}
	renderTikvTopologyStr = func(count, sizeGi uint32, spuName, storageType string, raftIops, dataAdditionalIops uint32) string {
		spec := fmt.Sprintf("%s,%dGiB,%s", spuName, sizeGi, storageType)
		if raftIops != 0 {
			spec += fmt.Sprintf(",%dRaftIOPS", raftIops)
		}
		if dataAdditionalIops != 0 {
			spec += fmt.Sprintf(",%dDataAdditionalIOPS", dataAdditionalIops)
		}
		return fmt.Sprintf(`%d x (%s)`, count, spec)
	}
	renderTiflashTopologyStr = func(count, sizeGi uint32, spuName, storageType string) string {
		return fmt.Sprintf(`%d x (%s,%dGiB,%s)`, count, spuName, sizeGi, storageType)
	}
)
