package service

import (
	"context"
	"strconv"
	"strings"

	pb "github.com/tidbcloud/config-center/proto/config_center"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"
	"google.golang.org/protobuf/types/known/structpb"
)

// special config type "CC_META_PROFILE" to store images info
type MetaProfileConfig struct {
	//ClusterVersion        string            `toml:"cluster_version" json:"cluster_version"`
	ComponentTags         map[string]string `toml:"component_tags" json:"component_tags"`
	ComponentVersions     map[string]string `toml:"component_versions" json:"component_versions"`
	ConfigCustomTemplates map[string]int64  `toml:"config_custom_templates" json:"config_custom_templates"`
}

const (
	metaProfileComponentTagPrefix     = "component_tags."
	metaProfileComponentVersionPrefix = "component_versions."
	metaProfileCustomTemplatePrefix   = "config_custom_templates."
	legacyMetaProfileComponentVersion = "v8.5.4"
	defaultMetaProfileListPageSize    = int32(50)
	maxMetaProfileListPageSize        = int32(500)
)

func (c *ConfigCenterService) CreateMetaProfile(ctx context.Context, req *pb.CreateMetaProfileRequest) (*pb.MetaProfile, error) {
	templateReq := &pb.CreateConfigurationTemplateRequest{
		ClusterType:       req.ClusterType,
		ConfigurationType: pb.CONFIG_CENTER_INTERNAL_META_PROFILE,
		TemplateName:      req.GetProfileName(),
		Tags:              req.GetTags(),
		Data:              map[string]*pb.ParamItem{},
	}

	templateReq.Data["description"] = &pb.ParamItem{
		Value: structpb.NewStringValue(req.Description),
	}

	componentVersions := req.GetComponentVersions()
	for component, version := range componentVersions {
		if version == "" {
			return nil, status.Errorf(codes.InvalidArgument, "component version is required for component %s", component)
		}
	}
	for component := range req.GetComponentImageTags() {
		if componentVersions[component] == "" {
			return nil, status.Errorf(codes.InvalidArgument, "component version is required when image tag is set for component %s", component)
		}
	}

	// convert component versions, tags and sub templates to Data field
	for component, version := range componentVersions {
		templateReq.Data[metaProfileComponentVersionPrefix+component] = &pb.ParamItem{
			Value: structpb.NewStringValue(version),
		}
	}
	for component, tag := range req.GetComponentImageTags() {
		templateReq.Data[metaProfileComponentTagPrefix+component] = &pb.ParamItem{
			Value: structpb.NewStringValue(tag),
		}
	}
	for configType, subID := range req.GetConfigCustomTemplates() {
		templateReq.Data[metaProfileCustomTemplatePrefix+configType] = &pb.ParamItem{
			Value: structpb.NewNumberValue(float64(subID)),
		}
	}

	// region sync will be handled inside CreateTemplate
	template, err := c.CreateConfigurationTemplate(templateReq)
	if err != nil {
		return nil, err
	}
	return template2MetaProfile(template), nil
}

func (c *ConfigCenterService) GetMetaProfile(ctx context.Context, req *pb.GetMetaProfileRequest) (*pb.MetaProfile, error) {
	tx, err := c.DB.Begin()
	if err != nil {
		return nil, err
	}
	defer tx.Rollback()

	mainTemplate, err := GetConfigurationTemplate(tx, &pb.GetConfigurationTemplateRequest{
		ClusterType: req.ClusterType,
		TemplateId:  req.ProfileId,
	})
	if err != nil {
		return nil, err
	}
	if mainTemplate.GetConfigurationType() != pb.CONFIG_CENTER_INTERNAL_META_PROFILE {
		return nil, status.Error(codes.InvalidArgument, "the requested id is not a meta profile")
	}

	return template2MetaProfile(mainTemplate), tx.Commit()
}

func (c *ConfigCenterService) ListMetaProfiles(ctx context.Context, req *pb.ListMetaProfilesRequest) (*pb.ListMetaProfilesResponse, error) {
	tx, err := c.DB.Begin()
	if err != nil {
		return nil, err
	}
	defer tx.Rollback()

	tags := make(map[string]string, len(req.GetTags())+1)
	for k, v := range req.GetTags() {
		tags[k] = v
	}

	if req.TenantId != nil {
		tags[pb.TEMPLATE_TAG_OWNER_TENANT_ID] = strconv.FormatUint(*req.TenantId, 10)
	}

	pageSize := req.GetPageSize()
	if pageSize <= 0 {
		pageSize = defaultMetaProfileListPageSize
	}
	if pageSize > maxMetaProfileListPageSize {
		pageSize = maxMetaProfileListPageSize
	}
	page := req.GetPage()
	if page <= 0 {
		page = 1
	}

	total, err := tx.CountTemplateRows(req.GetClusterType().String(), pb.CONFIG_CENTER_INTERNAL_META_PROFILE, tags, req.GetProfileName())
	if err != nil {
		return nil, err
	}
	templateRows, err := tx.ListTemplateRows(req.GetClusterType().String(), pb.CONFIG_CENTER_INTERNAL_META_PROFILE, tags, req.GetProfileName(), pageSize, page)
	if err != nil {
		return nil, err
	}

	resp := &pb.ListMetaProfilesResponse{
		Profiles: make([]*pb.MetaProfile, 0),
		Total:    total,
	}
	for _, row := range templateRows {
		template, err := GetConfigurationTemplate(tx, &pb.GetConfigurationTemplateRequest{
			ClusterType:       req.ClusterType,
			ConfigurationType: pb.CONFIG_CENTER_INTERNAL_META_PROFILE,
			TemplateId:        row.ID,
		})
		if err != nil {
			return nil, err
		}
		resp.Profiles = append(resp.Profiles, template2MetaProfile(template))
	}
	return resp, tx.Commit()
}

func (c *ConfigCenterService) DeleteMetaProfile(ctx context.Context, req *pb.DeleteMetaProfileRequest) error {
	tx, err := c.DB.Begin()
	if err != nil {
		return err
	}
	defer tx.Rollback()

	// verify that the requested id is a meta profile
	mainTemplate, err := GetConfigurationTemplate(tx, &pb.GetConfigurationTemplateRequest{
		ClusterType: req.ClusterType,
		TemplateId:  req.ProfileId,
	})
	if err != nil {
		return err
	}
	if mainTemplate.GetConfigurationType() != pb.CONFIG_CENTER_INTERNAL_META_PROFILE {
		return status.Error(codes.InvalidArgument, "the requested id is not a meta profile")
	}

	_, err = DeleteConfigurationTemplate(tx, &pb.DeleteConfigurationTemplateRequest{
		ClusterType:       req.ClusterType,
		ConfigurationType: pb.CONFIG_CENTER_INTERNAL_META_PROFILE,
		TemplateId:        req.ProfileId,
	})
	if err != nil {
		return err
	}

	return tx.Commit()
}

// convert without validation
func template2MetaProfile(template *pb.ConfigurationTemplate) *pb.MetaProfile {
	tagMap := make(map[string]string)
	versionMap := make(map[string]string)
	subTemplateID := make(map[string]int64)
	for k, v := range template.GetData() {
		if strings.HasPrefix(k, metaProfileComponentTagPrefix) {
			tagMap[k[len(metaProfileComponentTagPrefix):]] = v.Value.GetStringValue()
		} else if strings.HasPrefix(k, metaProfileComponentVersionPrefix) {
			versionMap[k[len(metaProfileComponentVersionPrefix):]] = v.Value.GetStringValue()
		} else if strings.HasPrefix(k, metaProfileCustomTemplatePrefix) {
			subTemplateID[k[len(metaProfileCustomTemplatePrefix):]] = int64(v.Value.GetNumberValue())
		}
	}
	for component := range tagMap {
		if versionMap[component] == "" {
			versionMap[component] = legacyMetaProfileComponentVersion
		}
	}
	return &pb.MetaProfile{
		ClusterType:           template.GetClusterType(),
		ProfileId:             template.GetTemplateId(),
		ProfileName:           template.GetTemplateName(),
		Description:           template.GetData()["description"].GetValue().GetStringValue(),
		Tags:                  template.Tags,
		ComponentImageTags:    tagMap,
		ConfigCustomTemplates: subTemplateID,
		ComponentVersions:     versionMap,
	}
}
