package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"os"
	"path/filepath"
	"regexp"
	"strconv"
	"strings"
	"time"

	"github.com/jinzhu/copier"
	"github.com/mohae/deepcopy"
	"github.com/pingcap/log"
	"github.com/pkg/errors"
	"github.com/tidbcloud/config-service/api/v1alpha1"
	"github.com/tidbcloud/config-service/pkg/clinic_event"
	cloudv1 "github.com/tidbcloud/infra-api/api/v1"
	"go.uber.org/zap"
	v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"sigs.k8s.io/yaml"
)

func overlay(t *Tree, outputDir string) error {
	if err := validateTree(t); err != nil {
		return err
	}

	tmp, err := os.MkdirTemp(filepath.Dir(outputDir), ".tmp")
	if err != nil {
		return errors.Wrap(err, "Error making temp dir for output")
	}

	for _, vConfig := range t.VersionedConfigs {
		version := vConfig.Meta.Version
		if globalVersion(t) == version {
			l := &layers{
				BaseConfig:      &vConfig,
				metaOverlays:    []*Meta{t.GlobalMeta, vConfig.Meta},
				configOverlays:  nil,
				PullRequestID:   genPullRequestID(),
				PullRequestLink: genPullRequestLink(),
			}
			if err := l.renderAndWrite(tmp); err != nil {
				return err
			}
			// ignore error
			_ = l.sendClinicEvent()
		}

		for _, tOverlay := range t.TenantOverlays {
			if tenantVersion(t, tOverlay) == version {
				// Tenant's Cluster-config
				l := layers{
					Tenant:          &tOverlay.TenantID,
					BaseConfig:      &vConfig,
					metaOverlays:    []*Meta{t.GlobalMeta, vConfig.Meta, tOverlay.Meta},
					configOverlays:  []ComponentConfigs{tOverlay.Configs},
					PullRequestID:   genPullRequestID(),
					PullRequestLink: genPullRequestLink(),
				}
				if err := l.renderAndWrite(tmp); err != nil {
					return err
				}
				// ignore error
				_ = l.sendClinicEvent()
			}

			for _, cOverlay := range tOverlay.ClusterOverlays {
				if clusterVersion(t, tOverlay, cOverlay) == version {
					// Cluster's Cluster-config
					l := layers{
						Tenant:          &tOverlay.TenantID,
						Cluster:         &cOverlay.ClusterID,
						BaseConfig:      &vConfig,
						metaOverlays:    []*Meta{t.GlobalMeta, vConfig.Meta, tOverlay.Meta, cOverlay.Meta},
						configOverlays:  []ComponentConfigs{tOverlay.Configs, cOverlay.Configs},
						PullRequestID:   genPullRequestID(),
						PullRequestLink: genPullRequestLink(),
					}
					if err := l.renderAndWrite(tmp); err != nil {
						return err
					}
					// ignore error
					err = l.sendClinicEvent()
					if err != nil {
						log.Warn("send clinic event failed", zap.Error(err), zap.String("cluster", cOverlay.ClusterID))
					}
				}
			}
		}
	}
	if err := os.RemoveAll(outputDir); err != nil {
		return err
	}
	return os.Rename(tmp, outputDir)
}

type layers struct {
	Tenant          *string
	Cluster         *string
	PullRequestID   int
	PullRequestLink string

	BaseConfig     *VersionedConfig
	metaOverlays   []*Meta
	configOverlays []ComponentConfigs
}

func (l layers) toName() string {
	var sb strings.Builder
	sb.WriteString("base")
	if l.Tenant != nil {
		sb.WriteString(fmt.Sprintf("--t%s", *l.Tenant))
	}
	if l.Cluster != nil {
		sb.WriteString(fmt.Sprintf("--c%s", *l.Cluster))
	}
	return sb.String()
}

func (l layers) toLabels() map[string]string {
	m := map[string]string{}
	if l.Tenant != nil {
		m["tidbcloud.com/tenant"] = *l.Tenant
	} else {
		m["tidbcloud.com/tenant"] = "all"
	}
	if l.Cluster != nil {
		m["tidbcloud.com/cluster"] = *l.Cluster
	} else {
		m["tidbcloud.com/cluster"] = "all"
	}
	return m
}

func (l layers) genClinicEventID() string {
	contextID := "gitops"

	if l.Tenant != nil {
		contextID = fmt.Sprintf("%s-%s", contextID, *l.Tenant)
	} else {
		contextID = fmt.Sprintf("%s-%s", contextID, "0")
	}

	if l.Cluster != nil {
		contextID = fmt.Sprintf("%s-%s", contextID, *l.Cluster)
	} else {
		contextID = fmt.Sprintf("%s-%s", contextID, "0")
	}

	// Divide every 20 minutes into a time period, with the same event context id in the same time period
	nowTs := time.Now().Unix()

	// gitops v1
	return fmt.Sprintf("%s-%d-v1", contextID, nowTs-nowTs%(20*60))
}

func (l layers) sendClinicEvent() error {

	if !activityHub {
		return nil
	}

	if !filter.filterLayters(l) {
		return nil
	}

	eventRequest := clinic_event.EventRequest{
		Catalog:       fmt.Sprintf("%s-v1", clinic_event.GitopsCatalog),
		SeverityLevel: int(clinic_event.Debug),
		Payload:       make(map[string]interface{}),
		ContextID:     l.genClinicEventID(),
	}

	if l.Tenant != nil {
		eventRequest.ApplicationID = *l.Tenant
	} else {
		eventRequest.ApplicationID = "0"
	}

	if l.Cluster != nil {
		eventRequest.TargetID = *l.Cluster
	} else {
		eventRequest.TargetID = "0"
	}

	// gen payload
	payloadByte, err := json.Marshal(l)
	if err != nil {
		return err
	}
	payloadDecoder := json.NewDecoder(bytes.NewBuffer(payloadByte))
	payloadDecoder.UseNumber()
	_ = payloadDecoder.Decode(&eventRequest.Payload)

	eventRequest.Name = fmt.Sprintf("%s-v1:overlay", clinic_event.GitopsCatalog)
	eventRequest.CalibrationTime = uint64(time.Now().Unix())
	eventRequest.Payload["summary"] = gitCommitMsg

	return clinic_event.SendEvent(eventRequest)
}

func (l *layers) renderAndWrite(dir string) error {
	cc, err := l.render()
	if err != nil {
		return err
	}
	b, err := yaml.Marshal(cc)
	if err != nil {
		return err
	}
	return os.WriteFile(filepath.Join(dir, fmt.Sprintf("%s.yaml", cc.Name)), b, 0644)
}

func (l *layers) render() (*v1alpha1.ClusterConfig, error) {
	v, err := overlayMeta(l.metaOverlays)
	if err != nil {
		return nil, err
	}
	vc, err := overlayConfigs(l.BaseConfig, l.configOverlays)
	if err != nil {
		return nil, err
	}
	images := v.Images
	cc := &v1alpha1.ClusterConfig{
		TypeMeta: v1.TypeMeta{
			APIVersion: v1alpha1.GroupVersion.String(),
			Kind:       v1alpha1.ClusterConfigKind,
		},
		ObjectMeta: v1.ObjectMeta{
			Name:        l.toName(),
			Labels:      l.toLabels(),
			Annotations: make(map[string]string),
		},
		Spec: v1alpha1.ClusterConfigSpec{
			Version:      v.Version,
			UpdatePolicy: v1alpha1.UpdatePolicy(v.UpdatePolicy),
			TiDB: &v1alpha1.Configuration{
				BaseImage: *images.TiDB.BaseImage,
				Tag:       images.TiDB.Tag,
				Config:    vc.Configs.GetConfig(ComponentTiDB),
				Overlays:  computeComponentInstanceOverlays(vc.InstanceOverlays, ComponentTiDB),
			},
			TiKV: &v1alpha1.Configuration{
				BaseImage: *images.TiKV.BaseImage,
				Tag:       images.TiKV.Tag,
				Config:    vc.Configs.GetConfig(ComponentTiKV),
				Overlays:  computeComponentInstanceOverlays(vc.InstanceOverlays, ComponentTiKV),
			},
			PD: &v1alpha1.Configuration{
				BaseImage: *images.PD.BaseImage,
				Tag:       images.PD.Tag,
				Config:    vc.Configs.GetConfig(ComponentPD),
				Overlays:  computeComponentInstanceOverlays(vc.InstanceOverlays, ComponentPD),
			},
			TiFlash: &v1alpha1.Configuration{
				BaseImage: *images.TiFlash.BaseImage,
				Tag:       images.TiFlash.Tag,
				Config:    vc.Configs.GetConfig(ComponentTiFlash),
				Overlays:  computeComponentInstanceOverlays(vc.InstanceOverlays, ComponentTiFlash),
			},
			TiFlashLearner: &v1alpha1.Configuration{
				BaseImage: *images.TiFlash.BaseImage,
				Tag:       images.TiFlash.Tag,
				Config:    vc.Configs.GetConfig(ComponentTiFlashLearner),
				Overlays:  computeComponentInstanceOverlays(vc.InstanceOverlays, ComponentTiFlashLearner),
			},
			Backup: &v1alpha1.Configuration{
				BaseImage: *images.Backup.BaseImage,
				Tag:       images.Backup.Tag,
				Config:    vc.Configs.GetConfig(ComponentBackup),
			},
			Restore: &v1alpha1.Configuration{
				BaseImage: *images.Restore.BaseImage,
				Tag:       images.Backup.Tag,
				Config:    vc.Configs.GetConfig(ComponentRestore),
			},
		},
	}
	cc.Labels["tidbcloud.com/managed-by"] = "cicd"
	cc.Labels["tidbcloud.com/module"] = "tidb-configuration"

	if img := images.TiProxy; img != nil {
		cc.Spec.TiProxy = &v1alpha1.Configuration{
			BaseImage: *img.BaseImage,
			Tag:       img.Tag,
			Config:    vc.Configs.GetConfig(ComponentTiProxy),
			Overlays:  computeComponentInstanceOverlays(vc.InstanceOverlays, ComponentTiProxy),
		}
	}

	cc.Annotations[clinic_event.ClinicEnableGOMEMLIMIT] = strconv.FormatBool(v.EnableGOMEMLIMIT)
	// add the following fields into cc.Labels is dangerous and may not be used, so we add them into cc.Annotations
	cc.Annotations["tidbcloud.com/pr-number"] = gitPRNum
	cc.Annotations["tidbcloud.com/pr-created-by"] = gitPRCreatedBy
	if meta {
		cc.Spec.ConfigMeta = &v1alpha1.ConfigMeta{
			GitCommitID:   gitCommit,
			ConfigVersion: version,
		}
	}

	return cc, nil
}

// overlay image settings to generate a new one, latter one overrides former one
func overlayMeta(vs []*Meta) (*Meta, error) {
	out := &Meta{}
	for _, v := range vs {
		if v == nil {
			continue
		}
		err := mergeVersion(out, v)
		if err != nil {
			return nil, err
		}
	}
	return out, nil
}

// overlay configs to generate a new one, latter one overrides former one
func overlayConfigs(base *VersionedConfig, overlays []ComponentConfigs) (*VersionedConfig, error) {
	output := deepcopy.Copy(base).(*VersionedConfig)

	// 1. compute full instance config
	for _, instance := range output.InstanceOverlays {
		for _, conf := range instance.Configs {
			fullConfig := map[string]interface{}{}
			if err := merge(fullConfig, output.Configs.GetConfig(conf.Component).MP); err != nil {
				return nil, err
			}
			if err := merge(fullConfig, conf.Config.MP); err != nil {
				return nil, err
			}
			conf.Config.MP = fullConfig
		}
	}

	// 2. process overlay
	for _, o := range overlays {
		// apply overlays to base configs
		for _, base := range output.Configs {
			overlay := o.Get(base.Component)
			if overlay == nil {
				continue
			}
			if err := merge(base.Config.MP, overlay.Config.MP); err != nil {
				return nil, err
			}
		}

		// apply overlays to instance configs
		for _, instance := range output.InstanceOverlays {
			for _, instanceConfig := range instance.Configs {
				overlay := o.Get(instanceConfig.Component)
				if overlay == nil {
					continue
				}
				if err := merge(instanceConfig.Config.MP, overlay.Config.MP); err != nil {
					return nil, err
				}
			}
		}
	}

	return output, nil
}

func globalVersion(t *Tree) string {
	return t.GlobalMeta.Version
}

func tenantVersion(t *Tree, Tenant TenantOverlay) string {
	if Tenant.Meta != nil && Tenant.Meta.Version != "" {
		return Tenant.Meta.Version
	}
	return globalVersion(t)
}

func clusterVersion(t *Tree, Tenant TenantOverlay, Cluster ClusterOverlay) string {
	if Cluster.Meta != nil && Cluster.Meta.Version != "" {
		return Cluster.Meta.Version
	}
	return tenantVersion(t, Tenant)
}

func genPullRequestID() int {
	// e.g. match[]: Update meta.yaml (#1654)
	prIDRegex := regexp.MustCompile(`\(#(.*?)\)`)
	matchs := prIDRegex.FindStringSubmatch(gitCommitMsg)

	if len(matchs) >= 2 {
		prIDStr := matchs[1]

		prID, err := strconv.Atoi(prIDStr)
		if err != nil {
			return 0
		}

		return prID
	}

	// 0 means not match pull request id
	return 0
}

func genPullRequestLink() string {
	prID := genPullRequestID()

	if prID == 0 {
		return ""
	}

	return fmt.Sprintf("%s/pull/%d", tidbConfigurationRepo, prID)
}

func computeComponentInstanceOverlays(is []InstanceOverlay, component Component) []v1alpha1.Overlay {
	var overlays []v1alpha1.Overlay
	for _, i := range is {
		if config := i.Configs.GetConfig(component); config != nil {
			instanceType := i.InstanceType
			overlays = append(overlays, v1alpha1.Overlay{
				InstanceType: instanceType,
				Config:       config,
			})
		}
	}
	return overlays
}

func mergeVersion(base, overlay *Meta) error {
	err := copier.CopyWithOption(base, overlay, copier.Option{
		IgnoreEmpty: true,
		DeepCopy:    true,
	})
	if err != nil {
		return errors.Wrapf(err, "error applying from %+v to %+v", overlay, base)
	}
	return nil
}

// merge apply the overlay struct to the base struct and override the same key
func merge(base map[string]interface{}, overlay map[string]interface{}, keyPath ...string) error {
	child, err := GetChild(base, keyPath...)
	if err != nil {
		return errors.Wrap(err, "get child")
	}
	for k, v := range overlay {
		switch v := v.(type) {
		case map[string]interface{}:
			if _, ok := child[k]; !ok {
				child[k] = map[string]interface{}{}
			}
			if err := merge(child, v, k); err != nil {
				return errors.Wrap(err, fmt.Sprintf("error overlay processing key %s", k))
			}
		default:
			child[k] = v
		}
	}
	return nil
}

func GetChild(base map[string]interface{}, keyPath ...string) (map[string]interface{}, error) {
	child := base
	for _, key := range keyPath {
		next, ok := child[key]
		if !ok {
			return nil, errors.New(fmt.Sprintf("key %s not found", strings.Join(keyPath, ".")))
		}
		child, ok = next.(map[string]interface{})
		if !ok {
			return nil, errors.New(fmt.Sprintf("key %s is not an Object", strings.Join(keyPath, ".")))
		}
	}
	return child, nil
}

func origin(t *Tree, outputDir string) error {
	if err := validateTree(t); err != nil {
		return err
	}

	tmp, err := os.MkdirTemp(filepath.Dir(outputDir), ".tmp")
	if err != nil {
		return errors.Wrap(err, "Error making temp dir for output")
	}

	for _, vConfig := range t.VersionedConfigs {
		version := vConfig.Meta.Version
		meta, err := overlayMeta([]*Meta{t.GlobalMeta, vConfig.Meta})
		if err != nil {
			return err
		}
		l := &layer{
			Meta:             meta,
			isDefault:        globalVersion(t) == version,
			ComponentConfigs: &vConfig.Configs,
			Version:          version,
			priority:         cloudv1.PriorityBase,
			PullRequestID:    genPullRequestID(),
			PullRequestLink:  genPullRequestLink(),
		}
		if l.isDefault {
			l.labelSelector = &v1.LabelSelector{
				MatchLabels: map[string]string{
					cloudv1.DefaultVersionLabel: "true",
				},
			}
		} else {
			l.labelSelector = &v1.LabelSelector{
				MatchLabels: map[string]string{
					cloudv1.VersionLabel: version,
				},
			}
		}
		if err := l.renderAndWrite(tmp); err != nil {
			return err
		}

		for _, instanceOverlay := range vConfig.InstanceOverlays {
			l := &layer{
				Meta:             meta,
				isDefault:        globalVersion(t) == version,
				ComponentConfigs: &instanceOverlay.Configs,
				Version:          version,
				priority:         cloudv1.PriorityInstanceType,
				instanceType:     instanceOverlay.InstanceType,
				PullRequestID:    genPullRequestID(),
				PullRequestLink:  genPullRequestLink(),
			}
			l.labelSelector = &v1.LabelSelector{
				MatchLabels: map[string]string{
					cloudv1.VersionLabel:      version,
					cloudv1.InstanceTypeLabel: instanceOverlay.InstanceType,
				},
			}
			if err := l.renderAndWrite(tmp); err != nil {
				return err
			}
		}

		for _, tOverlay := range t.TenantOverlays {
			if tenantVersion(t, tOverlay) == version {
				// Tenant's Cluster-config
				l := layer{
					Tenant:           &tOverlay.TenantID,
					Meta:             tOverlay.Meta,
					isDefault:        globalVersion(t) == version,
					ComponentConfigs: &tOverlay.Configs,
					Version:          version,
					priority:         cloudv1.PriorityTenant,
					PullRequestID:    genPullRequestID(),
					PullRequestLink:  genPullRequestLink(),
				}
				l.labelSelector = &v1.LabelSelector{
					MatchLabels: map[string]string{
						cloudv1.TenantLabel: tOverlay.TenantID,
					},
				}
				if err := l.renderAndWrite(tmp); err != nil {
					return err
				}
				// ignore error
				_ = l.sendClinicEvent()
			}

			for _, cOverlay := range tOverlay.ClusterOverlays {
				if clusterVersion(t, tOverlay, cOverlay) == version {
					// Cluster's Cluster-config
					l := layer{
						Tenant:           &tOverlay.TenantID,
						Cluster:          &cOverlay.ClusterID,
						Meta:             cOverlay.Meta,
						isDefault:        globalVersion(t) == version,
						ComponentConfigs: &cOverlay.Configs,
						Version:          version,
						priority:         cloudv1.PriorityCluster,
						PullRequestID:    genPullRequestID(),
						PullRequestLink:  genPullRequestLink(),
					}
					l.labelSelector = &v1.LabelSelector{
						MatchLabels: map[string]string{
							cloudv1.TenantLabel:  tOverlay.TenantID,
							cloudv1.ClusterLabel: cOverlay.ClusterID,
						},
					}
					if err := l.renderAndWrite(tmp); err != nil {
						return err
					}
					// ignore error
					err = l.sendClinicEvent()
					if err != nil {
						log.Warn("send clinic event failed", zap.Error(err), zap.String("cluster", cOverlay.ClusterID))
					}
				}
			}
		}
	}
	if err := os.RemoveAll(outputDir); err != nil {
		return err
	}
	return os.Rename(tmp, outputDir)
}

type layer struct {
	Tenant  *string
	Cluster *string
	// TODO: deprecated, remove in future
	InstanceOverlays []InstanceOverlay
	Meta             *Meta
	ComponentConfigs *ComponentConfigs
	isDefault        bool
	Version          string
	priority         cloudv1.Priority
	instanceType     string
	labelSelector    *v1.LabelSelector
	PullRequestID    int
	PullRequestLink  string
}

func (l *layer) imageEmpty() bool {
	if l.Meta != nil {
		if l.Meta.Images != nil {
			return false
		}
	}
	return true
}

func (l *layer) constructTiDBConfiguration() *v1alpha1.Configuration {
	conf := new(v1alpha1.Configuration)
	if !l.imageEmpty() {
		if l.Meta.Images.TiDB != nil {
			if l.Meta.Images.TiDB.BaseImage != nil {
				conf.BaseImage = *l.Meta.Images.TiDB.BaseImage
			}
			if l.Meta.Images.TiDB.Tag != nil {
				conf.Tag = l.Meta.Images.TiDB.Tag
			}
		}
	}
	conf.Config = l.ComponentConfigs.GetConfig(ComponentTiDB)
	conf.Overlays = computeComponentInstanceOverlays(l.InstanceOverlays, ComponentTiDB)
	return conf
}
func (l *layer) constructTiProxyConfiguration() *v1alpha1.Configuration {
	if !l.imageEmpty() {
		if l.Meta.Images.TiProxy != nil {
			conf := new(v1alpha1.Configuration)
			if l.Meta.Images.TiProxy.BaseImage != nil {
				conf.BaseImage = *l.Meta.Images.TiProxy.BaseImage
			}
			if l.Meta.Images.TiProxy.Tag != nil {
				conf.Tag = l.Meta.Images.TiProxy.Tag
			}
			conf.Config = l.ComponentConfigs.GetConfig(ComponentTiProxy)
			conf.Overlays = computeComponentInstanceOverlays(l.InstanceOverlays, ComponentTiProxy)
			return conf
		}
	}
	return nil
}
func (l *layer) constructTiKVConfiguration() *v1alpha1.Configuration {
	conf := new(v1alpha1.Configuration)
	if !l.imageEmpty() {
		if l.Meta.Images.TiKV != nil {
			if l.Meta.Images.TiKV.BaseImage != nil {
				conf.BaseImage = *l.Meta.Images.TiKV.BaseImage
			}
			if l.Meta.Images.TiKV.Tag != nil {
				conf.Tag = l.Meta.Images.TiKV.Tag
			}
		}
	}
	conf.Config = l.ComponentConfigs.GetConfig(ComponentTiKV)
	conf.Overlays = computeComponentInstanceOverlays(l.InstanceOverlays, ComponentTiKV)
	return conf
}
func (l *layer) constructPDConfiguration() *v1alpha1.Configuration {
	conf := new(v1alpha1.Configuration)
	if !l.imageEmpty() {
		if l.Meta.Images.PD != nil {
			if l.Meta.Images.PD.BaseImage != nil {
				conf.BaseImage = *l.Meta.Images.PD.BaseImage
			}
			if l.Meta.Images.PD.Tag != nil {
				conf.Tag = l.Meta.Images.PD.Tag
			}
		}
	}
	conf.Config = l.ComponentConfigs.GetConfig(ComponentPD)
	conf.Overlays = computeComponentInstanceOverlays(l.InstanceOverlays, ComponentPD)
	return conf
}
func (l *layer) constructTiFlashConfiguration() *v1alpha1.Configuration {
	conf := new(v1alpha1.Configuration)
	if !l.imageEmpty() {
		if l.Meta.Images.TiFlash != nil {
			if l.Meta.Images.TiFlash.BaseImage != nil {
				conf.BaseImage = *l.Meta.Images.TiFlash.BaseImage
			}
			if l.Meta.Images.TiFlash.Tag != nil {
				conf.Tag = l.Meta.Images.TiFlash.Tag
			}
		}
	}
	conf.Config = l.ComponentConfigs.GetConfig(ComponentTiFlash)
	conf.Overlays = computeComponentInstanceOverlays(l.InstanceOverlays, ComponentTiFlash)
	return conf
}
func (l *layer) constructTiFlashLearnerConfiguration() *cloudv1.Configuration {
	conf := new(cloudv1.Configuration)
	if !l.imageEmpty() {
		if l.Meta.Images.TiFlash != nil {
			if l.Meta.Images.TiFlash.BaseImage != nil {
				conf.BaseImage = l.Meta.Images.TiFlash.BaseImage
			}
			if l.Meta.Images.TiFlash.Tag != nil {
				conf.Tag = l.Meta.Images.TiFlash.Tag
			}
		}
	}
	conf.Config = (*cloudv1.TomlConfig)(l.ComponentConfigs.GetConfig(ComponentTiFlashLearner))
	return conf
}
func (l *layer) constructBackupConfiguration() *v1alpha1.Configuration {
	conf := new(v1alpha1.Configuration)
	if !l.imageEmpty() {
		if l.Meta.Images.Backup != nil {
			if l.Meta.Images.Backup.BaseImage != nil {
				conf.BaseImage = *l.Meta.Images.Backup.BaseImage
			}
			if l.Meta.Images.Backup.Tag != nil {
				conf.Tag = l.Meta.Images.Backup.Tag
			}
		}
	}
	conf.Config = l.ComponentConfigs.GetConfig(ComponentBackup)
	return conf
}
func (l *layer) constructRestoreConfiguration() *v1alpha1.Configuration {
	conf := new(v1alpha1.Configuration)
	if !l.imageEmpty() {
		if l.Meta.Images.Restore != nil {
			if l.Meta.Images.Restore.BaseImage != nil {
				conf.BaseImage = *l.Meta.Images.Restore.BaseImage
			}
			if l.Meta.Images.Restore.Tag != nil {
				conf.Tag = l.Meta.Images.Restore.Tag
			}
		}
	}
	conf.Config = l.ComponentConfigs.GetConfig(ComponentRestore)
	return conf
}
func (l *layer) constructLightningConfiguration() *cloudv1.Configuration {
	conf := new(cloudv1.Configuration)
	if !l.imageEmpty() {
		if l.Meta.Images.Lightning != nil {
			if l.Meta.Images.Lightning.BaseImage != nil {
				conf.BaseImage = l.Meta.Images.Lightning.BaseImage
			}
			if l.Meta.Images.Lightning.Tag != nil {
				conf.Tag = l.Meta.Images.Lightning.Tag
			}
		}
	}
	conf.Config = (*cloudv1.TomlConfig)(l.ComponentConfigs.GetConfig(ComponentLightning))
	return conf
}
func (l *layer) constructTiCDCConfiguration() *cloudv1.Configuration {
	conf := new(cloudv1.Configuration)
	if !l.imageEmpty() {
		if l.Meta.Images.TiCDC != nil {
			if l.Meta.Images.TiCDC.BaseImage != nil {
				conf.BaseImage = l.Meta.Images.TiCDC.BaseImage
			}
			if l.Meta.Images.TiCDC.Tag != nil {
				conf.Tag = l.Meta.Images.TiCDC.Tag
			}
		}
	}
	conf.Config = (*cloudv1.TomlConfig)(l.ComponentConfigs.GetConfig(ComponentTiCDC))
	return conf
}

func (l *layer) render() (*cloudv1.ClusterConfig, error) {
	cc := &cloudv1.ClusterConfig{
		TypeMeta: v1.TypeMeta{
			APIVersion: cloudv1.GroupVersion.String(),
			Kind:       "ClusterConfig",
		},
		ObjectMeta: v1.ObjectMeta{
			Name:   l.toName(),
			Labels: l.toLabels(),
		},
		Spec: cloudv1.ClusterConfigSpec{
			Selector:       l.labelSelector,
			TiDB:           convertV1alpha1ConfigurationToV1(l.constructTiDBConfiguration()),
			TiKV:           convertV1alpha1ConfigurationToV1(l.constructTiKVConfiguration()),
			TiProxy:        convertV1alpha1ConfigurationToV1(l.constructTiProxyConfiguration()),
			PD:             convertV1alpha1ConfigurationToV1(l.constructPDConfiguration()),
			TiFlash:        convertV1alpha1ConfigurationToV1(l.constructTiFlashConfiguration()),
			TiFlashLearner: l.constructTiFlashLearnerConfiguration(),
			Backup:         convertV1alpha1ConfigurationToV1(l.constructBackupConfiguration()),
			Restore:        convertV1alpha1ConfigurationToV1(l.constructRestoreConfiguration()),
			Lightning:      l.constructLightningConfiguration(),
			TiCDC:          l.constructTiCDCConfiguration(),
			Priority:       l.priority,
		},
	}

	if l.Meta != nil {
		cc.Spec.Version = l.Meta.Version
		cc.Spec.UpdatePolicy = cloudv1.UpdatePolicy(l.Meta.UpdatePolicy)
	}

	cc.Labels["tidbcloud.com/managed-by"] = "cicd"
	cc.Labels["tidbcloud.com/module"] = "tidb-configuration"

	if meta {
		cc.Spec.ConfigMeta = &cloudv1.ConfigMeta{
			GitCommitID:   gitCommit,
			ConfigVersion: version,
		}
	}
	// add the following fields into cc.Labels is dangerous and may not be used, so we add them into cc.Annotations
	if cc.Annotations == nil {
		cc.Annotations = map[string]string{}
	}
	cc.Annotations["tidbcloud.com/pr-number"] = gitPRNum
	cc.Annotations["tidbcloud.com/pr-created-by"] = gitPRCreatedBy
	return cc, nil
}

func (l layer) toName() string {
	var sb strings.Builder
	sb.WriteString("base")
	if !l.isDefault && l.Meta != nil && l.Meta.Version != "" {
		sb.WriteString(fmt.Sprintf("--%s", l.Meta.Version))
	}
	if l.instanceType != "" {
		sb.WriteString(fmt.Sprintf("--%s", strings.ReplaceAll(l.instanceType, ".", "")))
	}
	if l.Tenant != nil {
		sb.WriteString(fmt.Sprintf("--t%s", *l.Tenant))
	}
	if l.Cluster != nil {
		sb.WriteString(fmt.Sprintf("--c%s", *l.Cluster))
	}
	return sb.String()
}

func (l layer) toLabels() map[string]string {
	m := map[string]string{}
	if l.Tenant != nil {
		m[cloudv1.TenantLabel] = *l.Tenant
	} else {
		m[cloudv1.TenantLabel] = "all"
	}
	if l.Cluster != nil {
		m[cloudv1.ClusterLabel] = *l.Cluster
	} else {
		m[cloudv1.ClusterLabel] = "all"
	}

	if l.Tenant == nil && l.Cluster == nil {
		m[cloudv1.VersionLabel] = l.Version
	}
	return m
}

func (l layer) genClinicEventID() string {
	contextID := "gitops"

	if l.Tenant != nil {
		contextID = fmt.Sprintf("%s-%s", contextID, *l.Tenant)
	} else {
		contextID = fmt.Sprintf("%s-%s", contextID, "0")
	}

	if l.Cluster != nil {
		contextID = fmt.Sprintf("%s-%s", contextID, *l.Cluster)
	} else {
		contextID = fmt.Sprintf("%s-%s", contextID, "0")
	}

	// Divide every 20 minutes into a time period, with the same event context id in the same time period
	nowTs := time.Now().Unix()

	// gitops v1
	return fmt.Sprintf("%s-%d-v2", contextID, nowTs-nowTs%(20*60))
}

func (l layer) sendClinicEvent() error {
	if !activityHub {
		return nil
	}

	if !filter.filterLayter(l) {
		return nil
	}

	eventRequest := clinic_event.EventRequest{
		Catalog:       fmt.Sprintf("%s-v2", clinic_event.GitopsCatalog),
		SeverityLevel: int(clinic_event.Debug),
		Payload:       make(map[string]interface{}),
		ContextID:     l.genClinicEventID(),
	}

	if l.Tenant != nil {
		eventRequest.ApplicationID = *l.Tenant
	} else {
		eventRequest.ApplicationID = "0"
	}

	if l.Cluster != nil {
		eventRequest.TargetID = *l.Cluster
	} else {
		eventRequest.TargetID = "0"
	}

	// gen payload
	payloadByte, err := json.Marshal(l)
	if err != nil {
		return err
	}
	payloadDecoder := json.NewDecoder(bytes.NewBuffer(payloadByte))
	payloadDecoder.UseNumber()
	_ = payloadDecoder.Decode(&eventRequest.Payload)

	eventRequest.Name = fmt.Sprintf("%s-v2:origin", clinic_event.GitopsCatalog)
	eventRequest.CalibrationTime = uint64(time.Now().Unix())
	eventRequest.Payload["summary"] = gitCommitMsg

	return clinic_event.SendEvent(eventRequest)
}

func (l *layer) renderAndWrite(dir string) error {
	cc, err := l.render()
	if err != nil {
		return err
	}
	b, err := yaml.Marshal(cc)
	if err != nil {
		return err
	}
	return os.WriteFile(filepath.Join(dir, fmt.Sprintf("%s.yaml", cc.Name)), b, 0644)
}

func convertV1alpha1ConfigurationToV1(configuration *v1alpha1.Configuration) *cloudv1.Configuration {
	if configuration != nil {
		newcfg := new(cloudv1.Configuration)
		newcfg.Tag = configuration.Tag
		if configuration.BaseImage != "" {
			newcfg.BaseImage = &configuration.BaseImage
		}
		if configuration.Config != nil {
			newcfg.Config = cloudv1.NewTomlConfig(configuration.Config.MP)
		}
		return newcfg
	}
	return nil
}
