package gitopsfile

import (
	"bytes"
	"context"
	"fmt"
	"log/slog"
	"os"
	"path/filepath"
	"strings"

	"github.com/BurntSushi/toml"
	"github.com/tidbcloud/cluster-tier-config/script/model"
	"github.com/tidbcloud/config-center/pkg/utils"
	pb "github.com/tidbcloud/config-center/proto/config_center"
	"golang.org/x/mod/semver"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"
	"google.golang.org/protobuf/encoding/protojson"
	"google.golang.org/protobuf/types/known/structpb"
	"gopkg.in/yaml.v3"
)

func ImportBaseFromGitops(client pb.ConfigCenterServiceClient, gitopsPath, tierPath, minVersion, maxVersion string, updateDefaultVersion bool) error {
	err := ImportDefaultTemplatesFromGitops(client, gitopsPath, minVersion, maxVersion, GetAllInstancesFromTierConfig(tierPath))
	if err != nil {
		return err
	}

	err = ImportInitSql(client, gitopsPath, minVersion, maxVersion)
	if err != nil {
		return err
	}

	return ImportImagesFromGitops(client, gitopsPath, minVersion, maxVersion, updateDefaultVersion)
}

func GetAllInstancesFromTierConfig(tierPath string) map[string]map[string]bool {
	filenames := []string{"tidb", "tikv", "pd", "tiflash", "tiflash_learner", "restore", "backup", "ticdc", "tidb-lightning"}
	allInstances := make(map[string]map[string]bool)
	for _, filename := range filenames {
		allInstances[filename] = make(map[string]bool)
	}
	/*
		files := []string{
			"prod/tier_unit-dedicated-5-1.yaml",
			"prod/tier_unit-arm.yaml",
			"prod/tier_unit-special.yaml",
			"staging/tier_unit-dedicated-5-1.yaml",
			"staging/tier_unit-arm.yaml",
			"staging/tier_unit-special.yaml",
			"nightly/tier_unit.yaml",
			"nightly/tier_unit-arm.yaml",
		}*/
	files := []string{}
	prodfiles, err := os.ReadDir(filepath.Join(tierPath, "prod"))
	if err != nil {
		panic(err)
	}
	for _, f := range prodfiles {
		if !f.IsDir() && strings.HasPrefix(f.Name(), "tier_unit") {
			files = append(files, filepath.Join("prod", f.Name()))
		}
	}
	stagingfiles, err := os.ReadDir(filepath.Join(tierPath, "staging"))
	if err != nil {
		panic(err)
	}
	for _, f := range stagingfiles {
		if !f.IsDir() && strings.HasPrefix(f.Name(), "tier_unit") {
			files = append(files, filepath.Join("staging", f.Name()))
		}
	}
	devfiles, err := os.ReadDir(filepath.Join(tierPath, "nightly"))
	if err != nil {
		panic(err)
	}
	for _, f := range devfiles {
		if !f.IsDir() && strings.HasPrefix(f.Name(), "tier_unit") {
			files = append(files, filepath.Join("nightly", f.Name()))
		}
	}

	for _, f := range files {
		var tierUnits []model.TierUnitYaml
		content, err := os.ReadFile(filepath.Join(tierPath, f))
		if err != nil {
			panic(err)
		}
		err = yaml.Unmarshal(content, &tierUnits)
		if err != nil {
			panic(err)
		}
		for _, t := range tierUnits {
			if allInstances[t.ComponentType] == nil {
				continue
			}
			allInstances[t.ComponentType][fmt.Sprintf("%5s.%s", t.CloudProvider, t.Compute.InstanceType)] = true
			if t.ComponentType == "tiflash" {
				allInstances["tiflash_learner"][fmt.Sprintf("%5s.%s", t.CloudProvider, t.Compute.InstanceType)] = true
			}
		}
	}
	if len(allInstances["restore"]) > 0 || len(allInstances["backup"]) > 0 {
		panic("err")
	}
	allInstances["pd"][""] = true
	allInstances["restore"][""] = true
	allInstances["ticdc"][""] = true
	allInstances["backup"][""] = true
	allInstances["tidb-lightning"][""] = true

	return allInstances
}

func ToGitopsFilename(name string) string {
	switch name {
	case "tidb", "tikv", "tiflash", "pd", "ticdc", "tiproxy":
		return name + ".toml"
	case "tiflash_learner":
		return "tiflash-learner.toml"
	case "restore", "backup":
		return name + ".yaml"
	case "tidb-lightning":
		return "lightning.toml"
	}
	panic("unknown filename " + name)
}

func ToGitopsFormat(name string) string {
	gFilename := ToGitopsFilename(name)
	return gFilename[len(gFilename)-4:]
}

func FromGitopsFilename(name string) string {
	switch name {
	case "tidb.toml", "tikv.toml", "tiflash.toml", "pd.toml", "ticdc.toml", "tiproxy.toml":
		return strings.TrimSuffix(name, ".toml")
	case "tiflash-learner.toml":
		return "tiflash_learner"
	case "restore.yaml", "backup.yaml":
		return strings.TrimSuffix(name, ".yaml")
	case "lightning.toml":
		return "tidb-lightning"
	}
	panic("unknown filename " + name)
}

func azure2aws(instance, conifgType string) string {
	m := map[string]string{
		// arm
		"azure.Standard_D2ps_v5":  "aws.m5.large",   // 2C8G
		"azure.Standard_D8ps_v5":  "aws.m5.2xlarge", // 8C32G
		"azure.Standard_D16ps_v5": "aws.m5.4xlarge", // 16C64G
		"azure.Standard_D4pls_v5": "aws.c5.xlarge",  // 4C8G
		"azure.Standard_D8pls_v5": "aws.c5.2xlarge", // 8C16G
		"azure.Standard_E8ps_v5":  "aws.r5.2xlarge", // 8C64G
		"azure.Standard_E16ps_v5": "aws.r5.4xlarge", // 16C128G

		// intel
		"azure.Standard_D2s_v4":  "aws.m5.large",   // 2C8G
		"azure.Standard_D8s_v4":  "aws.m5.2xlarge", // 8C32G
		"azure.Standard_D16s_v4": "aws.m5.4xlarge", // 16C64G
		"azure.Standard_E8s_v4":  "aws.r5.2xlarge", // 8C64G
		"azure.Standard_E16s_v4": "aws.r5.4xlarge", // 16C128G
		"azure.Standard_D2s_v5":  "aws.m5.large",   // 2C8G
		"azure.Standard_D8s_v5":  "aws.m5.2xlarge", // 8C32G
		"azure.Standard_D16s_v5": "aws.m5.4xlarge", // 16C64G
		"azure.Standard_D4ls_v5": "aws.c5.xlarge",  // 4C8G
		"azure.Standard_D8ls_v5": "aws.c5.2xlarge", // 8C16G
		"azure.Standard_E8s_v5":  "aws.r5.2xlarge", // 8C64G
		"azure.Standard_E16s_v5": "aws.r5.4xlarge", // 16C128G

		// amd
		"azure.Standard_D2as_v5":  "aws.m5.large",
		"azure.Standard_D8as_v5":  "aws.m5.2xlarge",
		"azure.Standard_D16as_v5": "aws.m5.4xlarge",
		"azure.Standard_E8as_v5":  "aws.r5.2xlarge",
		"azure.Standard_E16as_v5": "aws.r5.4xlarge",
	}
	pdM := map[string]string{
		"azure.Standard_D4s_v4":  "aws.c5.xlarge",  // 4C16G as 4C8G
		"azure.Standard_D8s_v4":  "aws.c5.2xlarge", // 8C32G as 8C16G
		"azure.Standard_D4as_v5": "aws.c5.xlarge",  // 4C16G as 4C8G
		"azure.Standard_D8as_v5": "aws.c5.2xlarge", // 8C32G as 8C16G
		"azure.Standard_D4ps_v5": "aws.c5.xlarge",  // staging only
		"azure.Standard_D8ps_v5": "aws.c5.2xlarge", // staging only
		"azure.Standard_D4s_v5":  "aws.c5.xlarge",  // staging only
		"azure.Standard_D8s_v5":  "aws.c5.2xlarge", // staging only
	}
	if conifgType == "pd" && pdM[instance] != "" {
		return pdM[instance]
	}
	if ins, ok := m[instance]; ok {
		return ins
	}
	panic("no matched instance" + instance)
}

func ImportDefaultTemplatesFromGitops(c pb.ConfigCenterServiceClient, gitopsPath, minVersion, maxVersion string, allInstances map[string]map[string]bool) error {
	versions, err := os.ReadDir(filepath.Join(gitopsPath, "versions"))
	if err != nil {
		panic(err)
	}
	for c, i := range allInstances {
		slog.Info("prepare to import default templates", "cluster_type", c, "instance_count", len(i))
	}

	templateToID := make(map[string]map[string]int64)
	for _, v := range versions { // gitops/versions/vx.x.x
		if !v.IsDir() || semver.Compare(v.Name(), minVersion) < 0 || semver.Compare(v.Name(), maxVersion) > 0 {
			continue
		}

		baseConfigs := make(map[string]map[string]any) // filename => params
		for f, _ := range allInstances {
			// gitops/versions/vx.x.x/base/*.toml
			content, err := os.ReadFile(filepath.Join(gitopsPath, "versions", v.Name(), "base", ToGitopsFilename(f)))
			if err != nil {
				panic(err)
			}
			var structC map[string]any

			if strings.HasSuffix(ToGitopsFilename(f), ".toml") {
				_, err = toml.NewDecoder(bytes.NewReader(content)).Decode(&structC)
			} else {
				err = yaml.NewDecoder(bytes.NewReader(content)).Decode(&structC)
			}
			if err != nil {
				slog.Error("failed to decode gitops file", "path", filepath.Join(gitopsPath, "versions", v.Name(), "base", ToGitopsFilename(f)))
				panic(err)
			}

			baseConfigs[f] = structC
		}

		for name, _ := range baseConfigs {
			gFilename := ToGitopsFilename(name)
			for instName := range allInstances[name] {
				cloudProvider := ""
				nodeInstance := instName
				if len(instName) > 5 {
					cloudProvider = strings.TrimLeft(instName[:5], " ")
					nodeInstance = instName[6:]
				}
				instName = strings.TrimLeft(instName, " ")
				instancePath := filepath.Join(instName, gFilename)

				// azure support begins at v7.5.3
				if cloudProvider == "azure" {
					if semver.Compare(v.Name(), "v7.5.3") < 0 {
						continue
					} else {
						instancePath = filepath.Join(azure2aws(instName, name), gFilename)
					}
				}

				var instanceConfig map[string]any
				text, err := os.ReadFile(filepath.Join(gitopsPath, "versions", v.Name(), "instances", instancePath))
				if err != nil && !os.IsNotExist(err) {
					panic(err)
				}

				if err == nil {
					if strings.HasSuffix(gFilename, ".toml") {
						_, err = toml.NewDecoder(bytes.NewReader(text)).Decode(&instanceConfig)
					} else {
						err = yaml.NewDecoder(bytes.NewReader(text)).Decode(&instanceConfig)
					}
					if err != nil {
						slog.Error("failed to decode gitops file", "path", filepath.Join(gitopsPath, "versions", v.Name(), "instances", instancePath))
						panic(err)
					}
				}

				templateValue := utils.MergeRawConfig(baseConfigs[name], instanceConfig)
				flatten := utils.FlattenMap(templateValue)
				slog.Info("ready to import default template", "version", v.Name(), "instance", instName, "file", name, "kv_count", len(flatten), "base_kv_count", len(baseConfigs[name]), "instance_kv_count", len(instanceConfig))

				if len(flatten) == 0 {
					c.UpdateDefaultConfigurationTemplate(context.Background(), &pb.UpdateDefaultConfigurationTemplateRequest{
						ClusterType:       pb.ClusterType_DEDICATED_TIDB,
						ConfigurationType: name,
						Version:           v.Name(),
						CloudProvider:     cloudProvider,
						NodeInstance:      nodeInstance,
						TemplateId:        0,
					})
					if name == "ticdc" {
						c.UpdateDefaultConfigurationTemplate(context.Background(), &pb.UpdateDefaultConfigurationTemplateRequest{
							ClusterType:       pb.ClusterType_TICDC,
							ConfigurationType: "ticdc",
							Version:           v.Name(),
							CloudProvider:     cloudProvider,
							NodeInstance:      nodeInstance,
							TemplateId:        0,
						})
					}
					continue
				}

				buf := bytes.NewBuffer(nil)
				toml.NewEncoder(buf).Encode(templateValue)

				templateData := make(map[string]*pb.ParamItem)
				for k, v := range flatten {
					a, err := pb.NewValue(v)
					if err != nil {
						panic(err)
					}
					templateData[k] = &pb.ParamItem{
						Value:   a,
						Comment: "import from gitops",
					}
				}

				if _, ok := templateToID[name]; !ok {
					templateToID[name] = make(map[string]int64)
				}
				id, ok := templateToID[name][buf.String()]
				if !ok {
					t, err := c.CreateConfigurationTemplate(context.Background(), &pb.CreateConfigurationTemplateRequest{
						ClusterType:       pb.ClusterType_DEDICATED_TIDB,
						ConfigurationType: name,
						TemplateName:      fmt.Sprintf("gitops-%s-%s", v.Name(), name),
						Data:              templateData,
					})
					if err != nil {
						panic(err)
					}
					id = t.GetTemplateId()
					templateToID[name][buf.String()] = id
				}
				resp, err := c.UpdateDefaultConfigurationTemplate(context.Background(), &pb.UpdateDefaultConfigurationTemplateRequest{
					ClusterType:       pb.ClusterType_DEDICATED_TIDB,
					ConfigurationType: name,
					Version:           v.Name(),
					CloudProvider:     cloudProvider,
					NodeInstance:      nodeInstance,
					TemplateId:        id,
				})
				if err != nil {
					panic(err)
				}
				slog.Info("imported default template", "config_type", name, "template_id", resp.TemplateId)
				if name == "ticdc" {
					t, err := c.CreateConfigurationTemplate(context.Background(), &pb.CreateConfigurationTemplateRequest{
						ClusterType:       pb.ClusterType_TICDC,
						ConfigurationType: name,
						TemplateName:      fmt.Sprintf("gitops-%s-%s", v.Name(), name),
						Data:              templateData,
					})
					if err != nil {
						panic(err)
					}
					resp, err := c.UpdateDefaultConfigurationTemplate(context.Background(), &pb.UpdateDefaultConfigurationTemplateRequest{
						ClusterType:       pb.ClusterType_TICDC,
						ConfigurationType: "ticdc",
						Version:           v.Name(),
						CloudProvider:     cloudProvider,
						NodeInstance:      nodeInstance,
						TemplateId:        t.GetTemplateId(),
					})
					if err != nil {
						panic(err)
					}
					slog.Info("imported default template for TICDC", "config_type", "ticdc", "template_id", resp.TemplateId)
				}
			}
		}
	}

	for _, ctype := range pb.ClusterType_CHANGEFEED.AvailableConfigTypes() {
		resp, err := c.UpdateDefaultConfigurationTemplate(context.Background(), &pb.UpdateDefaultConfigurationTemplateRequest{
			ClusterType:       pb.ClusterType_CHANGEFEED,
			ConfigurationType: ctype,
			Version:           "",
			CloudProvider:     "",
			NodeInstance:      "",
			TemplateId:        0,
		})
		if err != nil {
			panic(err)
		}
		slog.Info("imported default template", "cluster_type", "CHANGEFEED", "config_type", ctype, "template_id", resp.TemplateId)
		resp, err = c.UpdateDefaultConfigurationTemplate(context.Background(), &pb.UpdateDefaultConfigurationTemplateRequest{
			ClusterType:       pb.ClusterType_CHANGEFEED_NG,
			ConfigurationType: ctype,
			Version:           "",
			CloudProvider:     "",
			NodeInstance:      "",
			TemplateId:        0,
		})
		if err != nil {
			panic(err)
		}
		slog.Info("imported default template", "cluster_type", "CHANGEFEED_NG", "config_type", ctype, "template_id", resp.TemplateId)
		resp, err = c.UpdateDefaultConfigurationTemplate(context.Background(), &pb.UpdateDefaultConfigurationTemplateRequest{
			ClusterType:       pb.ClusterType_CHANGEFEED_ESSENTIAL,
			ConfigurationType: ctype,
			Version:           "",
			CloudProvider:     "",
			NodeInstance:      "",
			TemplateId:        0,
		})
		if err != nil {
			panic(err)
		}
		slog.Info("imported default template", "cluster_type", "CHANGEFEED_ESSENTIAL", "config_type", ctype, "template_id", resp.TemplateId)
	}

	return nil
}

type meta struct {
	Version    string               `yaml:"version"`
	Components map[string]component `yaml:"images"`
}

type component struct {
	BaseImage string `yaml:"baseImage"`
	Tag       string `yaml:"tag"`
}

func resolveGitopsImage(base, version meta, name string) (component, bool) {
	image, ok := version.Components[name]
	if !ok {
		return component{}, false
	}
	if image.BaseImage == "" {
		image.BaseImage = base.Components[name].BaseImage
	}
	if image.Tag == "" {
		image.Tag = base.Components[name].Tag
	}
	return image, true
}

func resolveGitopsImageWithFallback(base, version meta, name, fallbackName string) (component, bool) {
	image, ok := version.Components[name]
	if !ok {
		return resolveGitopsImage(base, version, fallbackName)
	}
	fallback, hasFallback := resolveGitopsImage(base, version, fallbackName)
	if image.BaseImage == "" {
		image.BaseImage = base.Components[name].BaseImage
	}
	if image.BaseImage == "" && hasFallback {
		image.BaseImage = fallback.BaseImage
	}
	if image.Tag == "" {
		image.Tag = base.Components[name].Tag
	}
	if image.Tag == "" && hasFallback {
		image.Tag = fallback.Tag
	}
	return image, true
}

func updateDedicatedComponentImage(c pb.ConfigCenterServiceClient, componentName, version string, image component) {
	resp, err := c.UpdateComponentImage(context.Background(), &pb.UpdateComponentImageRequest{
		ClusterType:      pb.ClusterType_DEDICATED_TIDB,
		ComponentName:    componentName,
		ComponentVersion: version,
		BaseImage:        image.BaseImage,
		Tag:              image.Tag,
	})
	if err != nil {
		panic(err)
	}
	slog.Info("updated component image", "component", componentName, "resp", protojson.Format(resp))
}

func ImportImagesFromGitops(c pb.ConfigCenterServiceClient, path, minVersion, maxVersion string, updateDefaultVersion bool) error {
	versions, err := os.ReadDir(filepath.Join(path, "versions"))
	if err != nil {
		panic(err)
	}

	txt, err := os.ReadFile(filepath.Join(path, "meta.yaml"))
	if err != nil {
		panic(err)
	}
	var base meta
	err = yaml.Unmarshal(txt, &base)
	if err != nil {
		panic(err)
	}

	for _, v := range versions { // gitops/versions/vx.x.x
		if !v.IsDir() || semver.Compare(v.Name(), minVersion) < 0 || semver.Compare(v.Name(), maxVersion) > 0 {
			continue
		}

		txt, err := os.ReadFile(filepath.Join(path, "versions", v.Name(), "meta.yaml"))
		if err != nil {
			panic(err)
		}
		var m meta
		err = yaml.Unmarshal(txt, &m)
		if err != nil {
			slog.Error("failed to decode gitops file", "path", filepath.Join(path, "versions", v.Name(), "meta.toml"))
			panic(err)
		}
		slog.Info("decoded gitops file", "path", filepath.Join(path, "versions", v.Name(), "meta.toml"), "component_count", len(m.Components))

		for name, image := range m.Components {
			if name == "tiproxy" ||
				name == pb.DEDICATED_TIDB_COMPONENT_COMPACT_BACKUP_TIKV ||
				name == pb.DEDICATED_TIDB_COMPONENT_COMPACT_BACKUP_BR {
				continue
			}
			ccName := name
			if name == "lightning" {
				ccName = "tidb-lightning"
			}
			image, _ = resolveGitopsImage(base, m, name)
			// cdc image: only create if not exists, for both DEDICATED_TIDB and TICDC cluster types
			if name == "ticdc" {
				_, err := c.GetComponentImage(context.Background(), &pb.GetComponentImageRequest{
					ClusterType:      pb.ClusterType_DEDICATED_TIDB,
					ComponentName:    ccName,
					ComponentVersion: m.Version,
				})
				if err != nil && status.Code(err) == codes.NotFound {
					resp, err := c.UpdateComponentImage(context.Background(), &pb.UpdateComponentImageRequest{
						ClusterType:      pb.ClusterType_DEDICATED_TIDB,
						ComponentName:    ccName,
						ComponentVersion: m.Version,
						BaseImage:        image.BaseImage,
						Tag:              image.Tag,
					})
					if err != nil {
						panic(err)
					}
					slog.Info("created component image", "component", ccName, "version", m.Version, "resp", protojson.Format(resp))
				} else if err != nil {
					panic(err)
				} else {
					slog.Info("component image already exists, skipping", "clusterType", "DEDICATED_TIDB", "component", ccName, "version", m.Version)
				}

				_, err = c.GetComponentImage(context.Background(), &pb.GetComponentImageRequest{
					ClusterType:      pb.ClusterType_TICDC,
					ComponentName:    "ticdc",
					ComponentVersion: m.Version,
				})
				if err != nil && status.Code(err) == codes.NotFound {
					resp2, err := c.UpdateComponentImage(context.Background(), &pb.UpdateComponentImageRequest{
						ClusterType:      pb.ClusterType_TICDC,
						ComponentName:    "ticdc",
						ComponentVersion: m.Version,
						BaseImage:        image.BaseImage,
						Tag:              image.Tag,
					})
					if err != nil {
						panic(err)
					}
					slog.Info("created component image", "component", "ticdc", "version", m.Version, "resp", protojson.Format(resp2))
				} else if err != nil {
					panic(err)
				} else {
					slog.Info("component image already exists, skipping", "clusterType", "TICDC", "component", "ticdc", "version", m.Version)
				}
			} else {
				updateDedicatedComponentImage(c, ccName, m.Version, image)
			}
		}

		if image, ok := resolveGitopsImageWithFallback(base, m, pb.DEDICATED_TIDB_COMPONENT_COMPACT_BACKUP_TIKV, pb.DEDICATED_TIDB_COMPONENT_TIKV); ok {
			updateDedicatedComponentImage(c, pb.DEDICATED_TIDB_COMPONENT_COMPACT_BACKUP_TIKV, m.Version, image)
		}

		if image, ok := resolveGitopsImageWithFallback(base, m, pb.DEDICATED_TIDB_COMPONENT_COMPACT_BACKUP_BR, pb.DEDICATED_TIDB_COMPONENT_BACKUP); ok {
			updateDedicatedComponentImage(c, pb.DEDICATED_TIDB_COMPONENT_COMPACT_BACKUP_BR, m.Version, image)
		}
	}

	if !updateDefaultVersion {
		return nil
	}

	vr, err := c.UpdateDefaultVersion(context.Background(), &pb.UpdateDefaultVersionRequest{
		ClusterType: pb.ClusterType_DEDICATED_TIDB,
		Component:   "tidb",
		Version:     base.Version,
	})
	if err != nil {
		panic(err)
	}
	slog.Info("default version update", "resp", protojson.Format(vr))
	return nil
}

func ImportInitSql(c pb.ConfigCenterServiceClient, gitopsPath, minVersion, maxVersion string) error {
	step1 := &pb.ParamItem{
		Value: structpb.NewStringValue(`CREATE ROLE IF NOT EXISTS 'role_admin';
GRANT ALL ON *.* TO 'role_admin' WITH GRANT OPTION;
REVOKE SYSTEM_USER ON *.* FROM 'role_admin';
REVOKE SHUTDOWN, CONFIG ON *.* FROM 'role_admin';
GRANT ROLE_ADMIN ON *.* to 'role_admin';`),
		Comment: "Setup builtin roles",
	}
	step2 := &pb.ParamItem{
		Value: structpb.NewStringValue(`CREATE ROLE IF NOT EXISTS 'role_cdc_admin';
GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, DROP, ALTER, SHOW DATABASES, INDEX, CREATE VIEW ON *.* TO 'role_cdc_admin';
GRANT RESTRICTED_REPLICA_WRITER_ADMIN ON *.* TO 'role_cdc_admin';`),
		Comment: "role_cdc_admin is used by CDC",
	}
	step3 := &pb.ParamItem{
		Value: structpb.NewStringValue(`CREATE USER IF NOT EXISTS cloud_admin;

GRANT DASHBOARD_CLIENT on *.* TO 'cloud_admin'@'%';
GRANT SYSTEM_VARIABLES_ADMIN ON *.* TO 'cloud_admin'@'%';
GRANT CONNECTION_ADMIN ON *.* TO 'cloud_admin'@'%';
GRANT RESTRICTED_VARIABLES_ADMIN ON *.* TO 'cloud_admin'@'%';
GRANT RESTRICTED_STATUS_ADMIN ON *.* TO 'cloud_admin'@'%';
GRANT RESTRICTED_CONNECTION_ADMIN ON *.* TO 'cloud_admin'@'%';
GRANT RESTRICTED_USER_ADMIN ON *.* TO 'cloud_admin'@'%';
GRANT RESTRICTED_TABLES_ADMIN ON *.* TO 'cloud_admin'@'%';
GRANT RESTRICTED_REPLICA_WRITER_ADMIN ON *.* TO 'cloud_admin'@'%';
GRANT BACKUP_ADMIN, RESTORE_ADMIN ON *.* TO 'cloud_admin'@'%';
GRANT ROLE_ADMIN ON *.* to 'role_admin';
GRANT SYSTEM_USER ON *.* TO 'cloud_admin'@'%' WITH GRANT OPTION;

GRANT RELOAD ON *.* TO 'cloud_admin'@'%';
GRANT PROCESS ON *.* TO 'cloud_admin'@'%';
GRANT SHUTDOWN, CONFIG ON *.* TO 'cloud_admin'@'%';

GRANT CREATE USER, CREATE ROLE ON *.* TO 'cloud_admin'@'%';
GRANT SELECT, INSERT, UPDATE, DELETE ON mysql.* TO 'cloud_admin'@'%';
GRANT SELECT ON information_schema.* TO 'cloud_admin'@'%';
GRANT SELECT ON performance_schema.* TO 'cloud_admin'@'%';`),
		Comment: "Setup users",
	}

	step4 := &pb.ParamItem{
		Value: structpb.NewStringValue(`GRANT SELECT ON *.* TO 'cloud_admin'@'%';
GRANT INDEX ON *.* TO 'cloud_admin'@'%';
GRANT INSERT ON *.* TO 'cloud_admin'@'%';
GRANT UPDATE ON *.* TO 'cloud_admin'@'%';
GRANT DELETE ON *.* TO 'cloud_admin'@'%';
GRANT CREATE ON *.* TO 'cloud_admin'@'%';
GRANT DROP ON *.* TO 'cloud_admin'@'%';
GRANT ALTER ON *.* TO 'cloud_admin'@'%';
GRANT CREATE VIEW ON *.* TO 'cloud_admin'@'%';
GRANT SHOW DATABASES on *.* TO 'cloud_admin'@'%';`),
		Comment: "TODO(chenfei): for compatibility. We can remove the following privileges after all DP service(import, cdc, dm, etc.) have finished migrate all features to based on the delegate_user feature. Cross region requires these permissions, See https://docs.pingcap.com/tidb/stable/troubleshoot-ticdc/#when-the-sink-of-the-replication-downstream-is-tidb-or-mysql-what-permissions-do-users-of-the-downstream-database-need for more detail",
	}

	step5 := &pb.ParamItem{
		Value:   structpb.NewStringValue(`GRANT REFERENCES ON *.* TO 'cloud_admin'@'%';`),
		Comment: "used by import",
	}

	step6 := &pb.ParamItem{
		Value: structpb.NewStringValue(`REVOKE SHUTDOWN, CONFIG ON *.* FROM root;`),
	}

	// [6.1.0, v6.5.0)
	step7v1 := &pb.ParamItem{
		Value: structpb.NewStringValue(`SET GLOBAL tidb_redact_log = 1;
SET GLOBAL tidb_analyze_version = 1;`),
	}

	// [6.5.0, v7.3.0)
	step7v2 := &pb.ParamItem{
		Value: structpb.NewStringValue(`SET GLOBAL tidb_redact_log = 1;
SET GLOBAL tidb_analyze_version = 2;`),
	}

	// [7.3.0, ~)
	step7v3 := &pb.ParamItem{
		Value: structpb.NewStringValue(`SET GLOBAL tidb_redact_log = 1;
SET GLOBAL tidb_analyze_version = 2;
SET GLOBAL tidb_runtime_filter_mode = LOCAL;`),
	}

	template1 := map[string]*pb.ParamItem{
		"100": step1,
		"200": step2,
		"300": step3,
		"400": step4,
		"500": step5,
		"600": step6,
		"700": step7v1,
	}
	template2 := map[string]*pb.ParamItem{
		"100": step1,
		"200": step2,
		"300": step3,
		"400": step4,
		"500": step5,
		"600": step6,
		"700": step7v2,
	}
	template3 := map[string]*pb.ParamItem{
		"100": step1,
		"200": step2,
		"300": step3,
		"400": step4,
		"500": step5,
		"600": step6,
		"700": step7v3,
	}

	resp, err := c.CreateConfigurationTemplate(context.Background(), &pb.CreateConfigurationTemplateRequest{
		ClusterType:       pb.ClusterType_DEDICATED_TIDB,
		ConfigurationType: pb.DEDICATED_TIDB_CONFIG_INITSQL,
		TemplateName:      "imported init_sql template for [6.1.0, v6.5.0)",
		Data:              template1,
	})
	if err != nil {
		panic(err)
	}
	template1ID := resp.GetTemplateId()

	resp, err = c.CreateConfigurationTemplate(context.Background(), &pb.CreateConfigurationTemplateRequest{
		ClusterType:       pb.ClusterType_DEDICATED_TIDB,
		ConfigurationType: pb.DEDICATED_TIDB_CONFIG_INITSQL,
		TemplateName:      "imported init_sql templatem for [6.5.0, v7.3.0)",
		Data:              template2,
	})
	if err != nil {
		panic(err)
	}
	template2ID := resp.GetTemplateId()

	resp, err = c.CreateConfigurationTemplate(context.Background(), &pb.CreateConfigurationTemplateRequest{
		ClusterType:       pb.ClusterType_DEDICATED_TIDB,
		ConfigurationType: pb.DEDICATED_TIDB_CONFIG_INITSQL,
		TemplateName:      "imported init_sql template for [v7.3.0, ~)",
		Data:              template3,
	})
	if err != nil {
		panic(err)
	}
	template3ID := resp.GetTemplateId()

	versions, err := os.ReadDir(filepath.Join(gitopsPath, "versions"))
	if err != nil {
		panic(err)
	}

	for _, v := range versions { // gitops/versions/vx.x.x
		if !v.IsDir() || semver.Compare(v.Name(), minVersion) < 0 || semver.Compare(v.Name(), maxVersion) > 0 {
			continue
		}

		var templateID int64

		if semver.Compare(v.Name(), "v6.1.0") < 0 {
			continue
		} else if semver.Compare(v.Name(), "v6.5.0") < 0 {
			templateID = template1ID
		} else if semver.Compare(v.Name(), "v7.3.0") < 0 {
			templateID = template2ID
		} else {
			templateID = template3ID
		}

		_, err := c.UpdateDefaultConfigurationTemplate(context.Background(), &pb.UpdateDefaultConfigurationTemplateRequest{
			ClusterType:       pb.ClusterType_DEDICATED_TIDB,
			ConfigurationType: pb.DEDICATED_TIDB_CONFIG_INITSQL,
			Version:           v.Name(),
			CloudProvider:     "",
			NodeInstance:      "",
			TemplateId:        templateID,
		})
		if err != nil {
			panic(err)
		}
		slog.Info("INITSQL for dedicated updated", "template_id", templateID)
	}

	/* unused
	   -- configured on dev & stg but not on prod yet --
	   SET GLOBAL tidb_enable_column_tracking = ON; -- tidb >= v5.4.0
	   SET GLOBAL tidb_enable_new_only_full_group_by_check = ON; -- tidb >= v6.1.0
	   SET GLOBAL tidb_stats_cache_mem_quota = 2147483648; -- tidb >= v6.2.0`
	*/
	return nil
}

func AddTiproxyVersion(c pb.ConfigCenterServiceClient, version, tag string, setDefault bool) error {
	tiproxyTemplate := map[string]*pb.ParamItem{
		"proxy.graceful-wait-before-shutdown": {
			Value: structpb.NewNumberValue(30),
		},
		"proxy.graceful-close-conn-timeout": {
			Value: structpb.NewNumberValue(15),
		},
	}
	if semver.Compare(version, "v1.3.0") > 0 {
		tiproxyTemplate["enable-traffic-replay"] = &pb.ParamItem{
			Value:   structpb.NewBoolValue(false),
			Comment: "new config since tiproxy v1.3.1, from zhangming",
		}
		tiproxyTemplate["balance.label-name"] = &pb.ParamItem{
			Value:   structpb.NewStringValue("group"),
			Comment: "from chenfei",
		}
		tiproxyTemplate["balance.policy"] = &pb.ParamItem{
			Value: structpb.NewStringValue("connection"),
		}
	}
	t, err := c.CreateConfigurationTemplate(context.Background(), &pb.CreateConfigurationTemplateRequest{
		ClusterType:       pb.ClusterType_DEDICATED_TIDB,
		ConfigurationType: "tiproxy",
		TemplateName:      "tiproxy default",
		Data:              tiproxyTemplate,
	})
	if err != nil {
		return err
	}

	resp, err := c.UpdateDefaultConfigurationTemplate(context.Background(), &pb.UpdateDefaultConfigurationTemplateRequest{
		ClusterType:       pb.ClusterType_DEDICATED_TIDB,
		ConfigurationType: "tiproxy",
		Version:           version,
		CloudProvider:     "",
		NodeInstance:      "",
		TemplateId:        t.GetTemplateId(),
	})
	if err != nil {
		return err
	}
	fmt.Printf("tiproxy %s template: %d\n", version, resp.GetTemplateId())

	// import tiproxy
	resp2, err := c.UpdateComponentImage(context.Background(), &pb.UpdateComponentImageRequest{
		ClusterType:      pb.ClusterType_DEDICATED_TIDB,
		ComponentName:    "tiproxy",
		ComponentVersion: version,
		BaseImage:        "gcr.io/pingcap-public/dbaas/tiproxy",
		Tag:              tag,
	})
	if err != nil {
		return err
	}
	fmt.Println(resp2)

	if setDefault {
		vr, err := c.UpdateDefaultVersion(context.Background(), &pb.UpdateDefaultVersionRequest{
			ClusterType: pb.ClusterType_DEDICATED_TIDB,
			Component:   "tiproxy",
			Version:     version,
		})
		if err != nil {
			return err
		}
		fmt.Println(vr)
	}
	return nil
}
