package mapping

import (
	"encoding/json"
	"errors"
	"fmt"
	"math"
	"reflect"
	"strconv"
	"strings"
	"sync"

	"github.com/tal-tech/go-zero/core/stringx"
)

const (
	defaultOption   = "default"
	stringOption    = "string"
	optionalOption  = "optional"
	optionsOption   = "options"
	rangeOption     = "range"
	optionSeparator = "|"
	equalToken      = "="
)

var (
	errUnsupportedType  = errors.New("unsupported type on setting field value")
	errNumberRange      = errors.New("wrong number range setting")
	optionsCache        = make(map[string]optionsCacheValue)
	cacheLock           sync.RWMutex
	structRequiredCache = make(map[reflect.Type]requiredCacheValue)
	structCacheLock     sync.RWMutex
)

type (
	optionsCacheValue struct {
		key     string
		options *fieldOptions
		err     error
	}

	requiredCacheValue struct {
		required bool
		err      error
	}
)

func Deref(t reflect.Type) reflect.Type {
	if t.Kind() == reflect.Ptr {
		t = t.Elem()
	}

	return t
}

func Repr(v interface{}) string {
	if v == nil {
		return ""
	}

	// if func (v *Type) String() string, we can't use Elem()
	switch vt := v.(type) {
	case fmt.Stringer:
		return vt.String()
	}

	val := reflect.ValueOf(v)
	if val.Kind() == reflect.Ptr && !val.IsNil() {
		val = val.Elem()
	}

	switch vt := val.Interface().(type) {
	case bool:
		return strconv.FormatBool(vt)
	case error:
		return vt.Error()
	case float32:
		return strconv.FormatFloat(float64(vt), 'f', -1, 32)
	case float64:
		return strconv.FormatFloat(vt, 'f', -1, 64)
	case fmt.Stringer:
		return vt.String()
	case int:
		return strconv.Itoa(vt)
	case int8:
		return strconv.Itoa(int(vt))
	case int16:
		return strconv.Itoa(int(vt))
	case int32:
		return strconv.Itoa(int(vt))
	case int64:
		return strconv.FormatInt(vt, 10)
	case string:
		return vt
	case uint:
		return strconv.FormatUint(uint64(vt), 10)
	case uint8:
		return strconv.FormatUint(uint64(vt), 10)
	case uint16:
		return strconv.FormatUint(uint64(vt), 10)
	case uint32:
		return strconv.FormatUint(uint64(vt), 10)
	case uint64:
		return strconv.FormatUint(vt, 10)
	case []byte:
		return string(vt)
	default:
		return fmt.Sprint(val.Interface())
	}
}

func ValidatePtr(v *reflect.Value) error {
	// sequence is very important, IsNil must be called after checking Kind() with reflect.Ptr,
	// panic otherwise
	if !v.IsValid() || v.Kind() != reflect.Ptr || v.IsNil() {
		return fmt.Errorf("not a valid pointer: %v", v)
	}

	return nil
}

func convertType(kind reflect.Kind, str string) (interface{}, error) {
	switch kind {
	case reflect.Bool:
		return str == "1" || strings.ToLower(str) == "true", nil
	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
		if intValue, err := strconv.ParseInt(str, 10, 64); err != nil {
			return 0, fmt.Errorf("the value %q cannot parsed as int", str)
		} else {
			return intValue, nil
		}
	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
		if uintValue, err := strconv.ParseUint(str, 10, 64); err != nil {
			return 0, fmt.Errorf("the value %q cannot parsed as uint", str)
		} else {
			return uintValue, nil
		}
	case reflect.Float32, reflect.Float64:
		if floatValue, err := strconv.ParseFloat(str, 64); err != nil {
			return 0, fmt.Errorf("the value %q cannot parsed as float", str)
		} else {
			return floatValue, nil
		}
	case reflect.String:
		return str, nil
	default:
		return nil, errUnsupportedType
	}
}

func doParseKeyAndOptions(field reflect.StructField, value string) (string, *fieldOptions, error) {
	segments := strings.Split(value, ",")
	key := strings.TrimSpace(segments[0])
	options := segments[1:]

	if len(options) == 0 {
		return key, nil, nil
	}

	var fieldOpts fieldOptions
	for _, segment := range options {
		option := strings.TrimSpace(segment)
		switch {
		case option == stringOption:
			fieldOpts.FromString = true
		case strings.HasPrefix(option, optionalOption):
			segs := strings.Split(option, equalToken)
			switch len(segs) {
			case 1:
				fieldOpts.Optional = true
			case 2:
				fieldOpts.Optional = true
				fieldOpts.OptionalDep = segs[1]
			default:
				return "", nil, fmt.Errorf("field %s has wrong optional", field.Name)
			}
		case option == optionalOption:
			fieldOpts.Optional = true
		case strings.HasPrefix(option, optionsOption):
			segs := strings.Split(option, equalToken)
			if len(segs) != 2 {
				return "", nil, fmt.Errorf("field %s has wrong options", field.Name)
			} else {
				fieldOpts.Options = strings.Split(segs[1], optionSeparator)
			}
		case strings.HasPrefix(option, defaultOption):
			segs := strings.Split(option, equalToken)
			if len(segs) != 2 {
				return "", nil, fmt.Errorf("field %s has wrong default option", field.Name)
			} else {
				fieldOpts.Default = strings.TrimSpace(segs[1])
			}
		case strings.HasPrefix(option, rangeOption):
			segs := strings.Split(option, equalToken)
			if len(segs) != 2 {
				return "", nil, fmt.Errorf("field %s has wrong range", field.Name)
			}
			if nr, err := parseNumberRange(segs[1]); err != nil {
				return "", nil, err
			} else {
				fieldOpts.Range = nr
			}
		}
	}

	return key, &fieldOpts, nil
}

func implicitValueRequiredStruct(tag string, tp reflect.Type) (bool, error) {
	numFields := tp.NumField()
	for i := 0; i < numFields; i++ {
		childField := tp.Field(i)
		if usingDifferentKeys(tag, childField) {
			return true, nil
		}

		_, opts, err := parseKeyAndOptions(tag, childField)
		if err != nil {
			return false, err
		}

		if opts == nil {
			if childField.Type.Kind() != reflect.Struct {
				return true, nil
			}

			if required, err := implicitValueRequiredStruct(tag, childField.Type); err != nil {
				return false, err
			} else if required {
				return true, nil
			}
		} else if !opts.Optional && len(opts.Default) == 0 {
			return true, nil
		} else if len(opts.OptionalDep) > 0 && opts.OptionalDep[0] == notSymbol {
			return true, nil
		}
	}

	return false, nil
}

func maybeNewValue(field reflect.StructField, value reflect.Value) {
	if field.Type.Kind() == reflect.Ptr && value.IsNil() {
		value.Set(reflect.New(value.Type().Elem()))
	}
}

// don't modify returned fieldOptions, it's cached and shared among different calls.
func parseKeyAndOptions(tagName string, field reflect.StructField) (string, *fieldOptions, error) {
	value := field.Tag.Get(tagName)
	if len(value) == 0 {
		return field.Name, nil, nil
	}

	cacheLock.RLock()
	cache, ok := optionsCache[value]
	cacheLock.RUnlock()
	if ok {
		return stringx.TakeOne(cache.key, field.Name), cache.options, cache.err
	}

	key, options, err := doParseKeyAndOptions(field, value)
	cacheLock.Lock()
	optionsCache[value] = optionsCacheValue{
		key:     key,
		options: options,
		err:     err,
	}
	cacheLock.Unlock()

	return stringx.TakeOne(key, field.Name), options, err
}

// support below notations:
// [:5] (:5] [:5) (:5)
// [1:] [1:) (1:] (1:)
// [1:5] [1:5) (1:5] (1:5)
func parseNumberRange(str string) (*numberRange, error) {
	if len(str) == 0 {
		return nil, errNumberRange
	}

	var leftInclude bool
	switch str[0] {
	case '[':
		leftInclude = true
	case '(':
		leftInclude = false
	default:
		return nil, errNumberRange
	}

	str = str[1:]
	if len(str) == 0 {
		return nil, errNumberRange
	}

	var rightInclude bool
	switch str[len(str)-1] {
	case ']':
		rightInclude = true
	case ')':
		rightInclude = false
	default:
		return nil, errNumberRange
	}

	str = str[:len(str)-1]
	fields := strings.Split(str, ":")
	if len(fields) != 2 {
		return nil, errNumberRange
	}

	if len(fields[0]) == 0 && len(fields[1]) == 0 {
		return nil, errNumberRange
	}

	var left float64
	if len(fields[0]) > 0 {
		var err error
		if left, err = strconv.ParseFloat(fields[0], 64); err != nil {
			return nil, err
		}
	} else {
		left = -math.MaxFloat64
	}

	var right float64
	if len(fields[1]) > 0 {
		var err error
		if right, err = strconv.ParseFloat(fields[1], 64); err != nil {
			return nil, err
		}
	} else {
		right = math.MaxFloat64
	}

	return &numberRange{
		left:         left,
		leftInclude:  leftInclude,
		right:        right,
		rightInclude: rightInclude,
	}, nil
}

func setMatchedPrimitiveValue(kind reflect.Kind, value reflect.Value, v interface{}) error {
	switch kind {
	case reflect.Bool:
		value.SetBool(v.(bool))
	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
		value.SetInt(v.(int64))
	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
		value.SetUint(v.(uint64))
	case reflect.Float32, reflect.Float64:
		value.SetFloat(v.(float64))
	case reflect.String:
		value.SetString(v.(string))
	default:
		return errUnsupportedType
	}

	return nil
}

func setValue(kind reflect.Kind, value reflect.Value, str string) error {
	if !value.CanSet() {
		return errValueNotSettable
	}

	v, err := convertType(kind, str)
	if err != nil {
		return err
	}

	return setMatchedPrimitiveValue(kind, value, v)
}

func structValueRequired(tag string, tp reflect.Type) (bool, error) {
	structCacheLock.RLock()
	val, ok := structRequiredCache[tp]
	structCacheLock.RUnlock()
	if ok {
		return val.required, val.err
	}

	required, err := implicitValueRequiredStruct(tag, tp)
	structCacheLock.Lock()
	structRequiredCache[tp] = requiredCacheValue{
		required: required,
		err:      err,
	}
	structCacheLock.Unlock()

	return required, err
}

func toFloat64(v interface{}) (float64, bool) {
	switch val := v.(type) {
	case int:
		return float64(val), true
	case int8:
		return float64(val), true
	case int16:
		return float64(val), true
	case int32:
		return float64(val), true
	case int64:
		return float64(val), true
	case uint:
		return float64(val), true
	case uint8:
		return float64(val), true
	case uint16:
		return float64(val), true
	case uint32:
		return float64(val), true
	case uint64:
		return float64(val), true
	case float32:
		return float64(val), true
	case float64:
		return val, true
	default:
		return 0, false
	}
}

func usingDifferentKeys(key string, field reflect.StructField) bool {
	if len(field.Tag) > 0 {
		if _, ok := field.Tag.Lookup(key); !ok {
			return true
		}
	}

	return false
}

func validateAndSetValue(kind reflect.Kind, value reflect.Value, str string, opts *fieldOptionsWithContext) error {
	if !value.CanSet() {
		return errValueNotSettable
	}

	v, err := convertType(kind, str)
	if err != nil {
		return err
	}

	if err := validateValueRange(v, opts); err != nil {
		return err
	}

	return setMatchedPrimitiveValue(kind, value, v)
}

func validateJsonNumberRange(v json.Number, opts *fieldOptionsWithContext) error {
	if opts == nil || opts.Range == nil {
		return nil
	}

	fv, err := v.Float64()
	if err != nil {
		return err
	}

	return validateNumberRange(fv, opts.Range)
}

func validateNumberRange(fv float64, nr *numberRange) error {
	if nr == nil {
		return nil
	}

	if (nr.leftInclude && fv < nr.left) || (!nr.leftInclude && fv <= nr.left) {
		return errNumberRange
	}

	if (nr.rightInclude && fv > nr.right) || (!nr.rightInclude && fv >= nr.right) {
		return errNumberRange
	}

	return nil
}

func validateValueInOptions(options []string, value interface{}) error {
	if len(options) > 0 {
		switch v := value.(type) {
		case string:
			if !stringx.Contains(options, v) {
				return fmt.Errorf(`error: value "%s" is not defined in options "%v"`, v, options)
			}
		default:
			if !stringx.Contains(options, Repr(v)) {
				return fmt.Errorf(`error: value "%v" is not defined in options "%v"`, value, options)
			}
		}
	}

	return nil
}

func validateValueRange(mapValue interface{}, opts *fieldOptionsWithContext) error {
	if opts == nil || opts.Range == nil {
		return nil
	}

	fv, ok := toFloat64(mapValue)
	if !ok {
		return errNumberRange
	}

	return validateNumberRange(fv, opts.Range)
}
