package utils

import (
	"bytes"
	"context"
	"crypto/md5"
	"crypto/sha256"
	"dataservices/commons/constants"
	"encoding/base64"
	"encoding/hex"
	"encoding/json"
	"fmt"
	"log"
	"math/rand"
	"reflect"
	"regexp"
	"runtime"
	"sort"
	"strings"
	"time"
	"unicode/utf8"

	"github.com/pkg/errors"
	"github.com/tidbcloud/tiinf/errs"
	"go.opentelemetry.io/otel/trace"
	"golang.org/x/exp/constraints"
)

var numberReg = regexp.MustCompile(`^[0-9]+$`)

func GetFuncNameWithSkip(skip int) string {
	pc := make([]uintptr, 1)
	runtime.Callers(skip, pc)
	f := runtime.FuncForPC(pc[0])
	return f.Name()
}

// GetFuncName get caller's name
func GetFuncName() string {
	return GetFuncNameWithSkip(3)
}

// GetParentFuncName get caller's parent name
func GetParentFuncName() string {
	return GetFuncNameWithSkip(4)
}

// GetGrandFuncName get caller's grand parent name
func GetGrandFuncName() string {
	return GetFuncNameWithSkip(5)
}

const letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
const numLetters = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"

func RandStr(n int) string {
	rand.Seed(time.Now().UnixNano())
	b := make([]byte, n)
	for i := range b {
		b[i] = letters[rand.Int63()%int64(len(letters))]
	}
	return string(b)
}
func RandNumStr(n int) string {
	rand.Seed(time.Now().UnixNano())
	b := make([]byte, n)
	for i := range b {
		b[i] = numLetters[rand.Int63()%int64(len(letters))]
	}
	return string(b)
}

func ToGeneralArr[T interface{}](arr ...T) []interface{} {
	res := make([]interface{}, len(arr))
	for i := range arr {
		res[i] = arr[i]
	}
	return res
}

func ConvertParamString(args string) string {
	convertString := ""
	if strings.Contains(args, ",") {
		split := strings.Split(args, ",")
		for _, v := range split {
			convertString = convertString + "'" + v + "',"
		}
		return convertString[:len(convertString)-1]
	} else {
		convertString = "'" + args + "'"
		return convertString
	}

}

func ArrayIn(target string, strArray []string) bool {
	sort.Strings(strArray)
	index := sort.SearchStrings(strArray, target)
	if index < len(strArray) && strArray[index] == target {
		return true
	}
	return false
}

func CamelToSnake(camel string) string {
	name := make([]byte, 0, len(camel)+5)
	for i := range camel {
		if camel[i] >= 'A' && camel[i] <= 'Z' {
			if i > 0 {
				name = append(name, ' ')
			}
			name = append(name, camel[i]+32)
		} else {
			name = append(name, camel[i])
		}
	}
	return string(name)
}

func TraceContext(ctx context.Context) context.Context {
	return trace.ContextWithSpan(context.Background(), trace.SpanFromContext(ctx))
}

func TraceIDFromContext(ctx context.Context) string {
	return trace.SpanFromContext(ctx).SpanContext().TraceID().String()
}

func Md5(content string) string {
	has := md5.New()
	has.Write([]byte(content))
	b := has.Sum(nil)
	return hex.EncodeToString(b)
}

func Md5WithSalt(content string, salt string) string {
	return Md5(content + salt)
}

func Md5WithDefaultSalt(content string) string {
	return Md5WithSalt(content, constants.RedactSalt)
}

func Sha256(content string) string {
	bytes2 := sha256.Sum256([]byte(content))
	hashCode2 := hex.EncodeToString(bytes2[:])
	return hashCode2
}

func CompareDiff[T constraints.Ordered](a, b T) map[string]interface{} {
	if a == b {
		return nil
	}
	return map[string]interface{}{
		"a": a,
		"b": b,
	}
}

func IsSliceContain[T constraints.Ordered](source []T, target T) bool {
	for i := range source {
		if target == source[i] {
			return true
		}
	}
	return false
}

func CompareBoolDiff(a, b bool) map[string]interface{} {
	if a == b {
		return nil
	}
	return map[string]interface{}{
		"a": a,
		"b": b,
	}
}

func IsTestUser(email string) bool {
	switch email {
	case "neebiv@snapmail.cc":
		return true
	default:
		return false
	}
}

func ConvertObjToMap(obj interface{}) (map[string]interface{}, error) {
	if obj == nil {
		return nil, nil
	}
	objJson, err := json.Marshal(obj)
	if err != nil {
		return nil, err
	}
	result := make(map[string]interface{})
	if err := json.Unmarshal(objJson, &result); err != nil {
		return nil, err
	}
	return result, nil
}

func GO(f func()) {
	name := GetParentFuncName()
	GOWithName(name, f)
}

func GOWithName(name string, f func()) {
	go func() {
		start := time.Now()
		defer func() {
			end := time.Now()
			cost := end.Sub(start)
			if err := recover(); err != nil {
				buf := make([]byte, 1<<16)
				runtime.Stack(buf, true)
				log.Printf("utils.GOWithName[%s] start_at:%v, end_at:%v, cost:%v, panic with:%v, stack:%s", name, start, end, cost, err, string(buf))
			}
			log.Printf("utils.GOWithName[%s] run completed, start_at:%v, end_at:%v, cost:%v.", name, start, end, cost)
		}()
		log.Printf("utils.GOWithName[%s] start running.", name)
		f()
	}()
}

func NOPanicWrap(name string, f func() error) func() error {
	return func() (err error) {
		start := time.Now()
		defer func() {
			if rerr := recover(); rerr != nil {
				buf := make([]byte, 1<<16)
				runtime.Stack(buf, true)
				log.Printf("utils.NoPanicWrap[%s] panic with:%v, stack:%s", name, rerr, string(buf))
				err = fmt.Errorf("utils.NoPanicWrap[%s] panic with:%v", name, rerr)
			}
			log.Printf("utils.NoPanicWrap[%s] run completed, cost:%v, err:%v.", name, time.Since(start).String(), err)
		}()
		log.Printf("utils.NoPanicWrap[%s] start running.", name)
		return f()
	}
}

// Worker the worker is responsible for executing function f asynchronously.
// If the function f encounters an exception (panic or returns an err that is not nil),
// the worker will reexecute the function f until it actively exits, which means the returned err is nil
func Worker(name string, f func() error) {
	name = "Worker_" + name
	GOWithName(name, func() {
		for {
			if err := NOPanicWrap(name, f)(); err == nil {
				return
			}
		}
	})
}

func Str2Ptr(s string) *string {
	return &s
}

func Bool2Ptr(b bool) *bool {
	return &b
}

func ToJson(a any) string {
	res, _ := json.Marshal(a)
	return string(res)
}

func FromJson(a string, target any) error {
	return json.Unmarshal([]byte(a), target)
}

func FromJsonWithNum(a string, target any) error {
	decoder := json.NewDecoder(bytes.NewBufferString(a))
	decoder.UseNumber()
	return decoder.Decode(target)
}

func Base64Encode(s string) string {
	return base64.StdEncoding.EncodeToString([]byte(s))
}

func GetCurTimestamp() int64 {
	currentTime := time.Now()
	return currentTime.Unix()
}

func GetCurDate() string {
	return GetDiffDate(0)
}
func GetDiffDate(diffDays int64) string {
	return ConvertTimestampToDate(GetCurTimestamp() + diffDays*86400)
}
func GetDiffDateHour(diffDays int64) string {
	return ConvertTimestampToDateHour(GetCurTimestamp() + diffDays*86400)
}

func ConvertDateToTimestamp(dateString string) int64 {
	timeFormat := "20060102"
	timeValue, err := time.Parse(timeFormat, dateString)
	if err != nil {
		return 0
	}
	return timeValue.Unix()
}

func ConvertDateHourToTimestamp(dateString string) int64 {
	timeFormat := "2006010215"
	timeValue, err := time.Parse(timeFormat, dateString)
	if err != nil {
		return 0
	}
	return timeValue.Unix()
}

func ConvertTimestampToDate(timestamp int64) string {
	timeFormat := "20060102"
	timeValue := time.Unix(timestamp, 0)
	return timeValue.Format(timeFormat)
}

func ConvertTimestampToDateHour(timestamp int64) string {
	timeFormat := "2006010215"
	timeValue := time.Unix(timestamp, 0)
	return timeValue.Format(timeFormat)
}

func ConvertToTiError(err error, dft errs.Error) errs.Error {
	if tierr, ok := err.(errs.Error); ok {
		return tierr
	}
	if dft != nil {
		return dft
	}
	return errs.NewInternalGeneralBusiness("Unknown error", err)
}

func IsTimeoutError(err error) bool {
	errMsg := errors.Errorf("%+v", err).Error()
	if strings.Contains(errMsg, "context deadline") || strings.Contains(errMsg, "Client.Timeout") || strings.Contains(errMsg, "i/o timeout") {
		return true
	}
	return false
}

func GetExternalTypeByEmail(email string) string {
	externalType := "external"
	if strings.Contains(email, "pingcap.com") || strings.Contains(email, "snapmail.cc") {
		externalType = "internal"
	}
	return externalType
}

func ConvertArrToMap[T any, K comparable](arr []T, ider func(item T) K) map[K]T {
	res := make(map[K]T, len(arr))
	for _, item := range arr {
		res[ider(item)] = item
	}
	return res
}

func IsNil(x any) bool {
	if x == nil {
		return true
	}
	v := reflect.ValueOf(x)
	switch v.Kind() {
	case reflect.Ptr, reflect.Slice, reflect.Map, reflect.Chan, reflect.Func:
		return v.IsNil()
	default:
		return false
	}
}

func IsNumber(input string) bool {
	return numberReg.MatchString(input)
}

func Max[T constraints.Ordered](x, y T) T {
	if x > y {
		return x
	}
	return y
}

func Min[T constraints.Ordered](x, y T) T {
	if x < y {
		return x
	}
	return y
}

// SanitizeString replaces invalid UTF-8 sequences with the replacement character.
func SanitizeString(s string) string {
	if utf8.ValidString(s) {
		return s
	}

	// Create a buffer to hold the sanitized string
	buf := make([]rune, 0, len(s))

	// Iterate over the string and replace invalid sequences
	for i := 0; i < len(s); {
		r, size := utf8.DecodeRuneInString(s[i:])
		if r == utf8.RuneError && size == 1 {
			// Invalid UTF-8 sequence, replace with the Unicode replacement character
			buf = append(buf, '�')
			i++
		} else {
			buf = append(buf, r)
			i += size
		}
	}

	return string(buf)
}
