package enum

import (
	"errors"
	"fmt"
	"strings"

	"google.golang.org/protobuf/proto"
	"google.golang.org/protobuf/reflect/protoreflect"

	datatypev1 "github.com/tidbcloud/tidb-management-service/api/spec/datatype/v1"
	globalcommonv1 "github.com/tidbcloud/tidb-management-service/api/spec/global/common/v1"
)

type enumStringer interface {
	protoreflect.Enum
	fmt.Stringer
}

// Deprecated: Use `.String()` instead.
func ToString[E enumStringer](e E) string {
	valueDesc := e.Descriptor().Values().ByNumber(e.Number())

	if !proto.HasExtension(valueDesc.Options(), datatypev1.E_Name) {
		return e.String()
	}

	ext := proto.GetExtension(valueDesc.Options(), datatypev1.E_Name)

	s, ok := ext.(string)
	if !ok {
		return e.String()
	}

	if s == "" {
		return e.String()
	}

	return s
}

// FromString unmarshalls enum string with customized `[(name) = "xxx"]` extension.
func FromString[E enumStringer](v string) (int32, error) {
	if v == "" {
		return 0, nil
	}

	var e E
	enumType := e.Descriptor()

	var (
		fallbackNum     int32
		isFallbackFound bool
	)

	// Iterate over all enum values.
	for i := 0; i < enumType.Values().Len(); i++ {
		valueDesc := enumType.Values().Get(i)
		var name string
		if proto.HasExtension(valueDesc.Options(), datatypev1.E_Name) {
			ext := proto.GetExtension(valueDesc.Options(), datatypev1.E_Name)
			if extString, ok := ext.(string); ok && extString != "" {
				name = extString
			}
		}

		// Found the customized value, fast return.
		if name != "" && strings.EqualFold(name, v) {
			return int32(valueDesc.Number()), nil
		}

		if strings.EqualFold(string(valueDesc.Name()), v) {
			fallbackNum = int32(valueDesc.Number())
			isFallbackFound = true
		}
	}

	if isFallbackFound {
		return fallbackNum, nil
	}

	return 0, errors.New("invalid enum value")
}

func RegionFromString(provider, region string) (globalcommonv1.Region_Name, error) {
	if provider == "" || region == "" {
		return globalcommonv1.Region_NAME_UNSPECIFIED, nil
	}

	enumNameStr := strings.ReplaceAll(strings.ToUpper(fmt.Sprintf("%s_%s", provider, region)), "-", "_")
	enumValue, ok := globalcommonv1.Region_Name_value[enumNameStr]

	if !ok {
		return globalcommonv1.Region_NAME_UNSPECIFIED, fmt.Errorf("invalid region enum value: %s", enumNameStr)
	}

	return globalcommonv1.Region_Name(enumValue), nil
}
