package utils

import (
	"fmt"
	"github.com/goccy/go-json"
	"regexp"
	"strconv"
	"strings"
	"time"
)

func GetRandomInt(min int, max int) int {
	return Intn(max-min) + min
}

func GenerateCode(length int) string {
	seq := IntnSeq(10, length)

	var code string
	for i := 0; i < length; i++ {
		code += strconv.Itoa(seq[i])
	}
	return code
}

func GenerateChar(length int) string {
	const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
	seq := IntnSeq(len(charset), length)

	result := make([]byte, length)
	for i := 0; i < length; i++ {
		result[i] = charset[seq[i]]
	}
	return string(result)
}

func ConvertTime(t []uint8) *time.Time {
	val, err := time.Parse("2006-01-02 15:04:05", string(t))
	if err != nil {
		return nil
	}
	return &val
}

func Unmarshal[T interface{}](data []byte) (form T, err error) {
	err = json.Unmarshal(data, &form)
	return form, err
}

func UnmarshalString[T interface{}](data string) (form T, err error) {
	err = json.Unmarshal([]byte(data), &form)
	return form, err
}

func UnmarshalForm[T interface{}](data string) *T {
	form, err := UnmarshalString[T](data)
	if err != nil {
		return nil
	}
	return &form
}

func Marshal[T interface{}](data T) string {
	res, err := json.Marshal(data)
	if err != nil {
		return ""
	}
	return string(res)
}

func MarshalWithIndent[T interface{}](data T, length ...int) string {
	var indent string
	if len(length) > 0 {
		indent = strings.Repeat(" ", length[0])
	} else {
		indent = "  "
	}

	res, err := json.MarshalIndent(data, "", indent)
	if err != nil {
		return ""
	}
	return string(res)
}

func MapToStruct[T any](data interface{}) *T {
	val := Marshal(data)
	if form, err := Unmarshal[T]([]byte(val)); err == nil {
		return &form
	} else {
		return nil
	}
}

func MapToRawStruct[T any](data interface{}) (*T, error) {
	val, err := json.Marshal(data)
	if err != nil {
		return nil, err
	}

	form, err := Unmarshal[T](val)
	if err != nil {
		return nil, err
	}

	return &form, nil
}

func ParseInt(value string) int {
	if res, err := strconv.Atoi(value); err == nil {
		return res
	} else {
		return 0
	}
}

func ParseInt64(value string) int64 {
	if res, err := strconv.ParseInt(value, 10, 64); err == nil {
		return res
	} else {
		return 0
	}
}

func ParseFloat32(value string) float32 {
	if res, err := strconv.ParseFloat(value, 32); err == nil {
		return float32(res)
	} else {
		return 0
	}
}

func ParseBool(value string) bool {
	if res, err := strconv.ParseBool(value); err == nil {
		return res
	} else {
		return false
	}
}

func ConvertSqlTime(t time.Time) string {
	return t.Format("2006-01-02 15:04:05")
}

func GetImageMarkdown(url string) string {
	return fmt.Sprintf("![image](%s)", url)
}

// SplitItem is the split function for strings.Split
// e.g.
// SplitItem("a,b,c", ",") => ["a,", "b,", "c"]
func SplitItem(data string, sep string) []string {
	if data == "" {
		return []string{}
	}

	result := strings.Split(data, sep)
	length := len(result)
	for i, item := range result {
		if i == length-1 {
			break
		}
		result[i] = item + sep
	}
	return result
}

func SplitItems(data string, seps []string) []string {
	if len(seps) == 0 {
		return []string{}
	}

	result := []string{data}
	for _, sep := range seps {
		var temp []string
		for _, item := range result {
			temp = append(temp, SplitItem(item, sep)...)
		}
		result = temp
	}
	return result
}

func SplitLangItems(data string) []string {
	return SplitItems(data, []string{",", "，", " ", "\n"})
}

func Extract(data string, length int, flow_ ...string) string {
	flow := ""
	if len(flow_) > 0 {
		flow = flow_[0]
	}

	value := []rune(data)
	if len(value) > length {
		return string(value[:length]) + flow
	} else {
		return data
	}
}

func ExtractUrls(data string) []string {
	re := regexp.MustCompile(`(https?://\S+)`)
	return re.FindAllString(data, -1)
}

func ExtractImages(data string, includeBase64 bool) (content string, images []string) {
	ext := ExtractExternalImages(data)
	if includeBase64 {
		images = append(ext, ExtractBase64Images(data)...)
	} else {
		images = ext
	}

	content = data
	for _, image := range images {
		content = strings.ReplaceAll(content, image, "")
	}

	return content, images
}

func ExtractBase64Images(data string) []string {
	// get base64 images from data (data:image/png;base64,xxxxxx) (\n \\n [space] \\t \\r \\v \\f break the base64 string)
	re := regexp.MustCompile(`(data:image/\w+;base64,[\w+/=]+)`)
	return re.FindAllString(data, -1)
}

func ExtractExternalImages(data string) []string {
	// https://platform.openai.com/docs/guides/vision/what-type-of-files-can-i-upload

	re := regexp.MustCompile(`(https?://\S+\.(?:png|jpg|jpeg|gif|webp|heif|heic)(?:\s\S+)?)`)
	return re.FindAllString(data, -1)
}

func ContainUnicode(data string) bool {
	// like `hi\\u2019s` => true
	re := regexp.MustCompile(`\\u([0-9a-fA-F]{4})`)
	return re.MatchString(data)
}

func DecodeUnicode(data string) string {
	// like `hi\\u2019s` => `hi's`
	re := regexp.MustCompile(`\\u([0-9a-fA-F]{4})`)
	return re.ReplaceAllStringFunc(data, func(s string) string {
		unicode, err := strconv.ParseInt(s[2:], 16, 32)
		if err != nil {
			return s
		}

		return string(rune(unicode))
	})
}

func EscapeChar(data string) string {
	// like `\\n` => `\n`, `\\t` => `\t` and so on
	re := regexp.MustCompile(`\\([nrtvfb])`)

	mapper := map[string]string{
		"n": "\n",
		"r": "\r",
		"t": "\t",
		"v": "\v",
		"f": "\f",
		"b": "\b",
	}

	return re.ReplaceAllStringFunc(data, func(s string) string {
		return mapper[s[1:]]
	})
}

func ProcessRobustnessChar(data string) string {
	// like `hi\\u2019s` => `hi's`
	if ContainUnicode(data) {
		data = DecodeUnicode(data)
	}

	// like `\\n` => `\n`, `\\t` => `\t` and so on
	return EscapeChar(data)
}

func SortString(arr []string) []string {
	// sort string array by first char
	// e.g. ["a", "b", "c", "ab", "ac", "bc"] => ["a", "ab", "ac", "b", "bc", "c"]

	if len(arr) <= 1 {
		return arr
	}

	var result []string
	var temp []string
	var first string

	for _, item := range arr {
		if first == "" {
			first = item
			continue
		}

		if strings.HasPrefix(item, first) {
			temp = append(temp, item)
		} else {
			result = append(result, first)
			result = append(result, SortString(temp)...)
			first = item
			temp = []string{}
		}
	}

	if len(temp) > 0 {
		result = append(result, first)
		result = append(result, SortString(temp)...)
	} else {
		result = append(result, first)
	}

	return result
}

func SafeSplit(data string, sep string, seglen int) (res []string) {
	// split string by sep, and each segment has seglen length
	// e.g. SafeSplit("abc,def,ghi", ",", 2) => ["abc", "def,ghi"]

	if data == "" {
		for i := 0; i < seglen; i++ {
			res = append(res, "")
		}
		return res
	}

	arr := strings.Split(data, sep)
	length := len(arr)

	if length == seglen {
		return arr
	}

	if length < seglen {
		for i := 0; i < seglen-length; i++ {
			arr = append(arr, "")
		}
		return arr
	} else {
		offset := length - seglen
		for i := 0; i < offset; i++ {
			arr[seglen-1] += sep + arr[seglen+i]
		}
		return arr[:seglen]
	}
}

func ToSecret(raw string) string {
	// like `axVbeixvN` => `axVb*****`

	data := []rune(raw)
	length := len(data)

	if length < 4 {
		return "****"
	} else {
		suffix := len(data) - 4
		return string(data[:4]) + strings.Repeat("*", suffix)
	}
}

func ToMarkdownCode(lang string, code string) string {
	return fmt.Sprintf("```%s\n%s\n```", lang, code)
}

func ToMarkdownError(err error, body string) error {
	return fmt.Errorf("%s\n%s", err.Error(), ToMarkdownCode("html", body))
}
