package utils

import (
	"bytes"
	"encoding/json"
	"fmt"
	"github.com/beego/beego/v2/core/validation"
	jsonlib "github.com/linmadan/egglib-go/utils/json"
	"github.com/shopspring/decimal"
	"io"
	"reflect"
	"strconv"
	"strings"
	"time"
)

func CamelCase(name string, firstUpper bool) string {
	array := []byte(name)
	if len(array) == 0 {
		return ""
	}
	rspArray := make([]byte, len(array))
	if firstUpper {
		copy(rspArray[:1], strings.ToUpper(string(array[:1])))
	} else {
		copy(rspArray[:1], strings.ToLower(string(array[:1])))
	}
	copy(rspArray[1:], array[1:])
	return string(rspArray)
}

func ObjectToMap(o interface{}) map[string]interface{} {
	if o == nil {
		return nil
	}
	value := reflect.ValueOf(o)
	if value.Kind() != reflect.Ptr {
		return nil
	}
	elem := value.Elem()
	relType := elem.Type()
	m := make(map[string]interface{})
	for i := 0; i < relType.NumField(); i++ {
		field := relType.Field(i)
		if elem.Field(i).IsZero() {
			continue
		}
		m[CamelCase(field.Name, false)] = elem.Field(i).Interface()
	}
	return m
}

func DeleteMapKeys(options map[string]interface{}, keys ...string) map[string]interface{} {
	for i := range keys {
		if _, ok := options[keys[i]]; ok {
			delete(options, keys[i])
		}
	}
	return options
}

// AssertString convert v to string value
func AssertString(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())
	}
}

// ValidatePtr validate v is a ptr value
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 LoadCustomFieldToMap(src interface{}, fields ...string) map[string]interface{} {
	rsp := LoadCustomField(src, fields...)
	if rsp == nil {
		return map[string]interface{}{}
	}
	return rsp.(map[string]interface{})
}

func LoadCustomField(src interface{}, fields ...string) interface{} {
	typeSrc := reflect.TypeOf(src)
	valueSrc := reflect.ValueOf(src)

	if v, ok := src.(reflect.Value); ok {
		valueSrc = v
		typeSrc = v.Type()
	}
	if typeSrc.Kind() == reflect.Ptr {
		valueSrc = valueSrc.Elem()
	}
	k := valueSrc.Kind()
	switch k {
	case reflect.Array, reflect.Slice:
		len := valueSrc.Len()
		retSliceMap := make([]map[string]interface{}, 0)
		if len == 0 {
			return retSliceMap
		}
		for i := 0; i < len; i++ {
			v := valueSrc.Index(i)
			retSliceMap = append(retSliceMap, (LoadCustomField(v, fields...)).(map[string]interface{}))
		}
		return retSliceMap
	case reflect.Struct:
		retSliceMap := make(map[string]interface{})
		for _, filed := range fields {
			f := valueSrc.FieldByName(filed)
			if !f.IsValid() {
				continue
			}
			v := f.Interface()
			if t, ok := v.(time.Time); ok {
				v = t.Local().Format("2006-01-02 15:04:05")
			}
			retSliceMap[CamelCase(filed, false)] = v
		}
		return retSliceMap
	default:
		return src
	}
	return src
}

func AppendCustomField(src interface{}, options map[string]interface{}) interface{} {
	var mapSrc map[string]interface{}
	var ok bool
	mapSrc, ok = src.(map[string]interface{})
	if !ok {
		jsonlib.Unmarshal([]byte(jsonlib.MarshalToString(src)), &mapSrc)
	}
	for field, value := range options {
		mapSrc[CamelCase(field, false)] = value
	}
	return mapSrc
}

/*

json 格式化

*/

func Marshal(v interface{}) ([]byte, error) {
	return json.Marshal(v)
}

func Unmarshal(data []byte, v interface{}) error {
	decoder := json.NewDecoder(bytes.NewReader(data))
	if err := unmarshalUseNumber(decoder, v); err != nil {
		return formatError(string(data), err)
	}

	return nil
}

func UnmarshalFromString(str string, v interface{}) error {
	decoder := json.NewDecoder(strings.NewReader(str))
	if err := unmarshalUseNumber(decoder, v); err != nil {
		return formatError(str, err)
	}

	return nil
}

func UnmarshalFromReader(reader io.Reader, v interface{}) error {
	var buf strings.Builder
	teeReader := io.TeeReader(reader, &buf)
	decoder := json.NewDecoder(teeReader)
	if err := unmarshalUseNumber(decoder, v); err != nil {
		return formatError(buf.String(), err)
	}

	return nil
}

func unmarshalUseNumber(decoder *json.Decoder, v interface{}) error {
	decoder.UseNumber()
	return decoder.Decode(v)
}

func formatError(v string, err error) error {
	return fmt.Errorf("string: `%s`, error: `%s`", v, err.Error())
}

type ReflectVal struct {
	T reflect.Type
	V reflect.Value
}

/*
	拷贝当前对象到目标对象,具有相同属性的值
*/
func CopyObject(src, dst interface{}) {
	var srcMap = make(map[string]ReflectVal)

	vs := reflect.ValueOf(src)
	ts := reflect.TypeOf(src)
	vd := reflect.ValueOf(dst)
	td := reflect.TypeOf(dst)

	ls := vs.Elem().NumField()
	for i := 0; i < ls; i++ {
		srcMap[ts.Elem().Field(i).Name] = ReflectVal{
			T: vs.Elem().Field(i).Type(),
			V: vs.Elem().Field(i),
		}
	}

	ld := vd.Elem().NumField()
	for i := 0; i < ld; i++ {
		n := td.Elem().Field(i).Name
		t := vd.Elem().Field(i).Type()
		if v, ok := srcMap[n]; ok && v.T == t && vd.Elem().Field(i).CanSet() {
			vd.Elem().Field(i).Set(v.V)
		}
	}
}

/*
	时间计算
*/

func ValidWorkTime(t string) error {
	ts := strings.Split(t, ":")
	if len(ts) != 2 {
		return fmt.Errorf("时间格式有误")
	}
	ts1, err := strconv.Atoi(ts[0])
	if err != nil {
		return fmt.Errorf("小时格式有误")
	}
	if !(ts1 < 24 && ts1 >= 0) {
		return fmt.Errorf("小时格式有误")
	}
	ts2, err := strconv.Atoi(ts[1])
	if err != nil {
		return fmt.Errorf("分钟格式有误")
	}
	if !(ts2 < 60 && ts2 >= 0) {
		return fmt.Errorf("分钟格式有误")
	}
	return nil
}

// 计算两个时间间隔的时间
func ComputeTimeDuration(t1, t2 string) (result time.Duration, err error) {
	if err = ValidWorkTime(t1); err != nil {
		return
	}
	if err = ValidWorkTime(t2); err != nil {
		return
	}
	t1s := strings.Split(t1, ":")
	t1sHour, _ := strconv.Atoi(t1s[0])
	t1sMin, _ := strconv.Atoi(t1s[1])

	t2s := strings.Split(t2, ":")
	t2sHour, _ := strconv.Atoi(t2s[0])
	t2sMin, _ := strconv.Atoi(t2s[1])
	var t1t, t2t time.Time
	if t1sHour < t2sHour {
		t1t = time.Date(2006, 1, 1, t1sHour, t1sMin, 0, 0, time.Local)
		t2t = time.Date(2006, 1, 1, t2sHour, t2sMin, 0, 0, time.Local)
	} else {
		t1t = time.Date(2006, 1, 1, t1sHour, t1sMin, 0, 0, time.Local)
		t2t = time.Date(2006, 1, 2, t2sHour, t2sMin, 0, 0, time.Local)
	}
	ts := t2t.Sub(t1t)
	return ts, nil
}

func ToArrayString(inputs []int) []string {
	result := make([]string, 0)
	for i := range inputs {
		result = append(result, strconv.Itoa(inputs[i]))
	}
	return result
}

func ToArrayInt(inputs []string) []int {
	result := make([]int, 0)
	for i := range inputs {
		v, _ := strconv.Atoi(inputs[i])
		result = append(result, v)
	}
	return result
}

func LoadQueryObject(queryOption map[string]interface{}, obj interface{}) error {
	jsonlib.UnmarshalFromString(jsonlib.MarshalToString(queryOption), obj)
	validation := validation.Validation{}
	result, err := validation.Valid(obj)
	if !result && len(validation.Errors) > 0 {
		return validation.Errors[0]
	}
	return err
}

// 字符串截取
func SubStr(str string, start, length int) string {
	rs := []rune(str)
	rl := len(rs)
	end := 0
	if start < 0 {
		start = rl - 1 + start
	}
	end = start + length
	if start > end {
		start, end = end, start
	}
	if start < 0 {
		start = 0
	}
	if start > rl {
		start = rl
	}
	if end < 0 {
		end = 0
	}
	if end > rl {
		end = rl
	}
	return string(rs[start:end])
}

func Round(value float64, places int32) float64 {
	quantity := decimal.NewFromFloat(value)
	d := quantity.Round(places)
	rsp, _ := d.Float64()
	return rsp
}