package utils

import (
	"bytes"
	"encoding/base64"
	"fmt"
	"git.rosy.net.cn/baseapi"
	uuid "github.com/satori/go.uuid"
	"net/http"
	"reflect"
	"strings"
	"time"
	"unicode/utf8"
)

const (
	APIPin = "jxadmin"
)

func DictKeysMan(data interface{}, keysToRemove []string, keysToKeep []string) interface{} {
	if data == nil || (keysToKeep == nil && keysToRemove == nil) {
		return data
	}

	var keysToRemoveMap map[string]int
	var keysToKeepMap map[string]int
	if keysToKeep != nil {
		keysToKeepMap = make(map[string]int)
		for _, v := range keysToKeep {
			keysToKeepMap[v] = 1
		}
	} else if keysToRemove != nil {
		keysToRemoveMap = make(map[string]int)
		for _, v := range keysToRemove {
			keysToRemoveMap[v] = 1
		}
	}

	dataIsSlice := true
	valueOfData := reflect.ValueOf(data)
	if valueOfData.Kind() != reflect.Slice {
		dataIsSlice = false
		valueOfData = reflect.ValueOf([]interface{}{data})
	} else if valueOfData.Len() == 0 {
		return data
	}

	retVal := make([]interface{}, valueOfData.Len())

	for index := 0; index < valueOfData.Len(); index++ {
		realV := GetConcretValue(valueOfData.Index(index))
		if keysToRemoveMap != nil || keysToKeepMap != nil {
			mapV := make(map[string]interface{})
			for _, key := range realV.MapKeys() {
				fieldName := key.String()
				wantThisField := true
				if keysToKeepMap != nil {
					if _, ok := keysToKeepMap[fieldName]; !ok {
						wantThisField = false
					}
				} else if keysToRemoveMap != nil {
					if _, ok := keysToRemoveMap[fieldName]; ok {
						wantThisField = false
					}
				}
				if wantThisField {
					mapV[fieldName] = GetConcretValue(realV.MapIndex(key)).Interface()
				}
			}
			retVal[index] = mapV
		} else {
			retVal[index] = realV.Interface()
		}
	}

	if !dataIsSlice {
		return retVal[0]
	}
	return retVal
}

// 去除-号，全部大写，比如：929ADB626EB911E893E452540009DAB3
func GetUUID() string {
	return strings.Replace(GetUpperUUID(), "-", "", -1)
}

func GetUpperUUID() string {
	return strings.ToUpper(uuid.Must(uuid.NewV1()).String())
}

func GetCurTimeStr() string {
	return Time2Str(time.Now())
}

func GetCurDate() time.Time {
	now := time.Now()
	year, month, day := now.Date()
	return time.Date(year, month, day, 0, 0, 0, 0, now.Location())
}

func GetDate(dateTime time.Time) time.Time {
	year, month, day := dateTime.Date()
	return time.Date(year, month, day, 0, 0, 0, 0, dateTime.Location())
}

func GetTimeRange(baseTime time.Time, dayNum int, includeBaseTime bool) (beginTime, endTime time.Time) {
	if dayNum <= 0 {
		dayNum = 1
	}
	baseTime = GetDate(baseTime)
	if includeBaseTime {
		dayNum = dayNum - 1
		endTime = baseTime.Add(time.Hour*24 - time.Second)
	} else {
		endTime = baseTime.Add(-time.Second)
	}
	beginTime = baseTime.Add(-time.Hour * 24 * time.Duration(dayNum))

	return beginTime, endTime
}

// timestamp is in second
func GetCurTimestamp() int64 {
	return time.Now().Unix()
}

func GetAPIOperator(userName string) string {
	retVal := APIPin
	if userName != "" {
		retVal += "-" + userName
	}
	retVal = LimitUTF8StringLen(retVal, 50)
	return retVal
}

func CallFuncLogError(funcToCall func() error, msg string, params ...interface{}) error {
	err := funcToCall()
	if err != nil {
		baseapi.SugarLogger.Warnf("Failed "+msg+" error:%v", append(params, err.Error())...)
	}
	return err
}

func CallFuncLogErrorIgnore(funcToCall func() error, msg string, ignoreErr error, params ...interface{}) error {
	err := funcToCall()
	if err != nil && err != ignoreErr {
		baseapi.SugarLogger.Warnf("Failed "+msg+" error:%v", append(params, err.Error())...)
	}
	return err
}

func CallFuncLogErrorWithInfo(funcToCall func() error, msg string, params ...interface{}) error {
	err := funcToCall()
	if err != nil {
		baseapi.SugarLogger.Infof("Failed "+msg+" error:%v", append(params, err.Error())...)
	}
	return err
}

func CallFuncAsync(funcToCall func()) {
	go func() {
		defer func() {
			if r := recover(); r != nil {
				baseapi.SugarLogger.Errorf("error when calling func:%v, r:%v", funcToCall, r)
			}
		}()
		funcToCall()
	}()
}

func AfterFuncWithRecover(duration time.Duration, funcToCall func()) *time.Timer {
	return time.AfterFunc(duration, func() {
		defer func() {
			if r := recover(); r != nil {
				baseapi.SugarLogger.Errorf("error when calling func:%v, r:%v", funcToCall, r)
			}
		}()
		funcToCall()
	})
}

func CallFuncRetryAsync(handler func(int) error, duration time.Duration, retryCount int) (err error) {
	err = handler(retryCount)
	if err != nil && retryCount > 0 {
		AfterFuncWithRecover(duration, func() {
			CallFuncRetryAsync(handler, duration, retryCount-1)
		})
	}
	return err
}

func GenerateGetURL(baseURL, apiStr string, params map[string]interface{}) string {
	queryString := ""
	if params != nil {
		queryString = "?" + Map2URLValues(params).Encode()
	}
	if apiStr != "" {
		return baseURL + "/" + apiStr + queryString
	}
	return baseURL + queryString
}

func GenerateGetURL2(baseURL, apiStr string, params map[string]interface{}) string {
	queryString := ""
	bizContent := map[string]interface{}{"biz_content": params["biz_content"]}

	paramsString := ""
	for k, v := range params {
		if k == "biz_content" {
			continue
		}
		paramsString += "&" + fmt.Sprintf("%s=%v", k, v)
	}

	if params != nil {
		queryString = "?" + Map2URLValues(bizContent).Encode() + paramsString
	}
	if apiStr != "" {
		return baseURL + "/" + apiStr + queryString
	}
	return baseURL + queryString
}

func BuildRequest(method, url, body, contentType string) (request *http.Request) {
	request, _ = http.NewRequest(method, url, strings.NewReader(body))
	if contentType == "" {
		contentType = "application/x-www-form-urlencoded"
	}
	request.Header.Set("Content-Type", contentType)
	return request
}

func SendFakeRequest(method, url, body, contentType string) (*http.Response, error) {
	client := &http.Client{}
	return client.Do(BuildRequest(method, url, body, contentType))
}

// 过滤 utf8mb4（比如emoji表情）
func FilterMb4(content string) string {
	newContent := &strings.Builder{}
	for _, value := range content {
		size := utf8.RuneLen(value)
		if size >= 1 && size <= 3 {
			newContent.WriteRune(value)
		}
	}
	return newContent.String()
}

// 是否是修饰符，比如控制EMOJI的颜色控制符，ZWJ
func IsDecorateRune(value rune) bool {
	return value == 0xFE0F || value == 0xFE0E || value == 0x200D
}

func FilterEmoji(content string) string {
	newContent := &strings.Builder{}
	for _, value := range []rune(content) {
		size := utf8.RuneLen(value)
		if size >= 1 && size <= 3 && !(value >= 0x2300 && value < 0x32A0 || value >= 0x1F000 && value < 0x1F9F0) && !IsDecorateRune(value) {
			newContent.WriteRune(value)
		}
	}
	return newContent.String()
}

func TrimBlankChar(str string) string {
	return strings.Trim(str, "\u202C\n\r\t ")
}

func RemoveGeneralMapKeys(obj map[string]interface{}, keys ...string) map[string]interface{} {
	if obj != nil {
		for _, key := range keys {
			if _, ok := obj[key]; ok {
				delete(obj, key)
			}
		}
	}
	return obj
}

func FilterMapNilMembers(mapData map[string]interface{}) map[string]interface{} {
	for k, v := range mapData {
		if v == nil {
			delete(mapData, k)
		}
	}
	return mapData
}

func Base64DecodeMultiString(strs ...string) (decodedData [][]byte, err error) {
	decodedData = make([][]byte, len(strs))
	for k, v := range strs {
		if decodedData[k], err = base64.StdEncoding.DecodeString(v); err != nil {
			return nil, err
		}
	}
	return decodedData, nil
}

// 限制的是字节数，只适合纯英文的情况，推荐使用LimitMixedStringLen，除非确定是纯英文，且对性能相当敏感
func LimitStringLen(str string, maxByteCount int) (limitedStr string) {
	if maxByteCount > 0 {
		if strLen := len(str); strLen > maxByteCount {
			str = str[:maxByteCount]
		}
	}
	return str
}

// 限制的是字符数，不是字节数
func LimitUTF8StringLen(str string, maxRuneCount int) (limitedStr string) {
	if maxRuneCount > 0 {
		if len(str) > maxRuneCount {
			runeList := []rune(str)
			if len(runeList) > maxRuneCount {
				str = string(runeList[:maxRuneCount])
			}
		}
	}
	return str
}

// 限制的是字节数，正常处理乱码
func LimitMixedStringLen(str string, maxByteCount int) (limitedStr string) {
	if maxByteCount <= 0 {
		return str
	}
	length := len(str)
	if maxByteCount >= length {
		return str
	}
	bs := []byte(str)[:maxByteCount]
	bl := 0
	for i := len(bs) - 1; i >= 0; i-- {
		switch {
		case bs[i] >= 0 && bs[i] <= 127:
			return string(bs[:i+1])
		case bs[i] >= 128 && bs[i] <= 191:
			bl++
		case bs[i] >= 192 && bs[i] <= 253:
			cl := 0
			switch {
			case bs[i]&252 == 252:
				cl = 6
			case bs[i]&248 == 248:
				cl = 5
			case bs[i]&240 == 240:
				cl = 4
			case bs[i]&224 == 224:
				cl = 3
			default:
				cl = 2
			}
			if bl+1 == cl {
				return string(bs[:i+cl])
			}
			return string(bs[:i])
		}
	}
	return ""
}

// 限制的是字符数，不是字节数
func LimitUTF8StringLen2(str string, maxRuneCount int) (limitedStr string) {
	if maxRuneCount > 0 {
		if len(str) > maxRuneCount {
			runeList := []rune(str)
			if len(runeList) > maxRuneCount {
				str = string(runeList[:maxRuneCount])
			}
		}
	}
	limitedStr = loop([]byte(str))
	return limitedStr
}

func loop(bs []byte) (result string) {
	for i := len(bs) - 1; i >= 0; i-- {
		if bs[i] == 0 {
			bs2 := bs[:i]
			bs3 := bs[i+1:]
			bs4 := [][]byte{bs2, bs3}
			return loop(bytes.Join(bs4, []byte("")))
		}
	}
	return string(bs)
}
