package global

import (
	"archive/zip"
	"bytes"
	"crypto/md5"
	"encoding/hex"
	"encoding/json"
	"fmt"
	"io"
	"net"
	"net/http"
	"os"
	"path/filepath"
	"reflect"
	"regexp"
	"sort"
	"strings"
	"time"
)

const (
	IsIPv4 = "IPv4"
	IsIPv6 = "IPv6"
	NonIP  = "NonIp"
)

type em struct{}

func GetPkgName() string {
	return reflect.TypeOf(em{}).PkgPath()
}

// interface{} change to map[string]interface{}
// interface{} data is []interface{}
func ListToMap(list interface{}, key string) map[string]interface{} {
	res := make(map[string]interface{})
	arr := ToSlice(list)
	for _, row := range arr {
		immutable := reflect.ValueOf(row)
		val := immutable.FieldByName(key).String()
		res[val] = row
	}
	return res
}

// interface{} change to []interface{}
func ToSlice(arr interface{}) []interface{} {
	ret := make([]interface{}, 0)
	v := reflect.ValueOf(arr)
	if v.Kind() != reflect.Slice {
		ret = append(ret, arr)
		return ret
	}
	l := v.Len()
	for i := 0; i < l; i++ {
		ret = append(ret, v.Index(i).Interface())
	}
	return ret
}

var TodoList []Todo

type Todo struct {
	Id   int64
	Item string
}

// JSON序列化方式
func jsonStructToMap(TodoList Todo) (map[string]interface{}, error) {
	// 结构体转json
	strRet, err := json.Marshal(TodoList)
	if err != nil {
		return nil, err
	}
	// json转map
	var mRet map[string]interface{}
	err1 := json.Unmarshal(strRet, &mRet)
	if err1 != nil {
		return nil, err1
	}
	return mRet, nil
}

func IsContain(item string, items []string) bool {
	for _, e := range items {
		if e == item {
			return true
		}
	}
	return false
}

func IsContainP(item string, items *[]string, size int) bool {
	for i := 0; i < size; i++ {
		if (*items)[i] == item {
			return true
		}
	}
	return false
}

// 将字符串 分割成 字符串数组
// @s：分割符
func SplitString(str string, s string) []string {
	sa := strings.Split(str, s)
	return sa
}

// 　合并字符串数组
func MergeStringArr(a, b []string) []string {
	var arr []string
	for _, i := range a {
		arr = append(arr, i)
	}
	for _, j := range b {
		arr = append(arr, j)
	}
	return arr
}

// 数组去重
func UniqueStringArr(m []string) []string {
	d := make([]string, 0)
	tempMap := make(map[string]bool, len(m))
	for _, v := range m { // 以值作为键名
		if tempMap[v] == false {
			tempMap[v] = true
			d = append(d, v)
		}
	}
	return d
}

// 　合并整型数组
func MergeArr(a, b []int) []int {
	var arr []int
	for _, i := range a {
		arr = append(arr, i)
	}
	for _, j := range b {
		arr = append(arr, j)
	}
	return arr
}

// 数组去重
func UniqueArr(m []int) []int {
	d := make([]int, 0)
	tempMap := make(map[int]bool, len(m))
	for _, v := range m { // 以值作为键名
		if tempMap[v] == false {
			tempMap[v] = true
			d = append(d, v)
		}
	}
	return d
}

// 升序
func AscArr(e []int) []int {
	sort.Ints(e[:])
	return e
}

// 降序
func DescArr(e []int) []int {
	sort.Sort(sort.Reverse(sort.IntSlice(e)))
	return e
}

func MatchRmUID(p string, s string) bool {
	match, _ := regexp.MatchString(p, s)
	return match
}

type OrderedMap struct {
	Order []string
	Map   map[string]interface{}
}

func (om *OrderedMap) UnmarshalJson(b []byte) error {
	json.Unmarshal(b, &om.Map)

	index := make(map[string]int)
	for key := range om.Map {
		om.Order = append(om.Order, key)
		esc, _ := json.Marshal(key) //Escape the key
		index[key] = bytes.Index(b, esc)
	}

	sort.Slice(om.Order, func(i, j int) bool { return index[om.Order[i]] < index[om.Order[j]] })
	return nil
}

func (om OrderedMap) MarshalJson() ([]byte, error) {
	var b []byte
	buf := bytes.NewBuffer(b)
	buf.WriteRune('{')
	l := len(om.Order)
	for i, key := range om.Order {
		km, err := json.Marshal(key)
		if err != nil {
			return nil, err
		}
		buf.Write(km)
		buf.WriteRune(':')
		vm, err := json.Marshal(om.Map[key])
		if err != nil {
			return nil, err
		}
		buf.Write(vm)
		if i != l-1 {
			buf.WriteRune(',')
		}
		fmt.Println(buf.String())
	}
	buf.WriteRune('}')
	fmt.Println(buf.String())
	return buf.Bytes(), nil
}

func GetBodyCopy(r *http.Request) (*bytes.Buffer, error) {
	// If r.bodyBuf present, return the copy
	//	if r.bodyBuf != nil {
	//		return bytes.NewBuffer(r.bodyBuf.Bytes()), nil
	//	}

	// Maybe body is `io.Reader`.
	// Note: Resty user have to watchout for large body size of `io.Reader`
	if r.Body != nil {
		b, err := io.ReadAll(r.Body)
		if err != nil {
			return nil, err
		}

		// Restore the Body
		//		close(r.Body)
		r.Body = io.NopCloser(bytes.NewReader(b))

		// Return the Body bytes
		return bytes.NewBuffer(b), nil
	}
	return nil, nil
}

func UnmarshalBody(r *http.Request, v *interface{}, maxLen int64) error {
	body, err := io.ReadAll(io.LimitReader(r.Body, maxLen))
	if err != nil {
		return err
	}

	return json.Unmarshal(body, v)
}

func SetNotifyUrl(ip string, port uint16, uri string) string {
	return fmt.Sprintf("http://%s:%d%s", ip, port, uri)
}

func GetIps() (ips []string, err error) {
	interfaceAddr, err := net.InterfaceAddrs()
	if err != nil {
		return ips, err
	}

	for _, address := range interfaceAddr {
		ipNet, isVailIpNet := address.(*net.IPNet)
		// 检查ip地址判断是否回环地址
		if isVailIpNet && !ipNet.IP.IsLoopback() {
			if ipNet.IP.To4() != nil {
				ips = append(ips, ipNet.IP.String())
			}
		}
	}
	return ips, nil
}

func GetCurrentTimeSliceIndexByPeriod(t time.Time, period int) int {
	index := int((t.Hour()*60+t.Minute())/period) - 1
	if index < 0 {
		return int(24*60/period) - 1
	}
	return index
}

var (
	cst *time.Location
)

// RFC3339ToDateTime convert rfc3339 value to china standard time layout
func RFC3339ToDateTime(value string) (string, error) {
	ts, err := time.Parse(time.RFC3339, value)
	if err != nil {
		return "", err
	}

	return ts.In(cst).Format("2006-01-02 15:04:05"), nil
}

// CreateTimeDir 根据当前时间格式来创建文件夹
func CreateTimeDir(fmt string, path string) string {
	folderName := time.Now().Format(fmt)
	folderPath := filepath.Join(path, folderName)
	if _, err := os.Stat(folderPath); os.IsNotExist(err) {
		// 必须分成两步：先创建文件夹、再修改权限
		os.Mkdir(folderPath, 0664) //0644也可以os.ModePerm
		os.Chmod(folderPath, 0664)
	}
	return folderPath
}

// CreateDir 根据传入的目录名和路径来创建文件夹
func CreateDir(folderName string, path string) string {
	folderPath := filepath.Join(path, folderName)
	if _, err := os.Stat(folderPath); os.IsNotExist(err) {
		// 必须分成两步：先创建文件夹、再修改权限
		os.MkdirAll(folderPath, 0664) //0644也可以os.ModePerm
		os.Chmod(folderPath, 0664)
	}
	return folderPath
}

func GetFmtTimeString(srcFmt string, timeString string, dstFmt string) string {
	t, _ := time.ParseInLocation(srcFmt, timeString, time.Local)
	return t.Format(dstFmt)
}

func GetFileMD5Sum(filePath string) (string, error) {
	file, err := os.Open(filePath)
	if err != nil {
		return "", err
	}
	defer file.Close()
	md5 := md5.New()
	_, err = io.Copy(md5, file)
	if err != nil {
		return "", err
	}

	md5str := hex.EncodeToString(md5.Sum(nil))

	return md5str, nil
}

// PathExists check path is exist or no
func PathExists(path string) (bool, error) {
	_, err := os.Stat(path)
	if err == nil { //文件或者目录存在
		return true, nil
	}
	if os.IsNotExist(err) {
		return false, nil
	}
	return false, err
}

// PathExists check path is exist or no
func FilePathExists(filePath string) (bool, error) {
	_, err := os.Stat(filePath)
	if err == nil { //文件或者目录存在
		return true, nil
	}
	if os.IsNotExist(err) {
		return false, nil
	}
	return false, err
}

func GetDayDuration(d1, d2 string) int64 {
	a, _ := time.Parse("2006-01-02", d1)
	b, _ := time.Parse("2006-01-02", d2)
	d := a.Sub(b)

	return (int64)(d.Hours() / 24)
}

func GetSecondsSinceDatetime(datetimeStr string) (int64, error) {
	loc1, _ := time.LoadLocation("Local")
	// 解析日期时间字符串为时间对象
	datetime, err := time.ParseInLocation(time.DateTime, datetimeStr, loc1)
	if err != nil {
		return 0, err
	}

	// 计算时间差
	duration := time.Since(datetime)

	// 获取时间差的秒数
	seconds := int64(duration.Seconds())

	return seconds, nil
}

func GetSecondDuration(time1, time2 string) (int64, error) {
	loc1, _ := time.LoadLocation("Local")
	// 解析日期时间字符串为时间对象
	t1, err := time.ParseInLocation(time.DateTime, time1, loc1)
	if err != nil {
		return 0, err
	}
	t2, err := time.ParseInLocation(time.DateTime, time2, loc1)
	if err != nil {
		return 0, err
	}

	// 计算时间差
	duration := t2.Sub(t1)

	// 获取时间差的秒数
	seconds := int64(duration.Seconds())

	return seconds, nil
}

// 0: invalid ip
// 4: IPv4
// 6: IPv6
func ParseIP(s string) (net.IP, int) {
	ip := net.ParseIP(s)
	if ip == nil {
		return nil, 0
	}
	for i := 0; i < len(s); i++ {
		switch s[i] {
		case '.':
			return ip, 4
		case ':':
			return ip, 6
		}
	}
	return nil, 0
}

func BytesCombine1(pBytes ...[]byte) []byte {
	return bytes.Join(pBytes, []byte(""))
}

func BytesCombine(pBytes ...[]byte) []byte {
	length := len(pBytes)
	s := make([][]byte, length)
	for index := 0; index < length; index++ {
		s[index] = pBytes[index]
	}
	sep := []byte("")
	return bytes.Join(s, sep)
}

func ParseIPAddr(ip string) string {
	ipAddr := net.ParseIP(ip)

	if ipAddr != nil {
		if ipAddr.To4() != nil {
			return IsIPv4
		} else {
			return IsIPv6
		}
	}

	return NonIP
}

func CombineHostUri(ip string, port string) string {
	var hostUri string = ""
	ipType := ParseIPAddr(ip)
	if ipType == IsIPv4 {
		hostUri = fmt.Sprintf("http://%s:%v", ip, port)
	} else {
		hostUri = fmt.Sprintf("http://[%s]:%v", ip, port)
	}

	return hostUri
}

func StructToMap(obj interface{}) map[string]interface{} {
	objValue := reflect.ValueOf(obj)
	objType := objValue.Type()

	m := make(map[string]interface{})

	for i := 0; i < objValue.NumField(); i++ {
		field := objValue.Field(i)
		fieldName := objType.Field(i).Name

		m[fieldName] = field.Interface()
	}

	return m
}

// ToMap 结构体转为Map[string]interface{}
func ToMap(in interface{}, tagName string) (map[string]interface{}, error) {
	out := make(map[string]interface{})

	v := reflect.ValueOf(in)
	if v.Kind() == reflect.Ptr {
		v = v.Elem()
	}

	if v.Kind() != reflect.Struct { // 非结构体返回错误提示
		return nil, fmt.Errorf("ToMap only accepts struct or struct pointer; got %T", v)
	}

	t := v.Type()
	// 遍历结构体字段
	// 指定tagName值为map中key;字段值为map中value
	for i := 0; i < v.NumField(); i++ {
		fi := t.Field(i)
		if tagValue := fi.Tag.Get(tagName); tagValue != "" {
			out[tagValue] = v.Field(i).Interface()
		}
	}
	return out, nil
}

func ZipOneFile(srcFile, dstZip string, pathFlag bool) error {
	zipFile, err := os.Create(dstZip)
	if err != nil {
		return err
	}
	defer zipFile.Close()

	zipWriter := zip.NewWriter(zipFile)
	defer zipWriter.Close()

	fileToCompress, err := os.Open(srcFile)
	if err != nil {
		return err
	}
	defer fileToCompress.Close()

	var fileInZip io.Writer
	if pathFlag {
		fileInZip, err = zipWriter.Create(srcFile)
		if err != nil {
			return err
		}
	} else {
		// 获取文件的基本名称
		fileName := filepath.Base(fileToCompress.Name())
		fileInZip, err = zipWriter.Create(fileName)
		if err != nil {
			return err
		}
	}

	_, err = io.Copy(fileInZip, fileToCompress)
	if err != nil {
		return err
	}
	return nil
}

func ZipDirectoryFile(srcDir, dstZip string) error {
	// Create a new zip file
	zipfileWriter, err := os.Create(dstZip)
	if err != nil {
		return err
	}
	defer zipfileWriter.Close()

	// Create a new zip archive
	zipWriter := zip.NewWriter(zipfileWriter)
	defer zipWriter.Close()

	// Walk through the directory and add files to the zip archive
	err = filepath.Walk(srcDir, func(path string, info os.FileInfo, err error) error {
		if err != nil {
			return err
		}

		// Create a new file header for the current file
		header, err := zip.FileInfoHeader(info)
		if err != nil {
			return err
		}

		// Set the name of the file within the zip archive
		header.Name = filepath.Join(filepath.Base(srcDir), path[len(srcDir):])

		// If the current file is a directory, skip it
		if info.IsDir() {
			return nil
		}

		// Create a new file in the zip archive
		fileWriter, err := zipWriter.CreateHeader(header)
		if err != nil {
			return err
		}

		// Open the current file
		file, err := os.Open(path)
		if err != nil {
			return err
		}
		defer file.Close()

		// Copy the contents of the current file to the zip archive
		_, err = io.Copy(fileWriter, file)
		if err != nil {
			return err
		}

		return nil
	})

	return err
}

// 判断软件包是rpm或者deb, 1:rpm, 2:deb, 0:unknown format
func JudgeRpmOrDebPackage(filePath string) (int, error) {
	var fileType int = 0
	file, err := os.Open(filePath)
	if err != nil {
		return fileType, err
	}
	defer file.Close()

	// Read the first 6 bytes of the file
	header := make([]byte, 6)
	_, err = file.Read(header)
	if err != nil {
		return fileType, err
	}

	// Check the magic numbers to determine the package format
	if string(header) == "!<arch>" {
		fileType = 1
	} else if string(header) == "!<arch\n" || string(header) == "!<arch\r" {
		fileType = 2
	} else {
		fileType = 0
	}

	return fileType, nil
}

func isRpmPackage(file *os.File) bool {
	// RPM packages start with the magic number "EDABEEDB"
	magic := []byte{0xED, 0xAB, 0xEE, 0xDB}
	buffer := make([]byte, len(magic))

	_, err := file.Read(buffer)
	if err != nil && err != io.EOF {
		return false
	}

	return string(buffer) == string(magic)
}

func isDebPackage(file *os.File) bool {
	// DEB packages start with the magic number "!<arch>\n"
	magic := []byte("!<arch>\n")
	buffer := make([]byte, len(magic))

	_, err := file.Read(buffer)
	if err != nil && err != io.EOF {
		return false
	}

	return string(buffer) == string(magic)
}

func CheckRpmOrDebPackage(filePath string) (int, error) {
	var fileType int = 0
	file, err := os.Open(filePath)
	if err != nil {
		return fileType, err
	}
	defer file.Close()

	isRpm := isRpmPackage(file)
	isDeb := isDebPackage(file)

	if isRpm {
		fileType = 1
	} else if isDeb {
		fileType = 2
	} else {
		fileType = 0
	}

	return fileType, nil
}

func IsRpmOrDebPackage(filePath string) int {
	var fileType int = 0

	if strings.Contains(filePath, ".rpm") {
		fileType = 1
	} else if strings.Contains(filePath, ".deb") {
		fileType = 2
	} else {
		fileType = 0
	}

	return fileType
}
