package gitopsmigrate

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

	"github.com/tidbcloud/config-center/pkg/utils"
	pb "github.com/tidbcloud/config-center/proto/config_center"
	cloudv1 "github.com/tidbcloud/infra-api/api/v1"
	"golang.org/x/mod/semver"
	"google.golang.org/protobuf/types/known/structpb"
	"k8s.io/utils/ptr"
	"sigs.k8s.io/controller-runtime/pkg/client"
)

func ImportClusterConfigWithValidate(ccc pb.ConfigCenterServiceClient, kubec client.Client, clusterID string, cluster *cloudv1.Cluster) error {
	meta, err := ImportClusterConfigFromInfra(ccc, kubec, clusterID, cluster, true)
	if err != nil {
		return err
	}

	configRevision, err := GetConfigRevisionByCluster(context.Background(), kubec, cluster)
	if err != nil {
		return err
	}
	diff, err := Diff(meta, configRevision, cluster.Spec.Cluster.TiFlash.InstanceType != "")
	if diff {
		return err
	}
	meta2, err := ImportClusterConfigFromInfra(ccc, kubec, clusterID, cluster, false)

	diff, err = Diff(meta2, configRevision, cluster.Spec.Cluster.TiFlash.InstanceType != "")
	if diff {
		return err
	}
	return nil
}

func ImportClusterConfigFromInfra(ccc pb.ConfigCenterServiceClient, kubec client.Client, clusterID string, cluster *cloudv1.Cluster, dryRun bool) (*pb.UpdateClusterMetaResponse, error) {
	version := cluster.Status.DB.Version
	cloudProvider := cluster.Spec.Kubernetes.Provider
	if cloudProvider == "gke" {
		cloudProvider = "gcp"
	}
	if cloudProvider == "eks" {
		cloudProvider = "aws"
	}
	metaReq := pb.UpdateClusterMetaRequest{
		ClusterType:  pb.ClusterType_DEDICATED_TIDB,
		ClusterId:    clusterID,
		Version:      &version,
		ValidateOnly: dryRun,
	}
	gitopsC := Gitops{Cli: kubec}
	clusterConfig, err := gitopsC.DescribeClusterTenantOverlay(cluster)
	if err != nil {
		return nil, err
	}

	configRevision, err := GetConfigRevisionByCluster(context.Background(), kubec, cluster)
	if err != nil {
		panic(err)
	}

	var tidbTag, pdTag, tikvTag, tiflashTag, ticdcTag, backupTag, restoreTag string
	if clusterConfig != nil && clusterConfig.Spec.TiDB != nil && clusterConfig.Spec.TiDB.Tag != nil {
		tidbTag = *clusterConfig.Spec.TiDB.Tag
	}
	if clusterConfig != nil && clusterConfig.Spec.PD != nil && clusterConfig.Spec.PD.Tag != nil {
		pdTag = *clusterConfig.Spec.PD.Tag
	}
	if clusterConfig != nil && clusterConfig.Spec.TiKV != nil && clusterConfig.Spec.TiKV.Tag != nil {
		tikvTag = *clusterConfig.Spec.TiKV.Tag
	}
	if clusterConfig != nil && clusterConfig.Spec.TiFlash != nil && clusterConfig.Spec.TiFlash.Tag != nil {
		tiflashTag = *clusterConfig.Spec.TiFlash.Tag
		if clusterID == "1379661944646415065" && version == "v7.1.5" {
			tiflashTag = "v7.1.5"
		}
	}
	if clusterConfig != nil && clusterConfig.Spec.TiCDC != nil && clusterConfig.Spec.TiCDC.Tag != nil {
		ticdcTag = *clusterConfig.Spec.TiCDC.Tag
	}
	if clusterConfig != nil && clusterConfig.Spec.Backup != nil && clusterConfig.Spec.Backup.Tag != nil {
		backupTag = *clusterConfig.Spec.Backup.Tag
	}
	if clusterConfig != nil && clusterConfig.Spec.Restore != nil && clusterConfig.Spec.Restore.Tag != nil {
		restoreTag = *clusterConfig.Spec.Restore.Tag
	}
	metaReq.ComponentRequests = append(metaReq.ComponentRequests, &pb.UpdateClusterComponentRequest{
		ComponentName:     "tidb",
		Version:           &version,
		CloudProvider:     &cloudProvider,
		NodeInstance:      &cluster.Spec.Cluster.TiDB.InstanceType,
		OverwriteImageTag: &tidbTag,
		AllowMissing:      true,
	}, &pb.UpdateClusterComponentRequest{
		ComponentName:     "pd",
		Version:           &version,
		CloudProvider:     &cloudProvider,
		NodeInstance:      &cluster.Spec.Cluster.PD.InstanceType,
		OverwriteImageTag: &pdTag,
		AllowMissing:      true,
	}, &pb.UpdateClusterComponentRequest{
		ComponentName:     "tikv",
		Version:           &version,
		CloudProvider:     &cloudProvider,
		NodeInstance:      &cluster.Spec.Cluster.TiKV.InstanceType,
		OverwriteImageTag: &tikvTag,
		AllowMissing:      true,
	}, &pb.UpdateClusterComponentRequest{
		ComponentName:     "ticdc",
		Version:           &version,
		CloudProvider:     &cloudProvider,
		NodeInstance:      nil,
		OverwriteImageTag: &ticdcTag,
		AllowMissing:      true,
	}, &pb.UpdateClusterComponentRequest{
		ComponentName:     "backup",
		Version:           &version,
		CloudProvider:     &cloudProvider,
		NodeInstance:      nil,
		OverwriteImageTag: &backupTag,
		AllowMissing:      true,
	}, &pb.UpdateClusterComponentRequest{
		ComponentName:     "restore",
		Version:           &version,
		CloudProvider:     &cloudProvider,
		NodeInstance:      nil,
		OverwriteImageTag: &restoreTag,
		AllowMissing:      true,
	})
	if cluster.Spec.Cluster.TiFlash.InstanceType != "" {
		metaReq.ComponentRequests = append(metaReq.ComponentRequests, &pb.UpdateClusterComponentRequest{
			ComponentName:     "tiflash",
			Version:           &version,
			CloudProvider:     &cloudProvider,
			NodeInstance:      &cluster.Spec.Cluster.TiFlash.InstanceType,
			OverwriteImageTag: &tiflashTag,
			AllowMissing:      true,
		})
	}

	var tidbConfig, pdConfig, tikvConfig, tiflashConfig, learnerConfig, ticdcConfig, backupConfig, restoreConfig map[string]any
	if clusterConfig != nil && clusterConfig.Spec.TiDB != nil && clusterConfig.Spec.TiDB.Config != nil {
		tidbConfig = clusterConfig.Spec.TiDB.Config.MP
	}
	if clusterConfig != nil && clusterConfig.Spec.PD != nil && clusterConfig.Spec.PD.Config != nil {
		pdConfig = clusterConfig.Spec.PD.Config.MP
	}
	if clusterConfig != nil && clusterConfig.Spec.TiKV != nil && clusterConfig.Spec.TiKV.Config != nil {
		tikvConfig = clusterConfig.Spec.TiKV.Config.MP
	}
	if clusterConfig != nil && clusterConfig.Spec.TiFlash != nil && clusterConfig.Spec.TiFlash.Config != nil {
		tiflashConfig = clusterConfig.Spec.TiFlash.Config.MP
	}
	if clusterConfig != nil && clusterConfig.Spec.TiFlashLearner != nil && clusterConfig.Spec.TiFlashLearner.Config != nil {
		learnerConfig = clusterConfig.Spec.TiFlashLearner.Config.MP
	}
	if clusterConfig != nil && clusterConfig.Spec.TiCDC != nil && clusterConfig.Spec.TiCDC.Config != nil {
		ticdcConfig = clusterConfig.Spec.TiCDC.Config.MP
	}
	if clusterConfig != nil && clusterConfig.Spec.Backup != nil && clusterConfig.Spec.Backup.Config != nil {
		backupConfig = clusterConfig.Spec.Backup.Config.MP
	}
	if clusterConfig != nil && clusterConfig.Spec.Restore != nil && clusterConfig.Spec.Restore.Config != nil {
		restoreConfig = clusterConfig.Spec.Restore.Config.MP
	}
	req, err := genConfigReq(ccc, clusterID, version, "tidb", cloudProvider, cluster.Spec.Cluster.TiDB.InstanceType, tidbConfig, configRevision.Spec.TiDB)
	if err != nil {
		return nil, err
	}
	metaReq.ConfigurationFileRequests = append(metaReq.ConfigurationFileRequests, req)
	req, err = genConfigReq(ccc, clusterID, version, "pd", cloudProvider, cluster.Spec.Cluster.PD.InstanceType, pdConfig, configRevision.Spec.PD)
	if err != nil {
		return nil, err
	}
	metaReq.ConfigurationFileRequests = append(metaReq.ConfigurationFileRequests, req)
	req, err = genConfigReq(ccc, clusterID, version, "tikv", cloudProvider, cluster.Spec.Cluster.TiKV.InstanceType, tikvConfig, configRevision.Spec.TiKV)
	if err != nil {
		return nil, err
	}
	if cluster.Spec.Cluster.TiFlash.InstanceType != "" {
		metaReq.ConfigurationFileRequests = append(metaReq.ConfigurationFileRequests, req)
		req, err = genConfigReq(ccc, clusterID, version, "tiflash", cloudProvider, cluster.Spec.Cluster.TiFlash.InstanceType, tiflashConfig, configRevision.Spec.TiFlash)
		if err != nil {
			return nil, err
		}
		metaReq.ConfigurationFileRequests = append(metaReq.ConfigurationFileRequests, req)
		req, err = genConfigReq(ccc, clusterID, version, "tiflash_learner", cloudProvider, cluster.Spec.Cluster.TiFlash.InstanceType, learnerConfig, configRevision.Spec.TiFlashLearner)
		if err != nil {
			return nil, err
		}
	}
	metaReq.ConfigurationFileRequests = append(metaReq.ConfigurationFileRequests, req)
	req, err = genConfigReq(ccc, clusterID, version, "ticdc", cloudProvider, "", ticdcConfig, configRevision.Spec.TiCDC)
	if err != nil {
		return nil, err
	}
	metaReq.ConfigurationFileRequests = append(metaReq.ConfigurationFileRequests, req)
	req, err = genConfigReq(ccc, clusterID, version, "backup", cloudProvider, "", backupConfig, configRevision.Spec.Backup)
	if err != nil {
		return nil, err
	}
	metaReq.ConfigurationFileRequests = append(metaReq.ConfigurationFileRequests, req)
	req, err = genConfigReq(ccc, clusterID, version, "restore", cloudProvider, "", restoreConfig, configRevision.Spec.Restore)
	if err != nil {
		return nil, err
	}
	metaReq.ConfigurationFileRequests = append(metaReq.ConfigurationFileRequests, req)

	return ccc.UpdateClusterMeta(context.Background(), &metaReq)
}

// gen UpdateClusterConfigurationFileRequest for tidb/pd/tikv/tiflash/tiflash_learner/ticdc/backup/restore
func genConfigReq(c pb.ConfigCenterServiceClient, clusterID, version, configType, cloudProvider, nodeInstance string, tenantAndClusterConfig map[string]any, configRevision *cloudv1.Configuration) (*pb.UpdateClusterConfigurationFileRequest, error) {
	belongTo := configType
	if configType == "tiflash_learner" {
		belongTo = "tiflash"
	}
	fullKV := utils.FlattenMap(configRevision.Config.MP)
	tenantAndClusterKV := utils.FlattenMap(tenantAndClusterConfig)
	customConfig := make(map[string]*pb.ParamItem)
	/*
		for k, v := range tenantAndlusterKV {
			anyV, err := structpb.NewValue(v)
			if err != nil {
				return nil, err
			}
			customConfig[k] = &pb.ParamItem{
				Value:   anyV,
				Comment: "",
			}
		}*/

	template, err := c.SearchDefaultConfigurationTemplate(context.Background(), &pb.SearchDefaultConfigurationTemplateRequest{
		ClusterType:       pb.ClusterType_DEDICATED_TIDB,
		ConfigurationType: configType,
		Version:           version,
		CloudProvider:     cloudProvider,
		NodeInstance:      nodeInstance,
	})
	if err != nil {
		return nil, err
	}
	templateID := template.GetTemplateId()
	needFix, id, err := fixTemplateByCluster(c, clusterID, configType, version, fullKV, templateID)
	if err != nil {
		return nil, err
	}
	if needFix {
		templateID = id
	}

	for k, v := range fullKV {
		/*
			if templateV, ok := template.Data[k]; ok {
				if templateV == v {
					if _, ok := tenantAndClusterKV[k]; !ok {
						// if the value is the same as template and not exist in tenant nor cluster overlay, it is not custom param
						continue
					}
				}
			}*/
		if _, ok := tenantAndClusterKV[k]; !ok {
			if _, ok := template.Data[k]; ok {
				// if the value is the same as template and not exist in tenant nor cluster overlay, it is not custom param
				continue
			} else {
				fmt.Printf("warn: %s not exist in tenant nor cluster overlay\n", k)
			}
		}
		anyV, err := structpb.NewValue(v)
		if err != nil {
			return nil, err
		}
		customConfig[k] = &pb.ParamItem{
			Value:   anyV,
			Comment: "",
		}
	}

	return &pb.UpdateClusterConfigurationFileRequest{
		ConfigurationFilename: configType,
		ConfigurationType:     &configType,
		BelongToComponent:     &belongTo,
		Format:                ptr.To(ccformat(configType)),
		TemplateId:            &templateID,
		CustomParams:          customConfig,
		AllowMissing:          true,
	}, nil
}

func fixTemplateByCluster(ccc pb.ConfigCenterServiceClient, clusterID, configType, version string, fullKV map[string]any, originTemplate int64) (bool, int64, error) {
	if (clusterID == "1379661944640464091" && configType == "tikv") ||
		(clusterID == "1379661944638574071" && configType == "tikv") ||
		(clusterID == "1379661944633594066" && configType == "tikv") ||
		clusterID == "1379661944640134080" ||
		(clusterID == "1379661944646413872" && configType == "tiflash") ||
		(clusterID == "1379661944646414004" && configType == "tiflash") {
		data := make(map[string]*pb.ParamItem)
		for k, v := range fullKV {
			anyV, err := structpb.NewValue(v)
			if err != nil {
				return true, 0, err
			}
			data[k] = &pb.ParamItem{
				Value: anyV,
			}
		}
		t, err := ccc.CreateConfigurationTemplate(context.Background(), &pb.CreateConfigurationTemplateRequest{
			ClusterType:       pb.ClusterType_DEDICATED_TIDB,
			ConfigurationType: configType,
			TemplateName:      configType + "_for_cluster_" + clusterID,
			Data:              data,
		})
		if err != nil {
			return true, 0, err
		}
		return true, t.GetTemplateId(), nil
	} else if (semver.Compare(version, "v8.1.0") >= 0) && configType == "tiflash" {
		resp, err := ccc.GetConfigurationTemplate(context.Background(), &pb.GetConfigurationTemplateRequest{
			ClusterType:       pb.ClusterType_DEDICATED_TIDB,
			ConfigurationType: configType,
			TemplateId:        originTemplate,
		})
		if err != nil {
			return true, 0, err
		}
		resp.Data["profiles.default.no-value-for-migrate"] = &pb.ParamItem{
			Value: nil,
		}
		t, err := ccc.CreateConfigurationTemplate(context.Background(), &pb.CreateConfigurationTemplateRequest{
			ClusterType:       pb.ClusterType_DEDICATED_TIDB,
			ConfigurationType: configType,
			TemplateName:      configType + "_for_cluster_" + clusterID,
			Data:              resp.Data,
		})
		if err != nil {
			return true, 0, err
		}
		return true, t.GetTemplateId(), nil
	}

	return false, 0, nil
}

// return (isDiff, type, old, new)
func Diff(meta *pb.UpdateClusterMetaResponse, configRevision *cloudv1.ConfigRevision, tiflashExist bool) (bool, error) {
	type DiffInfo struct {
		Name string
		Old  string
		New  string
	}
	diffs := []DiffInfo{}

	tidb := configRevision.Spec.TiDB
	if tidb != nil {
		config := getConfigFromSlice(meta.ModifiedConfigurationFiles, "tidb")
		text, err := tidb.Config.MarshalTOML()
		if err != nil {
			panic(err)
		}
		if string(text) != config.GetText() {
			diffs = append(diffs, DiffInfo{"tidb", string(text), config.GetText()})
		}

		comp := getComponentFromSlice(meta.ModifiedComponents, "tidb")
		if *tidb.BaseImage != comp.GetBaseImage() {
			diffs = append(diffs, DiffInfo{"tidb", *tidb.BaseImage, comp.GetBaseImage()})
		}
		if *tidb.Tag != comp.GetEffectiveImageTag() {
			diffs = append(diffs, DiffInfo{"tidb", *tidb.Tag, comp.GetEffectiveImageTag()})
		}
	}
	pd := configRevision.Spec.PD
	if pd != nil {
		config := getConfigFromSlice(meta.ModifiedConfigurationFiles, "pd")
		text, err := pd.Config.MarshalTOML()
		if err != nil {
			panic(err)
		}
		if string(text) != config.GetText() {
			diffs = append(diffs, DiffInfo{"pd", string(text), config.GetText()})
		}

		comp := getComponentFromSlice(meta.ModifiedComponents, "pd")
		if *pd.BaseImage != comp.GetBaseImage() {
			diffs = append(diffs, DiffInfo{"pd", *pd.BaseImage, comp.GetBaseImage()})
		}
		if *pd.Tag != comp.GetEffectiveImageTag() {
			diffs = append(diffs, DiffInfo{"pd", *pd.Tag, comp.GetEffectiveImageTag()})
		}
	}
	tikv := configRevision.Spec.TiKV
	if tikv != nil {
		config := getConfigFromSlice(meta.ModifiedConfigurationFiles, "tikv")
		text, err := tikv.Config.MarshalTOML()
		if err != nil {
			panic(err)
		}
		if string(text) != config.GetText() {
			diffs = append(diffs, DiffInfo{"tikv", string(text), config.GetText()})
		}

		comp := getComponentFromSlice(meta.ModifiedComponents, "tikv")
		if *tikv.BaseImage != comp.GetBaseImage() {
			diffs = append(diffs, DiffInfo{"tikv", *tikv.BaseImage, comp.GetBaseImage()})
		}
		if *tikv.Tag != comp.GetEffectiveImageTag() {
			diffs = append(diffs, DiffInfo{"tikv", *tikv.Tag, comp.GetEffectiveImageTag()})
		}
	}
	tiflash := configRevision.Spec.TiFlash
	if tiflash != nil && tiflashExist {
		config := getConfigFromSlice(meta.ModifiedConfigurationFiles, "tiflash")
		text, err := tiflash.Config.MarshalTOML()
		if err != nil {
			panic(err)
		}
		if string(text) != config.GetText() {
			diffs = append(diffs, DiffInfo{"tiflash", string(text), config.GetText()})
		}

		comp := getComponentFromSlice(meta.ModifiedComponents, "tiflash")
		if *tiflash.BaseImage != comp.GetBaseImage() {
			diffs = append(diffs, DiffInfo{"tiflash", *tiflash.BaseImage, comp.GetBaseImage()})
		}
		if *tiflash.Tag != comp.GetEffectiveImageTag() {
			diffs = append(diffs, DiffInfo{"tiflash", *tiflash.Tag, comp.GetEffectiveImageTag()})
		}
	}
	tiflashLearner := configRevision.Spec.TiFlashLearner
	// skif diff for tiflash learner v6.5.x
	if tiflashLearner != nil && tiflashExist &&
		!strings.HasPrefix(configRevision.Spec.Version, "v6.5") {
		config := getConfigFromSlice(meta.ModifiedConfigurationFiles, "tiflash_learner")
		text, err := tiflashLearner.Config.MarshalTOML()
		if err != nil {
			panic(err)
		}
		if string(text) != config.GetText() {
			diffs = append(diffs, DiffInfo{"tiflash_learner", string(text), config.GetText()})
		}
	}

	ticdc := configRevision.Spec.TiCDC
	if ticdc != nil {
		config := getConfigFromSlice(meta.ModifiedConfigurationFiles, "ticdc")
		text, err := ticdc.Config.MarshalTOML()
		if err != nil {
			panic(err)
		}
		if string(text) != config.GetText() {
			diffs = append(diffs, DiffInfo{"ticdc", string(text), config.GetText()})
		}

		comp := getComponentFromSlice(meta.ModifiedComponents, "ticdc")
		if *ticdc.BaseImage != comp.GetBaseImage() {
			diffs = append(diffs, DiffInfo{"ticdc", *ticdc.BaseImage, comp.GetBaseImage()})
		}
		if *ticdc.Tag != comp.GetEffectiveImageTag() {
			diffs = append(diffs, DiffInfo{"ticdc", *ticdc.Tag, comp.GetEffectiveImageTag()})
		}
	}

	backup := configRevision.Spec.Backup
	if backup != nil {
		config := getConfigFromSlice(meta.ModifiedConfigurationFiles, "backup")
		text, err := json.Marshal(backup.Config.MP)
		if err != nil {
			panic(err)
		}
		if string(text) != config.GetText() {
			diffs = append(diffs, DiffInfo{"backup", string(text), config.GetText()})
		}

		comp := getComponentFromSlice(meta.ModifiedComponents, "backup")
		if *backup.BaseImage != comp.GetBaseImage() {
			diffs = append(diffs, DiffInfo{"backup", *backup.BaseImage, comp.GetBaseImage()})
		}
		if *backup.Tag != comp.GetEffectiveImageTag() {
			diffs = append(diffs, DiffInfo{"backup", *backup.Tag, comp.GetEffectiveImageTag()})
		}
	}

	restore := configRevision.Spec.Restore
	if restore != nil {
		config := getConfigFromSlice(meta.ModifiedConfigurationFiles, "restore")
		text, err := json.Marshal(restore.Config.MP)
		if err != nil {
			panic(err)
		}
		if string(text) != config.GetText() {
			diffs = append(diffs, DiffInfo{"restore", string(text), config.GetText()})
		}

		comp := getComponentFromSlice(meta.ModifiedComponents, "restore")
		if *restore.BaseImage != comp.GetBaseImage() {
			diffs = append(diffs, DiffInfo{"restore", *restore.BaseImage, comp.GetBaseImage()})
		}
		if *restore.Tag != comp.GetEffectiveImageTag() {
			diffs = append(diffs, DiffInfo{"restore", *restore.Tag, comp.GetEffectiveImageTag()})
		}
	}
	if len(diffs) > 0 {
		err := fmt.Errorf("(%s)cluster config is different:\n", configRevision.Spec.Version)
		for _, info := range diffs {
			err = errors.Join(err, fmt.Errorf("component/config %s:\n---gitops---\n%s\n---config-center---\n%s\n---end---\nignore import\n", info.Name, info.Old, info.New))
		}
		return true, err
	}
	return false, nil
}
