package service

import (
	"sort"
	"strings"

	"github.com/tidbcloud/config-center/internal/db"
	"github.com/tidbcloud/config-center/pkg/utils"
	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/timestamppb"
)

func GetConfigurationDictItem(d db.DB, req *pb.GetConfigurationDictItemRequest) (*pb.ConfigurationDictItem, error) {
	version := req.Version
	if req.GetMatchPartialVersion() {
		v, err := MatchPartialVersionFromDict(d, req.GetClusterType().String(), req.GetConfigurationType(), version)
		if err != nil {
			return nil, err
		}
		version = v
	}
	item, err := d.GetConfigDict(req.ClusterType.DictSource().String(), req.ConfigurationType, req.ParamKey, version)
	if err == db.ErrNotFound {
		return nil, status.Errorf(codes.NotFound, "ConfigurationDict item %s not found", req.ParamKey)
	} else if err != nil {
		return nil, status.Errorf(codes.Internal, "failed to get ConfigurationDict item %s: %v", req.ParamKey, err)
	}
	itemPb, err := Row2Pb(item)
	if err != nil {
		return nil, err
	}
	itemPb.ClusterType = req.ClusterType
	return itemPb, nil
}

func Row2Pb(item *db.ConfigDictRow) (*pb.ConfigurationDictItem, error) {
	v, err := pb.NewValue(item.DefaultValue.Raw)
	if err != nil {
		return nil, err
	}
	result := &pb.ConfigurationDictItem{
		ClusterType:       pb.ClusterType(pb.ClusterType_value[item.ClusterType]),
		ConfigurationType: item.ConfigType,
		Version:           item.Version,
		ParamKey:          item.ParamKey,
		DefaultValue:      v,
		Type:              pb.ConfigParamType(pb.ConfigParamType_value[item.Type]),
		HotReloadable:     item.HotReloadable,
		RiskLevel:         item.RiskLevel,
		Description:       item.Description,
		NeedTemplate:      item.NeedTemplate,
		Validation:        utils.EmptyIfNil(item.Validation),
		CreateTime:        timestamppb.New(item.CreateTime),
		UpdateTime:        timestamppb.New(item.UpdateTime),
	}
	if item.DeleteTime != nil {
		result.DeleteTime = timestamppb.New(*item.DeleteTime)
	}
	return result, nil
}

func ListConfigurationDictVersions(d db.DB, req *pb.ListConfigurationDictVersionsRequest) (*pb.ListConfigurationDictVersionsResponse, error) {
	versions, err := d.ListVersionsFromConfigDict(req.GetClusterType().DictSource().String(), req.GetConfigType())
	if err != nil {
		return nil, status.Error(codes.Internal, err.Error())
	}
	return &pb.ListConfigurationDictVersionsResponse{Versions: versions}, nil
}

func MatchPartialVersionFromDict(d db.DB, clusterType, configType, version string) (string, error) {
	versionsResp, err := ListConfigurationDictVersions(d, &pb.ListConfigurationDictVersionsRequest{
		ClusterType: pb.ClusterType(pb.ClusterType_value[clusterType]),
		ConfigType:  configType,
	})
	if err != nil {
		return "", status.Error(codes.Internal, err.Error())
	}

	versions := versionsResp.Versions
	sort.Slice(versions, func(i, j int) bool { return len(versions[i]) > len(versions[j]) })

	for _, v := range versions {
		if strings.HasPrefix(version, v) {
			return v, nil
		}
	}

	return "", status.Error(codes.NotFound, "cannot find version "+version)
}
