package resources

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

	"github.com/prometheus/client_golang/prometheus"
	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
	"k8s.io/apimachinery/pkg/runtime/schema"
	"sigs.k8s.io/yaml"

	cloudv1 "github.com/tidbcloud/infra-api/api/v1"

	"github.com/tidbcloud/infra-provider/pkg/scale"
	corev1 "k8s.io/api/core/v1"
)

// Type aliases for backward compatibility
type (
	MemberScaleType            = scale.MemberScaleType
	ScaleStatusTracked         = scale.ScaleStatusTracked
	ScaleChange                = scale.ScaleChange
	MemberConditionType        = scale.MemberConditionType
	MemberStatus               = scale.MemberStatus
	CompMemberStatus           = scale.CompMemberStatus
	StoreStatus                = scale.StoreStatus
	MemberResourceInfo         = scale.MemberResourceInfo
	CompMemberSetting          = scale.CompMemberSetting
	NodeStatus                 = scale.NodeStatus
	AutoScalingGroupListStatus = scale.AutoScalingGroupListStatus
	AutoScalingGroupStatus     = scale.AutoScalingGroupStatus
	PVInfo                     = scale.PVInfo
	PVCDetail                  = scale.PVCDetail
)

// Constants for backward compatibility
const (
	MemberScaleTypeNone            = scale.MemberScaleTypeNone
	MemberScaleTypeScaleUp         = scale.MemberScaleTypeScaleUp
	MemberScaleTypeScaleDown       = scale.MemberScaleTypeScaleDown
	MemberScaleTypeScaleOut        = scale.MemberScaleTypeScaleOut
	MemberScaleTypeScaleIn         = scale.MemberScaleTypeScaleIn
	MemberScaleConfigVersionChange = scale.MemberScaleConfigVersionChange
	MemberScaleStorageSizeChange   = scale.MemberScaleStorageSizeChange
	MemberPodRunning               = scale.MemberPodRunning
	MemberNodeReady                = scale.MemberNodeReady
	MemberPVCReady                 = scale.MemberPVCReady
	MemberStoreOnline              = scale.MemberStoreOnline
)

var (
	scaleStatusGVR = schema.GroupVersionResource{
		Group:    "",
		Version:  "v1",
		Resource: "configmaps",
	}

	scaleStatusTrackedMetric = prometheus.NewDesc(
		prometheus.BuildFQName("infra", "exporter", "scale_status_tracked"),
		"Information about ScaleStatusTracked CM last probe time.",
		[]string{
			"cluster_id",
			"tenant_id",
			"project_id",
			"namespace",
			"is_synced",
		}, nil)
	// store start time
	tikvOrTiFlashStartTimestamp = prometheus.NewDesc(
		prometheus.BuildFQName("infra", "exporter", "tikv_or_tiflash_start_timestamp"),
		"Information about tikv or tiflash store start timestamp.",
		[]string{
			"cluster_id",
			"tenant_id",
			"project_id",
			"namespace",
			"group_id",
			"component",
			"scale_type",
			"name",
		}, nil)
	// pod create time
	tikvOrTiFlashPodCreateTime = prometheus.NewDesc(
		prometheus.BuildFQName("infra", "exporter", "tikv_or_tiflash_pod_create_time"),
		"Information about tikv or tiflash pod create time.",
		[]string{
			"cluster_id",
			"tenant_id",
			"project_id",
			"namespace",
			"group_id",
			"component",
			"scale_type",
			"name",
		}, nil)
	// region count
	tikvOrTiFlashRegionCount = prometheus.NewDesc(
		prometheus.BuildFQName("infra", "exporter", "tikv_or_tiflash_region_count"),
		"Information about tikv or tiflash region count.",
		[]string{
			"cluster_id",
			"tenant_id",
			"project_id",
			"namespace",
			"group_id",
			"component",
			"scale_type",
			"name",
		}, nil)
	// leader count
	tikvOrTiFlashLeaderCount = prometheus.NewDesc(
		prometheus.BuildFQName("infra", "exporter", "tikv_or_tiflash_leader_count"),
		"Information about tikv or tiflash leader count.",
		[]string{
			"cluster_id",
			"tenant_id",
			"project_id",
			"namespace",
			"group_id",
			"component",
			"scale_type",
			"name",
		}, nil)
	// last update time from tidbcloud
	lastUpdateTimeFromTiDBCloud = prometheus.NewDesc(
		prometheus.BuildFQName("infra", "exporter", "last_update_time_from_tidbcloud"),
		"Information about last update time from tidbcloud.",
		[]string{
			"cluster_id",
			"tenant_id",
			"project_id",
			"namespace",
			"cluster_full_synced",
		}, nil)

	componentStatusMetric = prometheus.NewDesc(
		prometheus.BuildFQName("infra", "exporter", "component_status"),
		"Information about component status.",
		[]string{
			"cluster_id",
			"tenant_id",
			"project_id",
			"namespace",
			"group_id",
			"component",
			"component_status",
			"instance_type_scale_phase",
			"scale_type",
		}, nil)

	componentMemberStatusMetric = prometheus.NewDesc(
		prometheus.BuildFQName("infra", "exporter", "component_member_status"),
		"Information about ComponentMemberStatus configmap.",
		[]string{
			"cluster_id",
			"tenant_id",
			"project_id",
			"namespace",
			"group_id",
			"component",
			"component_status",
			"instance_type_scale_phase",
			"scale_type",
			"name",
		}, nil)
)

type scaleStatus struct {
	defaultResource
}

var _ Interface = &scaleStatus{}

func (s scaleStatus) HasDedicatedLabel() bool {
	return false
}

func (s scaleStatus) GVR() schema.GroupVersionResource {
	return scaleStatusGVR
}

func (s scaleStatus) Desc() *prometheus.Desc {
	return scaleStatusTrackedMetric
}

func (s scaleStatus) ExtraDescs() []*prometheus.Desc {
	return []*prometheus.Desc{
		tikvOrTiFlashStartTimestamp,
		tikvOrTiFlashPodCreateTime,
		tikvOrTiFlashRegionCount,
		tikvOrTiFlashLeaderCount,
		lastUpdateTimeFromTiDBCloud,
		componentStatusMetric,
		componentMemberStatusMetric,
	}
}

func (s scaleStatus) FilterOut(u *unstructured.Unstructured) bool {
	cm := &corev1.ConfigMap{}
	data, err := u.MarshalJSON()
	if err != nil {
		return true
	}
	if err := json.Unmarshal(data, cm); err != nil {
		return true
	}
	return !strings.HasPrefix(cm.Name, "tidb-cluster-scale-status-")
}

func (s *scaleStatus) unMarshalScaleStatusTracked(obj *unstructured.Unstructured) (*ScaleStatusTracked, error) {
	cm := &corev1.ConfigMap{}
	data, err := obj.MarshalJSON()
	if err != nil {
		return nil, err
	}
	if err := json.Unmarshal(data, cm); err != nil {
		return nil, err
	}
	// Check if the data key exists
	yamlData, ok := cm.Data["current-scale-status"]
	if !ok || yamlData == "" {
		return nil, fmt.Errorf("configmap data key 'current-scale-status' not found or empty")
	}
	scaleStatusTracked := ScaleStatusTracked{}
	if err := yaml.Unmarshal([]byte(yamlData), &scaleStatusTracked); err != nil {
		return nil, err
	}
	return &scaleStatusTracked, nil
}

func (s *scaleStatus) extractTikvOrTiFlashStartTimestamp(scaleStatusTracked *ScaleStatusTracked) []prometheus.Metric {
	output := []prometheus.Metric{}
	if scaleStatusTracked.ComponentMemberStatus == nil {
		return output
	}
	for _, compMemberStatus := range scaleStatusTracked.ComponentMemberStatus {
		if compMemberStatus.ComponentType != cloudv1.ComponentTiKV && compMemberStatus.ComponentType != cloudv1.ComponentTiFlash {
			continue
		}
		for _, memberStatus := range compMemberStatus.NotNormalMemberStatus {

			output = append(output, prometheus.MustNewConstMetric(
				tikvOrTiFlashStartTimestamp,
				prometheus.GaugeValue,
				float64(memberStatus.StoreStartTime.Unix()),
				scaleStatusTracked.ClusterId,
				scaleStatusTracked.TenantId,
				scaleStatusTracked.ProjectId,
				scaleStatusTracked.Namespace,
				compMemberStatus.GroupId,
				string(compMemberStatus.ComponentType),
				string(compMemberStatus.MemberScaleType),
				memberStatus.Name,
			))
		}
	}
	return output
}

func (s *scaleStatus) extractTikvOrTiFlashPodCreateTimestamp(scaleStatusTracked *ScaleStatusTracked) []prometheus.Metric {
	output := []prometheus.Metric{}
	if scaleStatusTracked.ComponentMemberStatus == nil {
		return output
	}
	for _, compMemberStatus := range scaleStatusTracked.ComponentMemberStatus {
		if compMemberStatus.ComponentType != cloudv1.ComponentTiKV && compMemberStatus.ComponentType != cloudv1.ComponentTiFlash {
			continue
		}
		for _, memberStatus := range compMemberStatus.NotNormalMemberStatus {
			output = append(output, prometheus.MustNewConstMetric(
				tikvOrTiFlashPodCreateTime,
				prometheus.GaugeValue,
				float64(memberStatus.PodCreateTime.Unix()),
				scaleStatusTracked.ClusterId,
				scaleStatusTracked.TenantId,
				scaleStatusTracked.ProjectId,
				scaleStatusTracked.Namespace,
				compMemberStatus.GroupId,
				string(compMemberStatus.ComponentType),
				string(compMemberStatus.MemberScaleType),
				memberStatus.Name,
			))
		}
	}
	return output
}

func (s *scaleStatus) extractTikvOrTiFlashRegionCount(scaleStatusTracked *ScaleStatusTracked) []prometheus.Metric {
	output := []prometheus.Metric{}
	if scaleStatusTracked.ComponentMemberStatus == nil {
		return output
	}
	for _, compMemberStatus := range scaleStatusTracked.ComponentMemberStatus {
		if compMemberStatus.ComponentType != cloudv1.ComponentTiKV && compMemberStatus.ComponentType != cloudv1.ComponentTiFlash {
			continue
		}
		for _, memberStatus := range compMemberStatus.NotNormalMemberStatus {
			output = append(output, prometheus.MustNewConstMetric(
				tikvOrTiFlashRegionCount,
				prometheus.GaugeValue,
				float64(memberStatus.RegionCount),
				scaleStatusTracked.ClusterId,
				scaleStatusTracked.TenantId,
				scaleStatusTracked.ProjectId,
				scaleStatusTracked.Namespace,
				compMemberStatus.GroupId,
				string(compMemberStatus.ComponentType),
				string(compMemberStatus.MemberScaleType),
				memberStatus.Name,
			))
		}
	}
	return output
}

func (s *scaleStatus) extractTikvOrTiFlashLeaderCount(scaleStatusTracked *ScaleStatusTracked) []prometheus.Metric {
	output := []prometheus.Metric{}
	if scaleStatusTracked.ComponentMemberStatus == nil {
		return output
	}
	for _, compMemberStatus := range scaleStatusTracked.ComponentMemberStatus {
		if compMemberStatus.ComponentType != cloudv1.ComponentTiKV && compMemberStatus.ComponentType != cloudv1.ComponentTiFlash {
			continue
		}
		for _, memberStatus := range compMemberStatus.NotNormalMemberStatus {
			output = append(output, prometheus.MustNewConstMetric(
				tikvOrTiFlashLeaderCount,
				prometheus.GaugeValue,
				float64(memberStatus.LeaderCount),
				scaleStatusTracked.ClusterId,
				scaleStatusTracked.TenantId,
				scaleStatusTracked.ProjectId,
				scaleStatusTracked.Namespace,
				compMemberStatus.GroupId,
				string(compMemberStatus.ComponentType),
				string(compMemberStatus.MemberScaleType),
				memberStatus.Name,
			))
		}
	}
	return output
}

func (s *scaleStatus) extractLastUpdateTimeFromTiDBCloud(scaleStatusTracked *ScaleStatusTracked) prometheus.Metric {
	if scaleStatusTracked.LastUpdateTimeFromTiDBCloud.IsZero() {
		return nil
	}
	return prometheus.MustNewConstMetric(
		lastUpdateTimeFromTiDBCloud,
		prometheus.GaugeValue,
		float64(scaleStatusTracked.LastUpdateTimeFromTiDBCloud.Unix()),
		scaleStatusTracked.ClusterId,
		scaleStatusTracked.TenantId,
		scaleStatusTracked.ProjectId,
		scaleStatusTracked.Namespace,
		strconv.FormatBool(scaleStatusTracked.ClusterFullSynced),
	)
}

func (s *scaleStatus) extractComponentStatus(scaleStatusTracked *ScaleStatusTracked) []prometheus.Metric {
	output := []prometheus.Metric{}
	if scaleStatusTracked.ComponentMemberStatus == nil {
		return output
	}
	for _, compMemberStatus := range scaleStatusTracked.ComponentMemberStatus {
		output = append(output, prometheus.MustNewConstMetric(
			componentStatusMetric,
			prometheus.GaugeValue,
			1,
			scaleStatusTracked.ClusterId,
			scaleStatusTracked.TenantId,
			scaleStatusTracked.ProjectId,
			scaleStatusTracked.Namespace,
			compMemberStatus.GroupId,
			string(compMemberStatus.ComponentType),
			string(compMemberStatus.ComponentStatus),
			compMemberStatus.InstanceTypeScalePhase,
			string(compMemberStatus.MemberScaleType),
		))
	}
	return output
}

func (s *scaleStatus) extractComponentMemberStatus(scaleStatusTracked *ScaleStatusTracked) []prometheus.Metric {
	output := []prometheus.Metric{}
	if scaleStatusTracked.ComponentMemberStatus == nil {
		return output
	}
	for _, compMemberStatus := range scaleStatusTracked.ComponentMemberStatus {
		for _, memberStatus := range compMemberStatus.NotNormalMemberStatus {
			output = append(output, prometheus.MustNewConstMetric(
				componentMemberStatusMetric,
				prometheus.GaugeValue,
				1.0,
				scaleStatusTracked.ClusterId,
				scaleStatusTracked.TenantId,
				scaleStatusTracked.ProjectId,
				scaleStatusTracked.Namespace,
				compMemberStatus.GroupId,
				string(compMemberStatus.ComponentType),
				string(compMemberStatus.ComponentStatus),
				compMemberStatus.InstanceTypeScalePhase,
				string(compMemberStatus.MemberScaleType),
				memberStatus.Name,
			))
		}
	}
	return output
}

func (s scaleStatus) ExtractMore(obj *unstructured.Unstructured) []prometheus.Metric {
	scaleStatusTracked, err := s.unMarshalScaleStatusTracked(obj)
	if err != nil || scaleStatusTracked == nil {
		return nil
	}
	output := []prometheus.Metric{}
	output = append(output, s.extractTikvOrTiFlashStartTimestamp(scaleStatusTracked)...)
	output = append(output, s.extractTikvOrTiFlashPodCreateTimestamp(scaleStatusTracked)...)
	output = append(output, s.extractTikvOrTiFlashRegionCount(scaleStatusTracked)...)
	output = append(output, s.extractTikvOrTiFlashLeaderCount(scaleStatusTracked)...)
	output = append(output, s.extractLastUpdateTimeFromTiDBCloud(scaleStatusTracked))
	output = append(output, s.extractComponentStatus(scaleStatusTracked)...)
	output = append(output, s.extractComponentMemberStatus(scaleStatusTracked)...)
	return output
}

func (s scaleStatus) Extract(obj *unstructured.Unstructured) prometheus.Metric {
	scaleStatusTracked, err := s.unMarshalScaleStatusTracked(obj)
	if err != nil || scaleStatusTracked == nil {
		return nil
	}
	return prometheus.MustNewConstMetric(
		scaleStatusTrackedMetric,
		prometheus.GaugeValue,
		float64(scaleStatusTracked.LastProbeTime.Unix()),
		scaleStatusTracked.ClusterId,
		scaleStatusTracked.TenantId,
		scaleStatusTracked.ProjectId,
		scaleStatusTracked.Namespace,
		strconv.FormatBool(scaleStatusTracked.IsSynced),
	)
}
