package util

import (
	"regexp"
	"runtime/debug"
	"strings"

	cosmos_proto "github.com/cosmos/cosmos-proto"
	"google.golang.org/protobuf/proto"
	"google.golang.org/protobuf/reflect/protoreflect"
	"google.golang.org/protobuf/reflect/protoregistry"
	"google.golang.org/protobuf/types/dynamicpb"

	"cosmossdk.io/client/v2/internal/strcase"
)

// get build info to verify later if comment is supported
// this is a hack in because of the global api module package
// later versions unsupported by the current version can be added
var buildInfo, _ = debug.ReadBuildInfo()

// DescriptorName returns the name of the descriptor in kebab case.
func DescriptorKebabName(descriptor protoreflect.Descriptor) string {
	return strcase.ToKebab(string(descriptor.Name()))
}

func ResolveMessageType(resolver protoregistry.MessageTypeResolver, descriptor protoreflect.MessageDescriptor) protoreflect.MessageType {
	typ, err := resolver.FindMessageByName(descriptor.FullName())
	if err == nil {
		return typ
	}

	return dynamicpb.NewMessageType(descriptor)
}

// IsSupportedVersion is used to determine in which version of a module / sdk a rpc was introduced.
// It returns false if the rpc has comment for an higher version than the current one.
// It returns true if the method descriptor contains no annotation.
func IsSupportedVersion(methodDesc protoreflect.MethodDescriptor) bool {
	return isSupportedVersion(methodDesc, buildInfo)
}

// isSupportedVersion is used to determine in which version of a module / sdk a rpc was introduced.
// It returns false if the rpc has comment for an higher version than the current one.
// It returns true if the method descriptor contains no annotation.
// It takes a buildInfo as argument to be able to test it.
func isSupportedVersion(methodDesc protoreflect.MethodDescriptor, buildInfo *debug.BuildInfo) bool {
	hasVersion := proto.HasExtension(methodDesc.Options(), cosmos_proto.E_MethodAddedIn)
	if !hasVersion || buildInfo == nil || len(buildInfo.Deps) == 0 {
		return true
	}

	version := proto.GetExtension(methodDesc.Options(), cosmos_proto.E_MethodAddedIn).(string)
	moduleName, version := parseVersion(version)
	if moduleName == "" || version == "" {
		return true // if no comment consider it's supported
	}

	for _, dep := range buildInfo.Deps {
		if !strings.Contains(dep.Path, moduleName) {
			continue
		}

		return version <= dep.Version
	}

	// if cannot find the module consider it isn't supported
	// for instance the x/gov module wasn't extracted in v0.50
	// so it isn't present in the build info, however, that means
	// it isn't supported in v0.50.
	return false
}

var sinceCommentRegex = regexp.MustCompile(`(\S+) (\S+)`)

// parseVersion parses the `cosmos-sdk v0.xx` comment on rpc.
func parseVersion(input string) (string, string) {
	var (
		moduleName string
		version    string
	)

	input = strings.ToLower(input)
	input = strings.ReplaceAll(input, "cosmos sdk", "cosmos-sdk")

	matches := sinceCommentRegex.FindStringSubmatch(input)
	if len(matches) >= 3 {
		moduleName, version = strings.TrimPrefix(matches[1], "x/"), matches[2]

		if !strings.HasPrefix(version, "v") {
			version = "v" + version
		}
	}

	return moduleName, version
}
