package structure

// references: https://github.com/mitchellh/mapstructure

import (
	"encoding"
	"encoding/base64"
	"fmt"
	"reflect"
	"sort"
	"strconv"
	"strings"
)

// Option is the configuration that is used to create a new decoder
type Option struct {
	TagName          string
	WeaklyTypedInput bool
	KeyReplacer      *strings.Replacer
}

var DefaultKeyReplacer = strings.NewReplacer("_", "-")

// Decoder is the core of structure
type Decoder struct {
	option *Option
}

// NewDecoder return a Decoder by Option
func NewDecoder(option Option) *Decoder {
	if option.TagName == "" {
		option.TagName = "structure"
	}
	return &Decoder{option: &option}
}

// Decode transform a map[string]any to a struct
func (d *Decoder) Decode(src map[string]any, dst any) error {
	if reflect.TypeOf(dst).Kind() != reflect.Ptr {
		return fmt.Errorf("decode must recive a ptr struct")
	}
	return d.decode("", src, reflect.ValueOf(dst).Elem())
}

// isNil returns true if the input is nil or a typed nil pointer.
func isNil(input any) bool {
	if input == nil {
		return true
	}
	val := reflect.ValueOf(input)
	return val.Kind() == reflect.Pointer && val.IsNil()
}

func (d *Decoder) decode(name string, data any, val reflect.Value) error {
	if isNil(data) {
		// If the data is nil, then we don't set anything
		// Maybe we should set to zero value?
		return nil
	}
	if !reflect.ValueOf(data).IsValid() {
		// If the input value is invalid, then we just set the value
		// to be the zero value.
		val.Set(reflect.Zero(val.Type()))
		return nil
	}
	for {
		kind := val.Kind()
		if kind == reflect.Pointer && val.IsNil() {
			val.Set(reflect.New(val.Type().Elem()))
		}
		if ok, err := d.decodeTextUnmarshaller(name, data, val); ok {
			return err
		}
		switch {
		case isInt(kind):
			return d.decodeInt(name, data, val)
		case isUint(kind):
			return d.decodeUint(name, data, val)
		case isFloat(kind):
			return d.decodeFloat(name, data, val)
		}
		switch kind {
		case reflect.Pointer:
			val = val.Elem()
			continue
		case reflect.String:
			return d.decodeString(name, data, val)
		case reflect.Bool:
			return d.decodeBool(name, data, val)
		case reflect.Slice:
			return d.decodeSlice(name, data, val)
		case reflect.Map:
			return d.decodeMap(name, data, val)
		case reflect.Interface:
			return d.setInterface(name, data, val)
		case reflect.Struct:
			return d.decodeStruct(name, data, val)
		default:
			return fmt.Errorf("type %s not support", val.Kind().String())
		}
	}
}

func isInt(kind reflect.Kind) bool {
	switch kind {
	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
		return true
	default:
		return false
	}
}

func isUint(kind reflect.Kind) bool {
	switch kind {
	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
		return true
	default:
		return false
	}
}

func isFloat(kind reflect.Kind) bool {
	switch kind {
	case reflect.Float32, reflect.Float64:
		return true
	default:
		return false
	}
}

func (d *Decoder) decodeInt(name string, data any, val reflect.Value) (err error) {
	dataVal := reflect.ValueOf(data)
	kind := dataVal.Kind()
	switch {
	case isInt(kind):
		val.SetInt(dataVal.Int())
	case isUint(kind) && d.option.WeaklyTypedInput:
		val.SetInt(int64(dataVal.Uint()))
	case isFloat(kind) && d.option.WeaklyTypedInput:
		val.SetInt(int64(dataVal.Float()))
	case kind == reflect.String && d.option.WeaklyTypedInput:
		var i int64
		i, err = strconv.ParseInt(dataVal.String(), 0, val.Type().Bits())
		if err == nil {
			val.SetInt(i)
		} else {
			err = fmt.Errorf("cannot parse '%s' as int: %s", name, err)
		}
	default:
		err = fmt.Errorf(
			"'%s' expected type '%s', got unconvertible type '%s'",
			name, val.Type(), dataVal.Type(),
		)
	}
	return err
}

func (d *Decoder) decodeUint(name string, data any, val reflect.Value) (err error) {
	dataVal := reflect.ValueOf(data)
	kind := dataVal.Kind()
	switch {
	case isUint(kind):
		val.SetUint(dataVal.Uint())
	case isInt(kind) && d.option.WeaklyTypedInput:
		val.SetUint(uint64(dataVal.Int()))
	case isFloat(kind) && d.option.WeaklyTypedInput:
		val.SetUint(uint64(dataVal.Float()))
	case kind == reflect.String && d.option.WeaklyTypedInput:
		var i uint64
		i, err = strconv.ParseUint(dataVal.String(), 0, val.Type().Bits())
		if err == nil {
			val.SetUint(i)
		} else {
			err = fmt.Errorf("cannot parse '%s' as int: %s", name, err)
		}
	default:
		err = fmt.Errorf(
			"'%s' expected type '%s', got unconvertible type '%s'",
			name, val.Type(), dataVal.Type(),
		)
	}
	return err
}

func (d *Decoder) decodeFloat(name string, data any, val reflect.Value) (err error) {
	dataVal := reflect.ValueOf(data)
	kind := dataVal.Kind()
	switch {
	case isFloat(kind):
		val.SetFloat(dataVal.Float())
	case isUint(kind):
		val.SetFloat(float64(dataVal.Uint()))
	case isInt(kind) && d.option.WeaklyTypedInput:
		val.SetFloat(float64(dataVal.Int()))
	case kind == reflect.String && d.option.WeaklyTypedInput:
		var i float64
		i, err = strconv.ParseFloat(dataVal.String(), val.Type().Bits())
		if err == nil {
			val.SetFloat(i)
		} else {
			err = fmt.Errorf("cannot parse '%s' as int: %s", name, err)
		}
	default:
		err = fmt.Errorf(
			"'%s' expected type '%s', got unconvertible type '%s'",
			name, val.Type(), dataVal.Type(),
		)
	}
	return err
}

func (d *Decoder) decodeString(name string, data any, val reflect.Value) (err error) {
	dataVal := reflect.ValueOf(data)
	kind := dataVal.Kind()
	switch {
	case kind == reflect.String:
		val.SetString(dataVal.String())
	case isInt(kind) && d.option.WeaklyTypedInput:
		val.SetString(strconv.FormatInt(dataVal.Int(), 10))
	case isUint(kind) && d.option.WeaklyTypedInput:
		val.SetString(strconv.FormatUint(dataVal.Uint(), 10))
	case isFloat(kind) && d.option.WeaklyTypedInput:
		val.SetString(strconv.FormatFloat(dataVal.Float(), 'E', -1, dataVal.Type().Bits()))
	default:
		err = fmt.Errorf(
			"'%s' expected type '%s', got unconvertible type '%s'",
			name, val.Type(), dataVal.Type(),
		)
	}
	return err
}

func (d *Decoder) decodeBool(name string, data any, val reflect.Value) (err error) {
	dataVal := reflect.ValueOf(data)
	kind := dataVal.Kind()
	switch {
	case kind == reflect.Bool:
		val.SetBool(dataVal.Bool())
	case isInt(kind) && d.option.WeaklyTypedInput:
		val.SetBool(dataVal.Int() != 0)
	case isUint(kind) && d.option.WeaklyTypedInput:
		val.SetBool(dataVal.Uint() != 0)
	default:
		err = fmt.Errorf(
			"'%s' expected type '%s', got unconvertible type '%s'",
			name, val.Type(), dataVal.Type(),
		)
	}
	return err
}

func (d *Decoder) decodeSlice(name string, data any, val reflect.Value) error {
	dataVal := reflect.Indirect(reflect.ValueOf(data))
	valType := val.Type()
	valElemType := valType.Elem()

	if dataVal.Kind() == reflect.String && valElemType.Kind() == reflect.Uint8 { // from encoding/json
		s := []byte(dataVal.String())
		b := make([]byte, base64.StdEncoding.DecodedLen(len(s)))
		n, err := base64.StdEncoding.Decode(b, s)
		if err != nil {
			return fmt.Errorf("try decode '%s' by base64 error: %w", name, err)
		}
		val.SetBytes(b[:n])
		return nil
	}

	if dataVal.Kind() != reflect.Slice {
		return fmt.Errorf("'%s' is not a slice", name)
	}

	valSlice := val
	// make a new slice with cap(val)==cap(dataVal)
	// the caller can determine whether the original configuration contains this item by judging whether the value is nil.
	valSlice = reflect.MakeSlice(valType, 0, dataVal.Len())
	for i := 0; i < dataVal.Len(); i++ {
		currentData := dataVal.Index(i).Interface()
		for valSlice.Len() <= i {
			valSlice = reflect.Append(valSlice, reflect.Zero(valElemType))
		}
		fieldName := fmt.Sprintf("%s[%d]", name, i)
		if currentData == nil {
			// in weakly type mode, null will convert to zero value
			if d.option.WeaklyTypedInput {
				continue
			}
			// in non-weakly type mode, null will convert to nil if element's zero value is nil, otherwise return an error
			if elemKind := valElemType.Kind(); elemKind == reflect.Map || elemKind == reflect.Slice {
				continue
			}
			return fmt.Errorf("'%s' can not be null", fieldName)
		}
		currentField := valSlice.Index(i)
		if err := d.decode(fieldName, currentData, currentField); err != nil {
			return err
		}
	}

	val.Set(valSlice)
	return nil
}

func (d *Decoder) decodeMap(name string, data any, val reflect.Value) error {
	valType := val.Type()
	valKeyType := valType.Key()
	valElemType := valType.Elem()

	valMap := val

	if valMap.IsNil() {
		mapType := reflect.MapOf(valKeyType, valElemType)
		valMap = reflect.MakeMap(mapType)
	}

	dataVal := reflect.Indirect(reflect.ValueOf(data))
	if dataVal.Kind() != reflect.Map {
		return fmt.Errorf("'%s' expected a map, got '%s'", name, dataVal.Kind())
	}

	return d.decodeMapFromMap(name, dataVal, val, valMap)
}

func (d *Decoder) decodeMapFromMap(name string, dataVal reflect.Value, val reflect.Value, valMap reflect.Value) error {
	valType := val.Type()
	valKeyType := valType.Key()
	valElemType := valType.Elem()

	errors := make([]string, 0)

	if dataVal.Len() == 0 {
		if dataVal.IsNil() {
			if !val.IsNil() {
				val.Set(dataVal)
			}
		} else {
			val.Set(valMap)
		}

		return nil
	}

	for _, k := range dataVal.MapKeys() {
		fieldName := fmt.Sprintf("%s[%s]", name, k)

		currentKey := reflect.Indirect(reflect.New(valKeyType))
		if err := d.decode(fieldName, k.Interface(), currentKey); err != nil {
			errors = append(errors, err.Error())
			continue
		}

		v := dataVal.MapIndex(k).Interface()
		if v == nil {
			errors = append(errors, fmt.Sprintf("filed %s invalid", fieldName))
			continue
		}

		currentVal := reflect.Indirect(reflect.New(valElemType))
		if err := d.decode(fieldName, v, currentVal); err != nil {
			errors = append(errors, err.Error())
			continue
		}

		valMap.SetMapIndex(currentKey, currentVal)
	}

	val.Set(valMap)

	if len(errors) > 0 {
		return fmt.Errorf(strings.Join(errors, ","))
	}

	return nil
}

func (d *Decoder) decodeStruct(name string, data any, val reflect.Value) error {
	dataVal := reflect.Indirect(reflect.ValueOf(data))

	// If the type of the value to write to and the data match directly,
	// then we just set it directly instead of recursing into the structure.
	if dataVal.Type() == val.Type() {
		val.Set(dataVal)
		return nil
	}

	dataValKind := dataVal.Kind()
	switch dataValKind {
	case reflect.Map:
		return d.decodeStructFromMap(name, dataVal, val)
	default:
		return fmt.Errorf("'%s' expected a map, got '%s'", name, dataVal.Kind())
	}
}

func (d *Decoder) decodeStructFromMap(name string, dataVal, val reflect.Value) error {
	dataValType := dataVal.Type()
	if kind := dataValType.Key().Kind(); kind != reflect.String && kind != reflect.Interface {
		return fmt.Errorf(
			"'%s' needs a map with string keys, has '%s' keys",
			name, dataValType.Key().Kind())
	}

	dataValKeys := make(map[reflect.Value]struct{})
	dataValKeysUnused := make(map[any]struct{})
	for _, dataValKey := range dataVal.MapKeys() {
		dataValKeys[dataValKey] = struct{}{}
		dataValKeysUnused[dataValKey.Interface()] = struct{}{}
	}

	targetValKeysUnused := make(map[any]struct{})
	errors := make([]string, 0)

	// This slice will keep track of all the structs we'll be decoding.
	// There can be more than one struct if there are embedded structs
	// that are squashed.
	structs := make([]reflect.Value, 1, 5)
	structs[0] = val

	// Compile the list of all the fields that we're going to be decoding
	// from all the structs.
	type field struct {
		field reflect.StructField
		val   reflect.Value
	}

	// remainField is set to a valid field set with the "remain" tag if
	// we are keeping track of remaining values.
	var remainField *field

	var fields []field
	for len(structs) > 0 {
		structVal := structs[0]
		structs = structs[1:]

		structType := structVal.Type()

		for i := 0; i < structType.NumField(); i++ {
			fieldType := structType.Field(i)
			fieldVal := structVal.Field(i)
			if fieldVal.Kind() == reflect.Ptr && fieldVal.Elem().Kind() == reflect.Struct {
				// Handle embedded struct pointers as embedded structs.
				fieldVal = fieldVal.Elem()
			}

			// If "squash" is specified in the tag, we squash the field down.
			squash := fieldVal.Kind() == reflect.Struct && fieldType.Anonymous
			remain := false

			// We always parse the tags cause we're looking for other tags too
			tagParts := strings.Split(fieldType.Tag.Get(d.option.TagName), ",")
			for _, tag := range tagParts[1:] {
				if tag == "squash" {
					squash = true
					break
				}

				if tag == "remain" {
					remain = true
					break
				}
			}

			if squash {
				if fieldVal.Kind() != reflect.Struct {
					errors = append(errors,
						fmt.Errorf("%s: unsupported type for squash: %s", fieldType.Name, fieldVal.Kind()).Error())
				} else {
					structs = append(structs, fieldVal)
				}
				continue
			}

			// Build our field
			if remain {
				remainField = &field{fieldType, fieldVal}
			} else {
				// Normal struct field, store it away
				fields = append(fields, field{fieldType, fieldVal})
			}
		}
	}

	// for fieldType, field := range fields {
	for _, f := range fields {
		field, fieldValue := f.field, f.val
		fieldName := field.Name

		tagParts := strings.Split(field.Tag.Get(d.option.TagName), ",")
		tagValue := tagParts[0]
		if tagValue != "" {
			fieldName = tagValue
		}

		if tagValue == "-" {
			continue
		}

		omitempty := false
		for _, tag := range tagParts[1:] {
			if tag == "omitempty" {
				omitempty = true
			}
		}

		rawMapKey := reflect.ValueOf(fieldName)
		rawMapVal := dataVal.MapIndex(rawMapKey)
		if !rawMapVal.IsValid() {
			// Do a slower search by iterating over each key and
			// doing case-insensitive search.
			if d.option.KeyReplacer != nil {
				fieldName = d.option.KeyReplacer.Replace(fieldName)
			}
			for dataValKey := range dataValKeys {
				mK, ok := dataValKey.Interface().(string)
				if !ok {
					// Not a string key
					continue
				}
				if d.option.KeyReplacer != nil {
					mK = d.option.KeyReplacer.Replace(mK)
				}

				if strings.EqualFold(mK, fieldName) {
					rawMapKey = dataValKey
					rawMapVal = dataVal.MapIndex(dataValKey)
					break
				}
			}

			if !rawMapVal.IsValid() {
				// There was no matching key in the map for the value in
				// the struct. Remember it for potential errors and metadata.
				if !omitempty {
					targetValKeysUnused[fieldName] = struct{}{}
				}
				continue
			}
		}

		// Delete the key we're using from the unused map so we stop tracking
		delete(dataValKeysUnused, rawMapKey.Interface())

		if !fieldValue.IsValid() {
			// This should never happen
			panic("field is not valid")
		}

		// If we can't set the field, then it is unexported or something,
		// and we just continue onwards.
		if !fieldValue.CanSet() {
			continue
		}

		// If the name is empty string, then we're at the root, and we
		// don't dot-join the fields.
		if name != "" {
			fieldName = name + "." + fieldName
		}

		if err := d.decode(fieldName, rawMapVal.Interface(), fieldValue); err != nil {
			errors = append(errors, err.Error())
		}
	}

	// If we have a "remain"-tagged field and we have unused keys then
	// we put the unused keys directly into the remain field.
	if remainField != nil && len(dataValKeysUnused) > 0 {
		// Build a map of only the unused values
		remain := map[interface{}]interface{}{}
		for key := range dataValKeysUnused {
			remain[key] = dataVal.MapIndex(reflect.ValueOf(key)).Interface()
		}

		// Decode it as-if we were just decoding this map onto our map.
		if err := d.decodeMap(name, remain, remainField.val); err != nil {
			errors = append(errors, err.Error())
		}

		// Set the map to nil so we have none so that the next check will
		// not error (ErrorUnused)
		dataValKeysUnused = nil
	}

	if len(targetValKeysUnused) > 0 {
		keys := make([]string, 0, len(targetValKeysUnused))
		for rawKey := range targetValKeysUnused {
			keys = append(keys, rawKey.(string))
		}
		sort.Strings(keys)

		err := fmt.Errorf("'%s' has unset fields: %s", name, strings.Join(keys, ", "))
		errors = append(errors, err.Error())
	}

	if len(errors) > 0 {
		return fmt.Errorf(strings.Join(errors, ","))
	}

	return nil
}

func (d *Decoder) setInterface(name string, data any, val reflect.Value) (err error) {
	dataVal := reflect.ValueOf(data)
	val.Set(dataVal)
	return nil
}

func (d *Decoder) decodeTextUnmarshaller(name string, data any, val reflect.Value) (bool, error) {
	if !val.CanAddr() {
		return false, nil
	}
	valAddr := val.Addr()
	if !valAddr.CanInterface() {
		return false, nil
	}
	unmarshaller, ok := valAddr.Interface().(encoding.TextUnmarshaler)
	if !ok {
		return false, nil
	}
	var str string
	if err := d.decodeString(name, data, reflect.Indirect(reflect.ValueOf(&str))); err != nil {
		return false, err
	}
	if err := unmarshaller.UnmarshalText([]byte(str)); err != nil {
		return true, fmt.Errorf("cannot parse '%s' as %s: %s", name, val.Type(), err)
	}
	return true, nil
}
