package utils

import (
	"crypto/sha256"
	"encoding/hex"
	"fmt"
	"github.com/civet148/log"
	"net/url"
	"reflect"
	"sort"
	"strings"
)

const (
	TAG_VALUE_IGNORE = "-"
	TAG_NAME_JSON    = "json"
)

var tagNames = []string{TAG_NAME_JSON}

//MakeSignString make a sign string sort by alpha character
// 				 obj can be url.Values, struct with json tag or map[string]interface{}
func MakeSignString(obj interface{}, excepts ...string) string {
	var strSort string
	if values, ok := obj.(url.Values); ok {
		dic := make(map[string]interface{}, 0)
		for k, v := range values {
			if len(v) > 0 {
				dic[k] = v[0]
			}
		}
		strSort = makeSignStringByMap(dic, excepts...)
	} else {
		typ := reflect.TypeOf(obj)
		val := reflect.ValueOf(obj)
		for typ.Kind() == reflect.Ptr {
			typ = typ.Elem()
			val = val.Elem()
		}
		switch typ.Kind() {
		case reflect.String:
			strSort = obj.(string)
		case reflect.Map:
			strSort = makeSignStringByMap(obj.(map[string]interface{}), excepts...)
		case reflect.Struct:
			strSort = makeSignStringByStruct(typ, val, excepts...)
		default:
			log.Errorf("object type [%s] not support", typ.Name())
		}
	}
	return strSort
}

func MakeSignSHA256Hex(obj interface{}, excepts ...string) string {
	strToSign := MakeSignString(obj, excepts...)
	digestHash := sha256.Sum256([]byte(strToSign))
	return hex.EncodeToString(digestHash[:])
}

func MakeSignSHA256(obj interface{}, excepts ...string) []byte {
	strToSign := MakeSignString(obj, excepts...)
	digestHash := sha256.Sum256([]byte(strToSign))
	return digestHash[:]
}

func makeSignStringByStruct(typ reflect.Type, val reflect.Value, excepts ...string) string {
	dic := parseStructFields(typ, val, tagNames...)
	return makeSignStringByMap(dic, excepts...)
}

func makeSignStringByMap(dic map[string]interface{}, excepts ...string) string {
	var keys, values []string
	for _, v := range excepts {
		delete(dic, v)
	}
	for k, _ := range dic {
		keys = append(keys, k)
	}
	sort.Slice(keys, func(i, j int) bool {
		if strings.Compare(keys[i], keys[j]) < 0 {
			return true
		}
		return false
	})
	for _, key := range keys {
		v := fmt.Sprintf("%s=%v", key, dic[key])
		values = append(values, v)
	}
	return strings.Join(values, "&")
}

// parse struct fields
func parseStructFields(typ reflect.Type, val reflect.Value, tagNames ...string) (dic map[string]interface{}) {

	kind := typ.Kind()
	dic = make(map[string]interface{}, 0)

	if kind == reflect.Struct {
		NumField := val.NumField()
		for i := 0; i < NumField; i++ {
			typField := typ.Field(i)
			valField := val.Field(i)

			if typField.Type.Kind() == reflect.Ptr {
				typField.Type = typField.Type.Elem()
				valField = valField.Elem()
			}
			if !valField.IsValid() || !valField.CanInterface() {
				continue
			}
			saveValueByField(dic, typField, valField, tagNames...) // save field tag value and field value to map
		}
	}
	return dic
}

//trim the field value's first and last blank character and save to map
func saveValueByField(dic map[string]interface{}, field reflect.StructField, val reflect.Value, tagNames ...string) {

	if len(tagNames) == 0 {
		log.Errorf("no tag to save value")
		return
	}

	var tagVal string
	for _, v := range tagNames {
		strTagValue, ignore := getTag(field, v)
		tagVal = handleTagValue(v, strTagValue)
		if ignore {
			break
		}
		if tagVal == "" {
			tagVal = field.Name
		}
		dic[tagVal] = fmt.Sprintf("%v", val.Interface())
	}
}

// get struct field's tag value
func getTag(sf reflect.StructField, tagName string) (strValue string, ignore bool) {

	strValue = sf.Tag.Get(tagName)
	if strValue == TAG_VALUE_IGNORE {
		return "", true
	}
	return
}

func handleTagValue(strTagName, strTagValue string) string {
	if strTagValue == "" {
		return ""
	}
	if strTagName == TAG_NAME_JSON {
		vs := strings.Split(strTagValue, ",")
		strTagValue = vs[0]
	}
	return strTagValue
}
