package tencentyun

import (
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	"git.hilo.cn/hilo-common/domain"
	"git.hilo.cn/hilo-common/mylogrus"
	"git.hilo.cn/hilo-common/resource/config"
	"git.hilo.cn/hilo-common/utils"
	"github.com/sirupsen/logrus"
	"github.com/tencentyun/tls-sig-api-v2-golang/tencentyun"
	"io/ioutil"
	"math/rand"
	"net/http"
	"strconv"
	"strings"
	"time"
)

const (
	timUrl     = "https://console.tim.qq.com/v4"
	overseaUrl = "https://adminapiger.im.qcloud.com/v4"

	loginUrl        = timUrl + "/im_open_login_svc/account_import?sdkappid={appid}&identifier={externalId}&usersig={userSin}&random={random}&contenttype=json"
	overseaLoginUrl = overseaUrl + "/im_open_login_svc/account_import?sdkappid={appid}&identifier={externalId}&usersig={userSin}&random={random}&contenttype=json"

	blackAddUrl         = timUrl + "/sns/black_list_add?sdkappid={appid}&identifier={externalId}&usersig={userSin}&random={random}&contenttype=json"
	oversearBlackAddUrl = overseaUrl + "/sns/black_list_add?sdkappid={appid}&identifier={externalId}&usersig={userSin}&random={random}&contenttype=json"

	blackCancelUrl        = timUrl + "/sns/black_list_delete?sdkappid={appid}&identifier={externalId}&usersig={userSin}&random={random}&contenttype=json"
	overseaBlackCancelUrl = overseaUrl + "/sns/black_list_delete?sdkappid={appid}&identifier={externalId}&usersig={userSin}&random={random}&contenttype=json"

	blackListGetUrl        = timUrl + "/sns/black_list_get?sdkappid={appid}&identifier={externalId}&usersig={userSin}&random={random}&contenttype=json"
	overseaBlackListGetUrl = overseaUrl + "/sns/black_list_get?sdkappid={appid}&identifier={externalId}&usersig={userSin}&random={random}&contenttype=json"

	blackListCheck        = timUrl + "/sns/black_list_check?sdkappid={appid}&identifier={externalId}&usersig={userSin}&random={random}&contenttype=json"
	overseaBlackListCheck = overseaUrl + "/sns/black_list_check?sdkappid={appid}&identifier={externalId}&usersig={userSin}&random={random}&contenttype=json"

	msgReadUrl        = timUrl + "/openim/admin_set_msg_read?sdkappid={appid}&identifier={externalId}&usersig={userSin}&random={random}&contenttype=json"
	overseaMsgReadUrl = overseaUrl + "/openim/admin_set_msg_read?sdkappid={appid}&identifier={externalId}&usersig={userSin}&random={random}&contenttype=json"

	basicParameters = "sdkappid={appid}&identifier=administrator&usersig={userSig}&random={random}&contenttype=json"

	// 账号相关
	// fixme: 哪边为准呢?
	queryStateUrl        = timUrl + "/openim/query_online_status?" + basicParameters
	overseaQueryStateUrl = overseaUrl + "/openim/query_online_status?" + basicParameters

	deleteAccountUrl = timUrl + "/im_open_login_svc/account_delete?" + basicParameters

	// 用户资料
	// fixme: 哪边为准呢?
	getProfileUrl        = timUrl + "/profile/portrait_get?" + basicParameters
	overseaGetProfileUrl = overseaUrl + "/profile/portrait_get?" + basicParameters

	setProfileUrl        = timUrl + "/profile/portrait_set?" + basicParameters
	overseaSetProfileUrl = overseaUrl + "/profile/portrait_set?" + basicParameters

	// 群组相关
	getAllGroupList        = timUrl + "/group_open_http_svc/get_appid_group_list?" + basicParameters
	overseaGetAllGroupList = overseaUrl + "/group_open_http_svc/get_appid_group_list?" + basicParameters

	createGroupUrl        = timUrl + "/group_open_http_svc/create_group?" + basicParameters
	overseaCreateGroupUrl = overseaUrl + "/group_open_http_svc/create_group?" + basicParameters

	destroyGroupUrl           = timUrl + "/group_open_http_svc/destroy_group?" + basicParameters
	overseaGetDestroyGroupUrl = overseaUrl + "/group_open_http_svc/destroy_group?" + basicParameters

	getGroupInfoUrl        = timUrl + "/group_open_http_svc/get_group_info?" + basicParameters
	overseaGetGroupInfoUrl = overseaUrl + "/group_open_http_svc/get_group_info?" + basicParameters

	joinGroupUrl      = timUrl + "/group_open_http_svc/add_group_member?" + basicParameters
	leaveGroupUrl     = timUrl + "/group_open_http_svc/delete_group_member?" + basicParameters
	getJoinedGroupUrl = timUrl + "/group_open_http_svc/get_joined_group_list?" + basicParameters

	modifyGroupInfoUrl        = timUrl + "/group_open_http_svc/modify_group_base_info?" + basicParameters
	overseaModifyGroupInfoUrl = overseaUrl + "/group_open_http_svc/modify_group_base_info?" + basicParameters

	getGroupMemberUrl        = timUrl + "/group_open_http_svc/get_group_member_info?" + basicParameters
	overseaGetGroupMemberUrl = overseaUrl + "/group_open_http_svc/get_group_member_info?" + basicParameters

	sendNormalMsgUrl        = timUrl + "/group_open_http_svc/send_group_msg?" + basicParameters
	overseaSendNormalMsgUrl = overseaUrl + "/group_open_http_svc/send_group_msg?" + basicParameters

	sendSystemMsgUrl        = timUrl + "/group_open_http_svc/send_group_system_notification?" + basicParameters
	overseaSendSystemMsgUrl = overseaUrl + "/group_open_http_svc/send_group_system_notification?" + basicParameters

	// -------- 单聊相关 --------
	c2cSendMsg        = timUrl + "/openim/sendmsg?" + basicParameters     // 单聊
	overseaC2cSendMsg = overseaUrl + "/openim/sendmsg?" + basicParameters // 单聊

	c2cBatchSendMsg        = timUrl + "/openim/batchsendmsg?" + basicParameters     // 群发单聊
	overseaC2cBatchSendMsg = overseaUrl + "/openim/batchsendmsg?" + basicParameters // 群发单聊

	c2cGetMsg        = timUrl + "/openim/admin_getroammsg?" + basicParameters     // 查询聊天记录
	overseaC2cGetMsg = overseaUrl + "/openim/admin_getroammsg?" + basicParameters // 查询聊天记录

	// -------- 设置用户属性 --------
	setAttrUrl        = timUrl + "/all_member_push/im_set_attr?" + basicParameters
	overseaSetAttrUrl = overseaUrl + "/all_member_push/im_set_attr?" + basicParameters

	// -------- 会话 --------
	recentcontactdDleteMsg = timUrl + "/recentcontact/delete?" + basicParameters //删除
)

func GenSig(externalId string) (string, error) {
	return tencentyun.GenSig(config.GetTencentyunAppId(), config.GetTencentyunKey(), externalId, 86400*30)
}

func getAdminSig() (string, error) {
	return tencentyun.GenSig(config.GetTencentyunAppId(), config.GetTencentyunKey(), "administrator", 86400*180)
}

func GenOverseaSig(externalId string) (string, error) {
	return tencentyun.GenSig(config.GetTxOverSeaAppId(), config.GetTxOverSeaAppKey(), externalId, 86400*30)
}

func getOverseaAdminSig() (string, error) {
	return tencentyun.GenSig(config.GetTxOverSeaAppId(), config.GetTxOverSeaAppKey(), "administrator", 86400*180)
}

func UserRegister(externalId string, nick string, avatar string) error {
	if config.AppIsRelease() {
		return UserRegisterBy(externalId, nick, avatar, overseaLoginUrl,
			func() (string, error) {
				return getOverseaAdminSig()
			}, config.GetTxOverSeaAppId())
	} else {
		return UserRegisterBy(externalId, nick, avatar, loginUrl,
			func() (string, error) {
				return getAdminSig()
			}, config.GetTencentyunAppId())
	}
}

func UserRegisterBy(externalId string, nick string, avatar string, reqUrl string, getAdminSig func() (string, error), appId int) error {
	type BodyStruct struct {
		Identifier string
		Nick       string
		FaceUrl    string
	}

	type RespStruct struct {
		ActionStatus string
		ErrorInfo    string
		ErrorCode    int
	}

	sig, err := getAdminSig()
	if err != nil {
		return err
	}
	//url := strings.Replace(strings.Replace(strings.Replace(strings.Replace(loginUrl, "{appid}", strconv.Itoa(config.GetTencentyunAppId()), 1), "{externalId}", "administrator", 1), "{userSin}", sig, 1), "{random}", strconv.Itoa(rand.Intn(10000)), 1)
	url := strings.Replace(strings.Replace(strings.Replace(strings.Replace(reqUrl, "{appid}", strconv.Itoa(appId), 1), "{externalId}", "administrator", 1), "{userSin}", sig, 1), "{random}", strconv.Itoa(rand.Intn(10000)), 1)
	body := BodyStruct{
		Identifier: externalId,
		Nick:       nick,
		FaceUrl:    avatar,
	}
	mylogrus.MyLog.Infof("url: %s, body: %+v", url, body)

	jsonStr, err := json.Marshal(body)
	if err != nil {
		return err
	}

	resp, err := http.Post(url, "application/json", bytes.NewBuffer(jsonStr))
	if err != nil {
		return err
	}
	defer resp.Body.Close()

	result, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return err
	}

	respStruct := RespStruct{}
	if err := json.Unmarshal(result, &respStruct); err != nil {
		return err
	}
	mylogrus.MyLog.Infof("body: %+v, resp:%+v", body, respStruct)

	if respStruct.ErrorCode == 0 {
		return nil
	} else {
		return fmt.Errorf("tencentyun UserRegister resp ErrorCode:%v, ErrorInfo:%v", respStruct.ErrorCode, respStruct.ErrorInfo)
	}
}

//测试用
func BlackCheckList(externalId string, beBlockExternalIds []string) error {
	type BodyStruct struct {
		From_Account string
		To_Account   []string
		CheckType    string
	}

	type Item struct {
		To_Account string
		Relation   string
		ResultCode int
		ResultInfo string
	}

	type RespStruct struct {
		BlackListCheckItem []Item
		Fail_Account       []string
		ActionStatus       string
		ErrorCode          int
		ErrorInfo          string
		ErrorDisplay       string
	}

	sig, err := getOverseaAdminSig()
	if err != nil {
		return err
	}
	url := strings.Replace(strings.Replace(strings.Replace(strings.Replace(overseaBlackListCheck,
		"{appid}", strconv.Itoa(config.GetTxOverSeaAppId()), 1), "{externalId}", "administrator", 1), "{userSin}", sig, 1), "{random}", strconv.Itoa(rand.Intn(10000)), 1)
	body := BodyStruct{
		From_Account: externalId,
		To_Account:   beBlockExternalIds,
		CheckType:    "BlackCheckResult_Type_Single",
	}

	jsonStr, err := json.Marshal(body)
	if err != nil {
		return err
	}

	resp, err := http.Post(url, "application/json", bytes.NewBuffer(jsonStr))
	if err != nil {
		return err
	}
	defer resp.Body.Close()

	result, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return err
	}

	respStruct := RespStruct{}
	if err := json.Unmarshal(result, &respStruct); err != nil {
		return err
	}
	if respStruct.ErrorCode == 0 {
		mylogrus.MyLog.Infof("tencentyun BlackCheckList resp data:%v", respStruct)
		return nil
	} else {
		return fmt.Errorf(fmt.Sprintf("tencentyun BlackCheckList resp data:%v, ErrorCode:%v, ErrorInfo:%v", respStruct, respStruct.ErrorCode, respStruct.ErrorInfo))
	}
}

func BlackCancel(externalId string, beBlockExternalId string) error {
	if config.AppIsRelease() {
		return BlackCancelBy(externalId, beBlockExternalId, overseaBlackCancelUrl, func() (string, error) {
			return getOverseaAdminSig()
		}, config.GetTxOverSeaAppId())
	} else {
		return BlackCancelBy(externalId, beBlockExternalId, blackCancelUrl, func() (string, error) {
			return getAdminSig()
		}, config.GetTencentyunAppId())
	}
}

func BlackCancelBy(externalId string, beBlockExternalId string, reqUrl string, getAdminSig func() (string, error), appId int) error {
	type BodyStruct struct {
		From_Account string
		To_Account   []string
	}
	//保留部分,因为接口只提供了一个,没有抽象返回值,因为接口没几个。
	type RespStruct struct {
		ActionStatus string
		ErrorInfo    string
		ErrorCode    int
	}

	sig, err := getAdminSig()
	if err != nil {
		return err
	}
	//url := strings.Replace(strings.Replace(strings.Replace(strings.Replace(blackCancelUrl, "{appid}", strconv.Itoa(config.GetTencentyunAppId()), 1), "{externalId}", "administrator", 1), "{userSin}", sig, 1), "{random}", strconv.Itoa(rand.Intn(10000)), 1)
	url := strings.Replace(strings.Replace(strings.Replace(strings.Replace(reqUrl, "{appid}", strconv.Itoa(appId), 1), "{externalId}", "administrator", 1), "{userSin}", sig, 1), "{random}", strconv.Itoa(rand.Intn(10000)), 1)
	body := BodyStruct{
		From_Account: externalId,
		To_Account:   []string{beBlockExternalId},
	}

	jsonStr, err := json.Marshal(body)
	if err != nil {
		return err
	}

	resp, err := http.Post(url, "application/json", bytes.NewBuffer(jsonStr))
	if err != nil {
		return err
	}
	defer resp.Body.Close()

	result, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return err
	}

	respStruct := RespStruct{}
	if err := json.Unmarshal(result, &respStruct); err != nil {
		return err
	}
	if respStruct.ErrorCode == 0 {
		return nil
	} else {
		return fmt.Errorf(fmt.Sprintf("tencentyun BlackAdd resp ErrorCode:%v, ErrorInfo:%v", respStruct.ErrorCode, respStruct.ErrorInfo))
	}
}

//测试而已
func BlackListGet(externalId string) ([]string, error) {
	type BodyStruct struct {
		From_Account string
		StartIndex   int
		MaxLimited   int
		LastSequence int
	}

	type RespItem struct {
		To_Account        string
		AddBlackTimeStamp int
	}
	type RespStruct struct {
		BlackListItem    []RespItem
		StartIndex       int
		CurruentSequence int
		ActionStatus     string
		ErrorInfo        string
		ErrorCode        int
		ErrorDisplay     string
	}

	sig, err := getOverseaAdminSig()
	if err != nil {
		return nil, err
	}
	url := strings.Replace(strings.Replace(strings.Replace(strings.Replace(overseaBlackListGetUrl,
		"{appid}", strconv.Itoa(config.GetTxOverSeaAppId()), 1), "{externalId}", "administrator", 1), "{userSin}", sig, 1), "{random}", strconv.Itoa(rand.Intn(10000)), 1)
	body := BodyStruct{
		From_Account: externalId,
		StartIndex:   0,
		MaxLimited:   100,
		LastSequence: 0,
	}
	jsonStr, err := json.Marshal(body)
	if err != nil {
		return nil, err
	}

	resp, err := http.Post(url, "application/json", bytes.NewBuffer(jsonStr))
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	result, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}

	respStruct := RespStruct{}
	if err := json.Unmarshal(result, &respStruct); err != nil {
		return nil, err
	}
	if respStruct.ErrorCode == 0 {
		strs := []string{}
		for _, r := range respStruct.BlackListItem {
			strs = append(strs, r.To_Account)
		}
		return strs, nil
	} else {
		return nil, fmt.Errorf(fmt.Sprintf("tencentyun BlackListGet resp ErrorCode:%v, ErrorInfo:%v", respStruct.ErrorCode, respStruct.ErrorInfo))
	}
}

func BlackAdd(externalId string, beBlockExternalId string) error {
	if config.AppIsRelease() {
		return BlackAddBy(externalId, beBlockExternalId, oversearBlackAddUrl,
			func() (string, error) {
				return getOverseaAdminSig()
			}, config.GetTxOverSeaAppId())
	} else {
		return BlackAddBy(externalId, beBlockExternalId, blackAddUrl,
			func() (string, error) {
				return getAdminSig()
			}, config.GetTencentyunAppId())
	}
}

func BlackAddOverSea(externalId string, beBlockExternalId string) error {
	if err := BlackAddBy(externalId, beBlockExternalId, oversearBlackAddUrl,
		func() (string, error) {
			return getOverseaAdminSig()
		}, config.GetTxOverSeaAppId()); err != nil {
		return err
	}
	return nil
}

func BlackAddBy(externalId string, beBlockExternalId string, reqUrl string, getAdminSig func() (string, error), appId int) error {
	type BodyStruct struct {
		From_Account string
		To_Account   []string
	}
	type RespStruct struct {
		ActionStatus string
		ErrorInfo    string
		ErrorCode    int
	}
	sig, err := getAdminSig()
	if err != nil {
		return err
	}
	//url := strings.Replace(strings.Replace(strings.Replace(strings.Replace(blackAddUrl, "{appid}", strconv.Itoa(config.GetTencentyunAppId()), 1), "{externalId}", "administrator", 1), "{userSin}", sig, 1), "{random}", strconv.Itoa(rand.Intn(10000)), 1)
	url := strings.Replace(strings.Replace(strings.Replace(strings.Replace(reqUrl, "{appid}", strconv.Itoa(appId), 1), "{externalId}", "administrator", 1), "{userSin}", sig, 1), "{random}", strconv.Itoa(rand.Intn(10000)), 1)
	body := BodyStruct{
		From_Account: externalId,
		To_Account:   []string{beBlockExternalId},
	}

	jsonStr, err := json.Marshal(body)
	if err != nil {
		return err
	}

	resp, err := http.Post(url, "application/json", bytes.NewBuffer(jsonStr))
	if err != nil {
		return err
	}
	defer resp.Body.Close()

	result, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return err
	}

	respStruct := RespStruct{}
	if err := json.Unmarshal(result, &respStruct); err != nil {
		return err
	}
	mylogrus.MyLog.Infof("BlackAdd, rsp: %+v, req: %+v", resp, body)

	if respStruct.ErrorCode == 0 {
		return nil
	} else {
		return fmt.Errorf(fmt.Sprintf("tencentyun BlackAdd resp ErrorCode:%v, ErrorInfo:%v", respStruct.ErrorCode, respStruct.ErrorInfo))
	}
}

//两个人互相已读
func AdminSetMsgRead(externalId1 string, externalId2 string) error {
	if config.AppIsRelease() {
		return AdminSetMsgReadBy(externalId1, externalId2, overseaMsgReadUrl,
			func() (string, error) {
				return getOverseaAdminSig()
			}, config.GetTxOverSeaAppId())
	} else {
		return AdminSetMsgReadBy(externalId1, externalId2, msgReadUrl,
			func() (string, error) {
				return getAdminSig()
			}, config.GetTencentyunAppId())
	}
}

//两个人互相已读
func AdminSetMsgReadBy(externalId1 string, externalId2 string, reqUrl string, getAdminSig func() (string, error), appId int) error {
	type BodyStruct struct {
		Report_Account string
		Peer_Account   string
	}
	//保留部分,因为接口只提供了一个,没有抽象返回值,因为接口没几个。
	type RespStruct struct {
		ActionStatus string
		ErrorInfo    string
		ErrorCode    int
	}

	sig, err := getAdminSig()
	if err != nil {
		return err
	}
	//url := strings.Replace(strings.Replace(strings.Replace(strings.Replace(msgReadUrl, "{appid}", strconv.Itoa(config.GetTencentyunAppId()), 1), "{externalId}", "administrator", 1), "{userSin}", sig, 1), "{random}", strconv.Itoa(rand.Intn(10000)), 1)
	url := strings.Replace(strings.Replace(strings.Replace(strings.Replace(reqUrl, "{appid}", strconv.Itoa(appId), 1), "{externalId}", "administrator", 1), "{userSin}", sig, 1), "{random}", strconv.Itoa(rand.Intn(10000)), 1)
	body := BodyStruct{
		Report_Account: externalId1,
		Peer_Account:   externalId2,
	}

	jsonStr, err := json.Marshal(body)
	if err != nil {
		return err
	}

	resp, err := http.Post(url, "application/json", bytes.NewBuffer(jsonStr))
	if err != nil {
		return err
	}
	defer resp.Body.Close()

	result, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return err
	}

	respStruct := RespStruct{}
	if err := json.Unmarshal(result, &respStruct); err != nil {
		return err
	}
	if respStruct.ErrorCode == 0 {
		return nil
	} else {
		return fmt.Errorf(fmt.Sprintf("tencentyun AdminSetMsgRead resp ErrorCode:%v, ErrorInfo:%v", respStruct.ErrorCode, respStruct.ErrorInfo))
	}
}

// 基本的返回结构
type RespStruct struct {
	ActionStatus string
	ErrorCode    int
	ErrorInfo    string
}

// 私聊发送返回结构
type C2cMsgRsp struct {
	RespStruct
	MsgTime int64
	MsgKey  string
}

func GetAllGroups() ([]string, uint, error) {
	type getAllGroupReq struct {
		GroupType string
	}

	type groupIdItem struct {
		GroupId string
	}
	type getAllGroupRsp struct {
		RespStruct
		TotalCount  uint
		GroupIdList []groupIdItem
	}

	sig, err := getOverseaAdminSig()
	if err != nil {
		return nil, 0, err
	}
	url := strings.ReplaceAll(strings.ReplaceAll(strings.ReplaceAll(overseaGetAllGroupList,
		"{appid}", strconv.Itoa(config.GetTxOverSeaAppId())),
		"{userSig}", sig),
		"{random}", strconv.FormatUint(uint64(rand.Uint32()), 10))

	body := getAllGroupReq{
		GroupType: "ChatRoom",
	}

	jsonStr, err := json.Marshal(body)
	if err != nil {
		return nil, 0, err
	}
	mylogrus.MyLog.Info("GetAllGroups json: ", string(jsonStr))

	rsp, err := http.Post(url, "application/json", bytes.NewBuffer(jsonStr))
	if err != nil {
		return nil, 0, err
	}
	defer rsp.Body.Close()

	result, err := ioutil.ReadAll(rsp.Body)
	if err != nil {
		return nil, 0, err
	}

	response := getAllGroupRsp{}
	if err := json.Unmarshal(result, &response); err != nil {
		return nil, 0, err
	}
	mylogrus.MyLog.Info("GetAllGroups: ", response)

	if response.ErrorCode == 0 {
		result := make([]string, 0)
		for _, i := range response.GroupIdList {
			result = append(result, i.GroupId)
		}
		return result, response.TotalCount, nil
	} else {
		return nil, 0, fmt.Errorf(fmt.Sprintf("tencentyun GetAllGroups rsp ErrorCode:%v, ErrorInfo:%v", response.ErrorCode, response.ErrorInfo))
	}
}

type GroupEditable struct {
	Name string // 群名称(选填)
	// Introduction string // 群简介(选填) NOTE:腾讯限制字节数太短,弃用
	// Notification string // 群公告(选填) NOTE:腾讯限制字节数太短,弃用
	// FaceUrl      string // 群头像 URL(选填) NOTE:腾讯限制最长100个字节太短,弃用
	// "MaxMemberNum": 500, // 最大群成员数量(选填)
	// "ApplyJoinOption": "NeedPermission", // 申请加群方式(选填)
	// "MuteAllMember": "On" // 设置全员禁言(选填):"On"开启,"Off"关闭
}

func CreateGroup(name string, groupId string) (string, error) {
	if config.AppIsRelease() {
		return CreateGroupBy(name, groupId, overseaCreateGroupUrl,
			func() (string, error) {
				return getOverseaAdminSig()
			}, config.GetTxOverSeaAppId())
	} else {
		return CreateGroupBy(name, groupId, createGroupUrl,
			func() (string, error) {
				return getAdminSig()
			}, config.GetTencentyunAppId())
	}
}

func CreateGroupBy(name string, txGroupId string, reqUrl string, getAdminSig func() (string, error), appId int) (string, error) {
	logger := mylogrus.MyLog.WithField("appId", appId).WithField("txGroupId", txGroupId)
	type createGroupReq struct {
		Type    string
		GroupId *string
		GroupEditable
	}

	type CreateGroupRsp struct {
		RespStruct
		GroupId string
	}

	sig, err := getAdminSig()
	if err != nil {
		return "", err
	}
	/*	url := strings.ReplaceAll(strings.ReplaceAll(strings.ReplaceAll(createGroupUrl,
		"{appid}", strconv.Itoa(config.GetTencentyunAppId())),
		"{userSig}", sig),
		"{random}", strconv.FormatUint(uint64(rand.Uint32()), 10))*/

	url := strings.ReplaceAll(strings.ReplaceAll(strings.ReplaceAll(reqUrl,
		"{appid}", strconv.Itoa(appId)),
		"{userSig}", sig),
		"{random}", strconv.FormatUint(uint64(rand.Uint32()), 10))

	// 限制字符数,避免超过腾讯的30字节限制
	groupName := name
	if len(name) > 7 {
		groupName = name[0:7]
	}
	body := createGroupReq{
		Type: "AVChatRoom",
		GroupEditable: GroupEditable{
			Name: groupName,
		},
	}
	if len(txGroupId) > 0 {
		body.GroupId = &txGroupId
	}

	jsonStr, err := json.Marshal(body)
	if err != nil {
		return "", err
	}
	logger.Info("CreateGroup json: ", string(jsonStr))

	rsp, err := http.Post(url, "application/json", bytes.NewBuffer(jsonStr))
	if err != nil {
		return "", err
	}
	defer rsp.Body.Close()

	result, err := ioutil.ReadAll(rsp.Body)
	if err != nil {
		return "", err
	}

	response := CreateGroupRsp{}
	if err := json.Unmarshal(result, &response); err != nil {
		return "", err
	}
	logger.Info("CreateGroup: ", response)

	if response.ErrorCode == 0 {
		return response.GroupId, nil
	} else {
		return "", fmt.Errorf(fmt.Sprintf("tencentyun CreateGroup rsp ErrorCode:%v, ErrorInfo:%v", response.ErrorCode, response.ErrorInfo))
	}
}

func DestroyGroup(groupId string, localOnly bool) error {
	err := DestroyGroupBy(groupId, destroyGroupUrl, func() (string, error) {
		return getAdminSig()
	}, config.GetTencentyunAppId())

	if !localOnly {
		DestroyGroupBy(groupId, overseaGetDestroyGroupUrl, func() (string, error) {
			return getOverseaAdminSig()
		}, config.GetTxOverSeaAppId())
	}

	return err
}

func DestroyGroupBy(txGroupId string, reqUrl string, getAdminSig func() (string, error), appId int) error {
	logger := mylogrus.MyLog.WithField("appId", appId).WithField("txGroupId", txGroupId)
	type BodyStruct struct {
		GroupId string
	}

	sig, err := getAdminSig()
	if err != nil {
		return err
	}
	/*	url := strings.ReplaceAll(strings.ReplaceAll(strings.ReplaceAll(destroyGroupUrl,
		"{appid}", strconv.Itoa(config.GetTencentyunAppId())),
		"{userSig}", sig),
		"{random}", strconv.FormatUint(uint64(rand.Uint32()), 10))*/

	url := strings.ReplaceAll(strings.ReplaceAll(strings.ReplaceAll(reqUrl,
		"{appid}", strconv.Itoa(appId)),
		"{userSig}", sig),
		"{random}", strconv.FormatUint(uint64(rand.Uint32()), 10))

	body := BodyStruct{
		GroupId: txGroupId,
	}

	jsonStr, err := json.Marshal(body)
	if err != nil {
		return err
	}

	rsp, err := http.Post(url, "application/json", bytes.NewBuffer(jsonStr))
	if err != nil {
		return err
	}
	defer rsp.Body.Close()

	result, err := ioutil.ReadAll(rsp.Body)
	if err != nil {
		return err
	}

	response := RespStruct{}
	if err := json.Unmarshal(result, &response); err != nil {
		return err
	}

	logger.Info("DestroyGroup: ", response)

	if response.ErrorCode == 0 || response.ErrorCode == 10010 {
		return nil
	} else {
		return fmt.Errorf(fmt.Sprintf("tencentyun DestroyGroup rsp ErrorCode:%v, ErrorInfo:%v", response.ErrorCode, response.ErrorInfo))
	}
}

type MemberListInfo struct {
	Member_Account string // 成员 ID
	Role           string // 群内角色
	JoinTime       int    // 入群时间(UTC 时间)
	//MsgSeq         uint
	// MsgFlag         string // 消息屏蔽选项
	LastSendMsgTime int64 // 最后发言时间(UTC 时间)
	ShutUpUntil     int   // 禁言截止时间(UTC 时间)
	// AppMemberDefinedData": [ // 群成员自定义字段 【暂时不用】
}

type GroupBasicInfo struct {
	GroupId         string
	Type            string
	Owner_Account   string
	CreateTime      int
	LastInfoTime    int
	LastMsgTime     int
	NextMsgSeq      uint
	MemberNum       uint
	MaxMemberNum    uint
	ShutUpAllMember string
}

type GroupInfo struct {
	ErrorCode int
	ErrorInfo string
	Appid     uint
	GroupBasicInfo
	// "AppDefinedData": 群组维度的自定义字段 【暂时不用】
	MemberList []MemberListInfo
}

const GROUP_GET_BATCHSIZE = 50
const IM_GET_BATCHSIZE = 500

//海外国内是同步的,只要查询一个则可
// 分批查询群信息,保证不会溢出
func BatchGetGroupInfo(model *domain.Model, groupIds []string, requireMemberList bool) ([]GroupInfo, error) {
	result := make([]GroupInfo, 0, len(groupIds))
	for start := 0; start < len(groupIds); start += GROUP_GET_BATCHSIZE {
		end := start + GROUP_GET_BATCHSIZE
		if end > len(groupIds) {
			end = len(groupIds)
		}
		mylogrus.MyLog.Info("tencentyun.BatchGetGroupInfo: ", start, ":", end)

		g := groupIds[start:end]
		gi, err := GetGroupInfo(model, g, requireMemberList)
		if err == nil {
			result = append(result, gi...)
		} else {
			return nil, err
		}
	}
	return result, nil
}

// 查询群信息,有溢出可能,只适用于少量的情况
func GetGroupInfo(model *domain.Model, groupIds []string, requireMemberList bool) ([]GroupInfo, error) {
	type responseFilter struct {
		GroupBaseInfoFilter []string
		MemberInfoFilter    []string
	}

	type BodyStruct struct {
		GroupIdList    []string
		ResponseFilter responseFilter
	}

	sig, err := getOverseaAdminSig()
	if err != nil {
		return nil, err
	}
	url := strings.ReplaceAll(strings.ReplaceAll(strings.ReplaceAll(overseaGetGroupInfoUrl,
		"{appid}", strconv.Itoa(config.GetTxOverSeaAppId())),
		"{userSig}", sig),
		"{random}", strconv.FormatUint(uint64(rand.Uint32()), 10))

	model.Log.Info("tencentyun.GetGroupInfo: ", url)

	beginTime := time.Now()
	body := BodyStruct{
		GroupIdList: groupIds,
		ResponseFilter: responseFilter{
			GroupBaseInfoFilter: []string{"Type", "CreateTime", "Owner_Account", "LastMsgTime", "NextMsgSeq", "MemberNum", "MaxMemberNum"},
		},
	}

	if requireMemberList {
		body.ResponseFilter.MemberInfoFilter = append(body.ResponseFilter.MemberInfoFilter, "Account", "JoinTime")
	}

	jsonStr, err := json.Marshal(body)
	if err != nil {
		return nil, err
	}

	rsp, err := http.Post(url, "application/json", bytes.NewBuffer(jsonStr))
	if err != nil {
		return nil, err
	}
	defer rsp.Body.Close()

	result, err := ioutil.ReadAll(rsp.Body)
	if err != nil {
		return nil, err
	}

	type getGroupInfoRsp struct {
		RespStruct
		GroupInfo []GroupInfo
	}

	response := getGroupInfoRsp{}
	if err := json.Unmarshal(result, &response); err != nil {
		return nil, err
	}

	endTime := time.Now()
	model.Log.Info("tencentyun.GetGroupInfo takes ", endTime.Sub(beginTime).Milliseconds(), "ms")

	model.Log.Info("tencentyun.GetGroupInfo: ", response)

	if response.ErrorCode == 0 {
		result := make([]GroupInfo, 0)
		for _, i := range response.GroupInfo {
			if i.Owner_Account == IM_INVALID_USER {
				model.Log.Warnf("Skip Group %s with invalid owner", i.GroupId)
			} else {
				result = append(result, i)
			}
		}
		return result, nil
	} else {
		return nil, fmt.Errorf(fmt.Sprintf("tencentyun GetGroupInfo rsp ErrorCode:%v, ErrorInfo:%v", response.ErrorCode, response.ErrorInfo))
	}
}

//已丢弃
func AddGroup(model *domain.Model, groupId string, members []string) (map[string]uint, error, int) {
	beginTime := time.Now()

	type Member struct {
		Member_Account string
	}
	type BodyStruct struct {
		GroupId    string
		MemberList []Member
	}

	sig, err := getAdminSig()
	if err != nil {
		return nil, err, 0
	}
	url := strings.ReplaceAll(strings.ReplaceAll(strings.ReplaceAll(joinGroupUrl,
		"{appid}", strconv.Itoa(config.GetTencentyunAppId())),
		"{userSig}", sig),
		"{random}", strconv.FormatUint(uint64(rand.Uint32()), 10))

	body := BodyStruct{
		GroupId:    groupId,
		MemberList: []Member{},
	}
	for _, i := range members {
		body.MemberList = append(body.MemberList, Member{Member_Account: i})
	}

	jsonStr, err := json.Marshal(body)
	if err != nil {
		return nil, err, 0
	}

	rsp, err := http.Post(url, "application/json", bytes.NewBuffer(jsonStr))
	if err != nil {
		return nil, err, 0
	}
	defer rsp.Body.Close()

	result, err := ioutil.ReadAll(rsp.Body)
	if err != nil {
		return nil, err, 0
	}

	type memberResult struct {
		Member_Account string
		Result         uint
	}
	type addGroupRsp struct {
		RespStruct
		MemberList []memberResult
	}
	response := addGroupRsp{}
	if err := json.Unmarshal(result, &response); err != nil {
		return nil, err, 0
	}

	endTime := time.Now()
	model.Log.Info("tencentyun.AddGroup takes ", endTime.Sub(beginTime).Milliseconds(), "ms")

	model.Log.Info("AddGroup: ", response)

	if response.ErrorCode == 0 {
		result := make(map[string]uint)
		for _, i := range response.MemberList {
			result[i.Member_Account] = i.Result
		}
		return result, nil, 0
	} else {
		return nil, fmt.Errorf(fmt.Sprintf("tencentyun AddGroup rsp ErrorCode:%v, ErrorInfo:%v", response.ErrorCode, response.ErrorInfo)), response.ErrorCode
	}
}

//已丢弃
func LeaveGroup(groupId string, members []string) {
	type BodyStruct struct {
		GroupId             string
		MemberToDel_Account []string
	}

	sig, err := getAdminSig()
	if err != nil {
		mylogrus.MyLog.Errorln(err)
		return
	}
	url := strings.ReplaceAll(strings.ReplaceAll(strings.ReplaceAll(leaveGroupUrl,
		"{appid}", strconv.Itoa(config.GetTencentyunAppId())),
		"{userSig}", sig),
		"{random}", strconv.FormatUint(uint64(rand.Uint32()), 10))

	body := BodyStruct{
		GroupId:             groupId,
		MemberToDel_Account: members,
	}

	jsonStr, err := json.Marshal(body)
	if err != nil {
		mylogrus.MyLog.Errorln(err)
		return
	}

	rsp, err := http.Post(url, "application/json", bytes.NewBuffer(jsonStr))
	if err != nil {
		mylogrus.MyLog.Errorln(err)
		return
	}
	defer rsp.Body.Close()

	result, err := ioutil.ReadAll(rsp.Body)
	if err != nil {
		mylogrus.MyLog.Error(err)
		return
	}

	response := RespStruct{}
	if err := json.Unmarshal(result, &response); err != nil {
		mylogrus.MyLog.Errorln(err)
		return
	}

	mylogrus.MyLog.Info("LeaveGroup: ", response)

	if response.ErrorCode == 0 {
		return
	} else {
		mylogrus.MyLog.Errorln(fmt.Errorf(fmt.Sprintf("tencentyun LeaveGroup rsp ErrorCode:%v, ErrorInfo:%v", response.ErrorCode, response.ErrorInfo)))
		return
	}
}

type SelfGroupInfo struct {
	JoinTime        int
	Role            string
	MsgFlag         string
	LastSendMsgTime int
	NameCard        string
}

type JoinedGroupInfo struct {
	GroupBasicInfo
	SelfInfo SelfGroupInfo
}

func ShutUpAllMember(groupId string, shutUp bool) error {
	if config.AppIsRelease() {
		return ShutUpAllMemberBy(groupId, shutUp, overseaModifyGroupInfoUrl, func() (string, error) {
			return getOverseaAdminSig()
		}, config.GetTxOverSeaAppId())
	} else {
		return ShutUpAllMemberBy(groupId, shutUp, modifyGroupInfoUrl, func() (string, error) {
			return getAdminSig()
		}, config.GetTencentyunAppId())
	}
}

func ShutUpAllMemberBy(groupId string, shutUp bool, reqUrl string, getAdminSig func() (string, error), appId int) error {
	type ModifyGroupInfoReq struct {
		GroupId       string
		MuteAllMember string
	}

	sig, err := getAdminSig()
	if err != nil {
		return err
	}
	/*	url := strings.ReplaceAll(strings.ReplaceAll(strings.ReplaceAll(modifyGroupInfoUrl,
		"{appid}", strconv.Itoa(config.GetTencentyunAppId())),
		"{userSig}", sig),
		"{random}", strconv.FormatUint(uint64(rand.Uint32()), 10))*/

	url := strings.ReplaceAll(strings.ReplaceAll(strings.ReplaceAll(reqUrl,
		"{appid}", strconv.Itoa(appId)),
		"{userSig}", sig),
		"{random}", strconv.FormatUint(uint64(rand.Uint32()), 10))

	body := ModifyGroupInfoReq{
		GroupId: groupId,
	}
	if shutUp {
		body.MuteAllMember = "On"
	} else {
		body.MuteAllMember = "Off"
	}

	jsonStr, err := json.Marshal(body)
	if err != nil {
		return err
	}

	mylogrus.MyLog.Info("ShutUpAllMember json: ", string(jsonStr))

	rsp, err := http.Post(url, "application/json", bytes.NewBuffer(jsonStr))
	if err != nil {
		return err
	}
	defer rsp.Body.Close()
	result, err := ioutil.ReadAll(rsp.Body)
	if err != nil {
		return err
	}

	response := RespStruct{}
	if err := json.Unmarshal(result, &response); err != nil {
		return err
	}

	mylogrus.MyLog.Info("ShutUpAllMember: ", response)

	if response.ErrorCode == 0 {
		return nil
	} else {
		return fmt.Errorf(fmt.Sprintf("tencentyun ShutUpAllMember rsp ErrorCode:%v, ErrorInfo:%v", response.ErrorCode, response.ErrorInfo))
	}
}

//丢弃
func SetGroupMaxMemberNum(groupId string, maxMemberNum uint) error {
	type modifyGroupInfoReq struct {
		GroupId      string
		MaxMemberNum uint
	}

	sig, err := getOverseaAdminSig()
	if err != nil {
		return err
	}
	url := strings.ReplaceAll(strings.ReplaceAll(strings.ReplaceAll(overseaModifyGroupInfoUrl,
		"{appid}", strconv.Itoa(config.GetTxOverSeaAppId())),
		"{userSig}", sig),
		"{random}", strconv.FormatUint(uint64(rand.Uint32()), 10))

	body := modifyGroupInfoReq{
		GroupId:      groupId,
		MaxMemberNum: maxMemberNum,
	}

	jsonStr, err := json.Marshal(body)
	if err != nil {
		return err
	}

	mylogrus.MyLog.Info("SetGroupMaxMemberNum json: ", string(jsonStr))

	rsp, err := http.Post(url, "application/json", bytes.NewBuffer(jsonStr))
	if err != nil {
		return err
	}

	defer rsp.Body.Close()
	result, err := ioutil.ReadAll(rsp.Body)
	if err != nil {
		return err
	}

	response := RespStruct{}
	if err := json.Unmarshal(result, &response); err != nil {
		return err
	}

	mylogrus.MyLog.Info("SetGroupMaxMemberNum: ", response)

	if response.ErrorCode == 0 {
		return nil
	} else {
		return fmt.Errorf(fmt.Sprintf("tencentyun SetGroupMaxMemberNum rsp ErrorCode:%v, ErrorInfo:%v", response.ErrorCode, response.ErrorInfo))
	}
}

func GetGroupMemberInfo(model *domain.Model, groupId string) (uint, []MemberListInfo, error) {
	beginTime := time.Now()

	type BodyStruct struct {
		GroupId          string
		MemberInfoFilter []string
	}

	sig, err := getOverseaAdminSig()
	if err != nil {
		return 0, nil, err
	}
	url := strings.ReplaceAll(strings.ReplaceAll(strings.ReplaceAll(overseaGetGroupMemberUrl,
		"{appid}", strconv.Itoa(config.GetTxOverSeaAppId())),
		"{userSig}", sig),
		"{random}", strconv.FormatUint(uint64(rand.Uint32()), 10))

	body := BodyStruct{
		GroupId:          groupId,
		MemberInfoFilter: []string{"Role", "LastSendMsgTime"},
	}

	jsonStr, err := json.Marshal(body)
	if err != nil {
		return 0, nil, err
	}

	rsp, err := http.Post(url, "application/json", bytes.NewBuffer(jsonStr))
	if err != nil {
		return 0, nil, err
	}

	defer rsp.Body.Close()
	result, err := ioutil.ReadAll(rsp.Body)
	if err != nil {
		return 0, nil, err
	}

	type getGroupMemberRsp struct {
		RespStruct
		MemberNum  uint
		MemberList []MemberListInfo
	}

	response := getGroupMemberRsp{}
	if err := json.Unmarshal(result, &response); err != nil {
		return 0, nil, err
	}
	endTime := time.Now()
	model.Log.Info("tencentyun.GetGroupMemberInfo takes ", endTime.Sub(beginTime).Milliseconds(), "ms")

	model.Log.Debug("GetGroupMemberInfo ", groupId, " : ", response)

	if response.ErrorCode == 0 {
		return response.MemberNum, response.MemberList, nil
	} else {
		return 0, nil, fmt.Errorf(fmt.Sprintf("tencentyun GetGroupMemberInfo rsp ErrorCode:%v, ErrorInfo:%v", response.ErrorCode, response.ErrorInfo))
	}
}

// 海外版和国内版之间交叉发群消息
func CrossSendGroupMsg(appId int, txGroupId string, fromAccount *string, cloudCustomData string, msgType string, msgContent json.RawMessage) (uint, error) {
	if appId == config.GetTencentyunAppId() {
		return SendGroupMsgBy(txGroupId, fromAccount, cloudCustomData, msgType, msgContent, overseaSendNormalMsgUrl,
			func() (string, error) {
				return getOverseaAdminSig()
			}, config.GetTxOverSeaAppId())
	} else if appId == config.GetTxOverSeaAppId() {
		return SendGroupMsgBy(txGroupId, fromAccount, cloudCustomData, msgType, msgContent, sendNormalMsgUrl,
			func() (string, error) {
				return getAdminSig()
			}, config.GetTencentyunAppId())
	}
	return 0, errors.New("illegal appid")
}

func SendGroupMsgBy(txGroupId string, fromAccount *string, cloudCustomData string, msgType string, msgContent json.RawMessage,
	reqUrl string, getAdminSig func() (string, error), appId int) (uint, error) {

	beginTime := time.Now()

	type msgBodyItem struct {
		MsgType    string
		MsgContent json.RawMessage
	}
	type BodyStruct struct {
		GroupId               string
		From_Account          *string
		Random                uint32
		ForbidCallbackControl []string
		MsgBody               []msgBodyItem
		CloudCustomData       string
	}

	sig, err := getAdminSig()
	if err != nil {
		return 0, err
	}
	url := strings.ReplaceAll(strings.ReplaceAll(strings.ReplaceAll(reqUrl,
		"{appid}", strconv.Itoa(appId)),
		"{userSig}", sig),
		"{random}", strconv.FormatUint(uint64(rand.Uint32()), 10))

	body := BodyStruct{
		GroupId:               txGroupId,
		Random:                rand.Uint32(),
		ForbidCallbackControl: []string{"ForbidAfterSendMsgCallback"},
		MsgBody:               []msgBodyItem{{MsgType: msgType, MsgContent: msgContent}},
		CloudCustomData:       cloudCustomData,
	}

	if fromAccount != nil {
		body.From_Account = fromAccount
	}

	mylogrus.MyLog.Infof("SendGroupMsgBy on Group %s, url:%s, body: %+v", txGroupId, url, body)

	jsonStr, err := json.Marshal(body)
	if err != nil {
		return 0, err
	}

	rsp, err := http.Post(url, "application/json", bytes.NewBuffer(jsonStr))

	if err != nil {
		return 0, err
	}
	defer rsp.Body.Close()
	result, err := ioutil.ReadAll(rsp.Body)
	if err != nil {
		return 0, err
	}

	type sendNormalMsgRsp struct {
		RespStruct
		MsgTime int64
		MsgSeq  uint
	}
	response := sendNormalMsgRsp{}
	if err := json.Unmarshal(result, &response); err != nil {
		return 0, err
	}

	endTime := time.Now()

	mylogrus.MyLog.Infof("SendGroupMsgBy on Group %s, result: %v, takes %v ms", txGroupId, response, endTime.Sub(beginTime).Milliseconds())

	if response.ErrorCode == 0 {
		return response.MsgSeq, nil
	} else {
		return 0, fmt.Errorf(fmt.Sprintf("tencentyun SendGroupMsgBy rsp ErrorCode:%v, ErrorInfo:%v", response.ErrorCode, response.ErrorInfo))
	}
}

func SendCustomMsg(logger *logrus.Entry, txGroupId string, fromAccount *string, content string, cloudCustomData string) (uint, error) {
	if config.AppIsRelease() {
		return SendCustomMsgBy(logger, txGroupId, fromAccount, content, cloudCustomData, overseaSendNormalMsgUrl,
			func() (string, error) {
				return getOverseaAdminSig()
			}, config.GetTxOverSeaAppId())
	} else {
		return SendCustomMsgBy(logger, txGroupId, fromAccount, content, cloudCustomData, sendNormalMsgUrl,
			func() (string, error) {
				return getAdminSig()
			}, config.GetTencentyunAppId())
	}
}

// 发送(自定义)群聊天消息 // fixme: 异步化
func SendCustomMsgBy(logger *logrus.Entry, txGroupId string, fromAccount *string, content string, cloudCustomData string, reqUrl string, getAdminSig func() (string, error), appId int) (uint, error) {
	logger = logger.WithField("appId", appId).WithField("txGroupId", txGroupId)
	logger.Infof("from %v, content: %s", fromAccount, content)

	beginTime := time.Now()

	type msgContentItem struct {
		Data string
	}
	type msgBodyItem struct {
		MsgType    string
		MsgContent msgContentItem
	}
	type BodyStruct struct {
		GroupId               string
		From_Account          *string
		Random                uint32
		ForbidCallbackControl []string
		MsgBody               []msgBodyItem
		CloudCustomData       string
	}

	sig, err := getAdminSig()
	if err != nil {
		return 0, err
	}
	url := strings.ReplaceAll(strings.ReplaceAll(strings.ReplaceAll(reqUrl,
		"{appid}", strconv.Itoa(appId)),
		"{userSig}", sig),
		"{random}", strconv.FormatUint(uint64(rand.Uint32()), 10))

	body := BodyStruct{
		GroupId:               txGroupId,
		Random:                rand.Uint32(),
		ForbidCallbackControl: []string{"ForbidAfterSendMsgCallback"},
		MsgBody:               []msgBodyItem{{MsgType: "TIMCustomElem", MsgContent: msgContentItem{Data: content}}},
		CloudCustomData:       cloudCustomData,
	}
	if fromAccount != nil {
		body.From_Account = fromAccount
	}

	jsonStr, err := json.Marshal(body)
	if err != nil {
		return 0, err
	}

	rsp, err := http.Post(url, "application/json", bytes.NewBuffer(jsonStr))
	if err != nil {
		return 0, err
	}
	defer rsp.Body.Close()

	result, err := ioutil.ReadAll(rsp.Body)
	if err != nil {
		return 0, err
	}

	type sendNormalMsgRsp struct {
		RespStruct
		MsgTime int64
		MsgSeq  uint
	}
	response := sendNormalMsgRsp{}
	if err := json.Unmarshal(result, &response); err != nil {
		return 0, err
	}

	endTime := time.Now()
	logger.Infof("SendCustomMsg taskes %d ms, result: %v", endTime.Sub(beginTime).Milliseconds(), response)

	if response.ErrorCode == 0 {
		return response.MsgSeq, nil
	} else {
		return 0, fmt.Errorf(fmt.Sprintf("tencentyun SendCustomMsg rsp ErrorCode:%v, ErrorInfo:%v", response.ErrorCode, response.ErrorInfo))
	}
}

func SendSimpleAtMsg(txGroupId string, from string, account string, onLineOnly int, content string, cloudCustomData string) (uint, error) {
	if config.AppIsRelease() {
		return SendSimpleAtMsgBy(txGroupId, from, account, onLineOnly, content, cloudCustomData, overseaSendNormalMsgUrl,
			func() (string, error) {
				return getOverseaAdminSig()
			}, config.GetTxOverSeaAppId())
	} else {
		return SendSimpleAtMsgBy(txGroupId, from, account, onLineOnly, content, cloudCustomData, sendNormalMsgUrl,
			func() (string, error) {
				return getAdminSig()
			}, config.GetTencentyunAppId())
	}
}

// 向群内发送形如“@tommy xxx"的消息
func SendSimpleAtMsgBy(txGroupId string, from string, account string, onLineOnly int, content string, cloudCustomData string,
	reqUrl string, getAdminSig func() (string, error), appId int) (uint, error) {
	mylogrus.MyLog.Infof("SendSimpleAtMsg on Group %s, from %s, %s, content: %s", txGroupId, from, account, content)
	beginTime := time.Now()

	type msgContentItem struct {
		Text string
	}
	type msgBodyItem struct {
		MsgType    string
		MsgContent msgContentItem
	}
	type groupAtItem struct {
		GroupAtAllFlag  int
		GroupAt_Account string
	}
	type BodyStruct struct {
		GroupId               string
		From_Account          string
		Random                uint32
		ForbidCallbackControl []string
		OnlineOnlyFlag        *int
		MsgBody               []msgBodyItem
		GroupAtInfo           []groupAtItem
		CloudCustomData       string
	}

	sig, err := getAdminSig()
	if err != nil {
		return 0, err
	}
	url := strings.ReplaceAll(strings.ReplaceAll(strings.ReplaceAll(reqUrl,
		"{appid}", strconv.Itoa(appId)),
		"{userSig}", sig),
		"{random}", strconv.FormatUint(uint64(rand.Uint32()), 10))

	body := BodyStruct{
		GroupId:               txGroupId,
		From_Account:          from,
		Random:                rand.Uint32(),
		ForbidCallbackControl: []string{"ForbidAfterSendMsgCallback"},
		MsgBody:               []msgBodyItem{{MsgType: "TIMTextElem", MsgContent: msgContentItem{Text: content}}},
		GroupAtInfo:           []groupAtItem{{GroupAtAllFlag: 0, GroupAt_Account: account}},
		CloudCustomData:       cloudCustomData,
	}
	if onLineOnly == 1 {
		body.OnlineOnlyFlag = &onLineOnly
	}

	jsonStr, err := json.Marshal(body)
	if err != nil {
		return 0, err
	}

	rsp, err := http.Post(url, "application/json", bytes.NewBuffer(jsonStr))
	if err != nil {
		return 0, err
	}
	defer rsp.Body.Close()

	result, err := ioutil.ReadAll(rsp.Body)
	if err != nil {
		return 0, err
	}

	type sendNormalMsgRsp struct {
		RespStruct
		MsgTime int64
		MsgSeq  uint
	}
	response := sendNormalMsgRsp{}
	if err := json.Unmarshal(result, &response); err != nil {
		return 0, err
	}

	endTime := time.Now()
	mylogrus.MyLog.Info("tencentyun.SendSimpleAtMsg takes ", endTime.Sub(beginTime).Milliseconds(), "ms")
	mylogrus.MyLog.Infof("SendSimpleAtMsg on Group %s, result: %v", txGroupId, response)

	if response.ErrorCode == 0 {
		return response.MsgSeq, nil
	} else {
		return 0, fmt.Errorf(fmt.Sprintf("tencentyun SendSimpleAtMsg rsp ErrorCode:%v, ErrorInfo:%v", response.ErrorCode, response.ErrorInfo))
	}
}

func SendSystemMsg(logger *logrus.Entry, groupId string, members []string, content string) error {
	if config.AppIsRelease() {
		return SendSystemMsgBy(logger, groupId, members, content, overseaSendSystemMsgUrl,
			func() (string, error) {
				return getOverseaAdminSig()
			}, config.GetTxOverSeaAppId())
	} else {
		return SendSystemMsgBy(logger, groupId, members, content, sendSystemMsgUrl,
			func() (string, error) {
				return getAdminSig()
			}, config.GetTencentyunAppId())
	}
}
func SendSystemMsgBy(logger *logrus.Entry, txGroupId string, members []string, content string, reqUrl string, getAdminSig func() (string, error), appId int) error {
	logger = logger.WithField("appId", appId).WithField("txGroupId", txGroupId)
	//logger.Infof("SendSystemMsg content: %s", content)
	beginTime := time.Now()

	type BodyStruct struct {
		GroupId           string
		ToMembers_Account []string
		Content           string
	}

	sig, err := getAdminSig()
	if err != nil {
		return err
	}
	url := strings.ReplaceAll(strings.ReplaceAll(strings.ReplaceAll(reqUrl,
		"{appid}", strconv.Itoa(appId)),
		"{userSig}", sig),
		"{random}", strconv.FormatUint(uint64(rand.Uint32()), 10))

	body := BodyStruct{
		GroupId:           txGroupId,
		ToMembers_Account: members,
		Content:           content,
	}

	jsonStr, err := json.Marshal(body)
	if err != nil {
		return err
	}

	rsp, err := http.Post(url, "application/json", bytes.NewBuffer(jsonStr))
	if err != nil {
		return err
	}
	defer rsp.Body.Close()

	result, err := ioutil.ReadAll(rsp.Body)
	if err != nil {
		return err
	}

	response := RespStruct{}
	if err := json.Unmarshal(result, &response); err != nil {
		return err
	}

	endTime := time.Now()
	logger.Infof("SendSystemMsg takes %dms: rsp: %+v", endTime.Sub(beginTime).Milliseconds(), response)

	if response.ErrorCode == 0 {
		return nil
	} else {
		return fmt.Errorf(fmt.Sprintf("tencentyun SendSystemMsg rsp ErrorCode:%v, ErrorInfo:%v", response.ErrorCode, response.ErrorInfo))
	}
}

type MsgContentItem struct {
	Text string
}
type MsgBodyItem struct {
	MsgContent MsgContentItem
	MsgType    string
}
type GroupMsgItem struct {
	From_Account string
	IsPlaceMsg   int
	MsgBody      []MsgBodyItem
	MsgPriority  int
	MsgRandom    uint
	MsgSeq       uint
	MsgTimeStamp uint
}

func QueryState(model *domain.Model, users []string, isNeedDetail bool) (map[string]string, []string, error) {
	beginTime := time.Now()

	type BodyStruct struct {
		To_Account   []string
		IsNeedDetail bool
	}

	sig, err := getAdminSig()
	if err != nil {
		return nil, nil, err
	}
	url := strings.ReplaceAll(strings.ReplaceAll(strings.ReplaceAll(queryStateUrl,
		"{appid}", strconv.Itoa(config.GetTencentyunAppId())),
		"{userSig}", sig),
		"{random}", strconv.FormatUint(uint64(rand.Uint32()), 10))

	body := BodyStruct{
		To_Account:   users,
		IsNeedDetail: isNeedDetail,
	}

	jsonStr, err := json.Marshal(body)
	if err != nil {
		return nil, nil, err
	}

	rsp, err := http.Post(url, "application/json", bytes.NewBuffer(jsonStr))
	if err != nil {
		return nil, nil, err
	}
	defer rsp.Body.Close()

	result, err := ioutil.ReadAll(rsp.Body)
	if err != nil {
		return nil, nil, err
	}

	type detailInfo struct {
		Platform string
		Status   string
	}
	type stateInfo struct {
		To_Account string
		Status     string
		Detail     []detailInfo
	}
	type errorInfo struct {
		To_Account string
		ErrorCode  uint
	}
	type queryStateRsp struct {
		RespStruct
		QueryResult []stateInfo
		ErrorList   []errorInfo
	}

	response := queryStateRsp{}
	if err := json.Unmarshal(result, &response); err != nil {
		return nil, nil, err
	}

	endTime := time.Now()
	model.Log.Info("tencentyun.QueryState takes ", endTime.Sub(beginTime).Milliseconds(), "ms")
	model.Log.Info("tencentyun.QueryState: ", response)

	failed := make([]string, 0)
	if response.ErrorCode == 0 {
		result := make(map[string]string, 0)
		for _, i := range response.QueryResult {
			result[i.To_Account] = i.Status
		}
		for _, i := range response.ErrorList {
			failed = append(failed, i.To_Account)
		}
		return result, failed, nil
	} else {
		return nil, failed, nil
		//return nil, nil, fmt.Errorf(fmt.Sprintf("tencentyun QueryState rsp ErrorCode:%v, ErrorInfo:%v", response.ErrorCode, response.ErrorInfo))
	}
}

func BatchQueryState(model *domain.Model, users []string) (map[string]uint, error) {
	result := make(map[string]uint, 0)
	for start := 0; start < len(users); start += IM_GET_BATCHSIZE {
		end := start + IM_GET_BATCHSIZE
		if end > len(users) {
			end = len(users)
		}

		u := users[start:end]
		s, missed, err := QueryState(model, u, false)

		mylogrus.MyLog.Info("tencentyun.BatchQueryState: ", start, ":", end, ", statusMap: ", s, ", missed: ", missed)

		if err == nil {
			for _, i := range missed {
				result[i] = IM_STATUS_OFF_LINE
			}
			for k, v := range s {
				if v == "Offline" {
					result[k] = IM_STATUS_OFF_LINE
				} else if v == "PushOnline" {
					result[k] = IM_STATUS_PUSH_ON_LINE
				} else if v == "Online" {
					result[k] = IM_STATUS_ON_LINE
				}
			}
		} else {
			return nil, err
		}
	}
	return result, nil
}

func DeleteAccount(userId string) ([]string, error) {
	type accountType struct {
		UserID string
	}
	type BodyStruct struct {
		DeleteItem []accountType
	}

	sig, err := getAdminSig()
	if err != nil {
		return nil, err
	}
	url := strings.ReplaceAll(strings.ReplaceAll(strings.ReplaceAll(deleteAccountUrl,
		"{appid}", strconv.Itoa(config.GetTencentyunAppId())),
		"{userSig}", sig),
		"{random}", strconv.FormatUint(uint64(rand.Uint32()), 10))

	body := BodyStruct{
		DeleteItem: []accountType{{UserID: userId}},
	}

	jsonStr, err := json.Marshal(body)
	if err != nil {
		return nil, err
	}

	rsp, err := http.Post(url, "application/json", bytes.NewBuffer(jsonStr))
	if err != nil {
		return nil, err
	}
	defer rsp.Body.Close()

	result, err := ioutil.ReadAll(rsp.Body)
	if err != nil {
		return nil, err
	}

	type resultItemType struct {
		ResultCode int
		ResultInfo string
		UserID     string
	}
	type deleteAccountRsp struct {
		RespStruct
		ResultItem []resultItemType
	}

	response := deleteAccountRsp{}
	if err := json.Unmarshal(result, &response); err != nil {
		return nil, err
	}

	mylogrus.MyLog.Info("DeleteAccount: ", response)

	failed := make([]string, 0)
	if response.ErrorCode == 0 {
		for _, i := range response.ResultItem {
			mylogrus.MyLog.Info("DeleteAccount ResultItem", i)

			if i.ResultCode != 0 {
				failed = append(failed, i.UserID)
			}
		}
		return failed, nil
	} else {
		return nil, nil
		//return nil, nil, fmt.Errorf(fmt.Sprintf("tencentyun QueryState rsp ErrorCode:%v, ErrorInfo:%v", response.ErrorCode, response.ErrorInfo))
	}
}

func GetUserLevel(userId string) (uint32, error) {
	type BodyStruct struct {
		To_Account []string
		TagList    []string
	}

	sig, err := getAdminSig()
	if err != nil {
		return 0, err
	}
	url := strings.ReplaceAll(strings.ReplaceAll(strings.ReplaceAll(getProfileUrl,
		"{appid}", strconv.Itoa(config.GetTencentyunAppId())),
		"{userSig}", sig),
		"{random}", strconv.FormatUint(uint64(rand.Uint32()), 10))

	body := BodyStruct{
		To_Account: []string{userId},
		TagList:    []string{TAG_PROFILE_IM_LEVEL},
	}

	jsonStr, err := json.Marshal(body)
	if err != nil {
		return 0, err
	}

	rsp, err := http.Post(url, "application/json", bytes.NewBuffer(jsonStr))
	if err != nil {
		return 0, err
	}
	defer rsp.Body.Close()

	result, err := ioutil.ReadAll(rsp.Body)
	if err != nil {
		return 0, err
	}

	type ProfileItemType struct {
		Tag   string
		Value uint32
	}

	type UserProfileItemType struct {
		To_Account  string
		ProfileItem []ProfileItemType
		ResultCode  int
		ResultInfo  string
	}
	type getUserLevelRsp struct {
		UserProfileItem []UserProfileItemType
		RespStruct
		ErrorDisplay string
	}

	response := getUserLevelRsp{}
	if err := json.Unmarshal(result, &response); err != nil {
		return 0, err
	}

	mylogrus.MyLog.Info("GetUserLevel: ", response)

	if response.ErrorCode == 0 {
		for _, i := range response.UserProfileItem {
			if i.To_Account == userId && i.ResultCode == 0 {
				for _, j := range i.ProfileItem {
					if j.Tag == TAG_PROFILE_IM_LEVEL {
						return j.Value, nil
					}
				}
			}
		}
		return 0, nil
	} else {
		return 0, fmt.Errorf(response.ErrorDisplay)
	}
}

// fixme: 可以去掉了
func SetUserLevel(userId string, level uint32) error {
	if !config.AppIsRelease() {
		return SetUserLevelBy(userId, level, setProfileUrl,
			func() (string, error) {
				return getAdminSig()
			}, config.GetTencentyunAppId())
	}
	return nil
}

func SetUserLevelBy(userId string, level uint32, reqUrl string, getAdminSig func() (string, error), appId int) error {
	mylogrus.MyLog.Infof("SetUserLevel: user %s, level = %x", userId, level)

	type ProfileItemType struct {
		Tag   string
		Value uint32
	}
	type BodyStruct struct {
		From_Account string
		ProfileItem  []ProfileItemType
	}

	sig, err := getAdminSig()
	if err != nil {
		return err
	}
	url := strings.ReplaceAll(strings.ReplaceAll(strings.ReplaceAll(reqUrl,
		"{appid}", strconv.Itoa(appId)),
		"{userSig}", sig),
		"{random}", strconv.FormatUint(uint64(rand.Uint32()), 10))

	body := BodyStruct{
		From_Account: userId,
		ProfileItem:  []ProfileItemType{{Tag: TAG_PROFILE_IM_LEVEL, Value: level}},
	}

	jsonStr, err := json.Marshal(body)
	if err != nil {
		return err
	}

	rsp, err := http.Post(url, "application/json", bytes.NewBuffer(jsonStr))
	if err != nil {
		return err
	}
	defer rsp.Body.Close()

	result, err := ioutil.ReadAll(rsp.Body)
	if err != nil {
		return err
	}

	type setUserLevelRsp struct {
		RespStruct
		ErrorDisplay string
	}

	response := setUserLevelRsp{}
	if err := json.Unmarshal(result, &response); err != nil {
		return err
	}

	mylogrus.MyLog.Info("SetUserLevel: ", response)

	if response.ErrorCode == 0 {
		return nil
	} else {
		return fmt.Errorf(response.ErrorDisplay)
	}
}

func GetUserHiloInfo(userId string) (string, error) {
	type BodyStruct struct {
		To_Account []string
		TagList    []string
	}

	sig, err := getOverseaAdminSig()
	if err != nil {
		return "", err
	}
	url := strings.ReplaceAll(strings.ReplaceAll(strings.ReplaceAll(overseaGetProfileUrl,
		"{appid}", strconv.Itoa(config.GetTxOverSeaAppId())),
		"{userSig}", sig),
		"{random}", strconv.FormatUint(uint64(rand.Uint32()), 10))

	body := BodyStruct{
		To_Account: []string{userId},
		TagList:    []string{TAG_PROFILE_IM_HILO},
	}

	jsonStr, err := json.Marshal(body)
	if err != nil {
		return "", err
	}

	rsp, err := http.Post(url, "application/json", bytes.NewBuffer(jsonStr))
	if err != nil {
		return "", err
	}
	defer rsp.Body.Close()

	result, err := ioutil.ReadAll(rsp.Body)
	if err != nil {
		return "", err
	}

	type ProfileItemType struct {
		Tag   string
		Value string
	}

	type UserProfileItemType struct {
		To_Account  string
		ProfileItem []ProfileItemType
		ResultCode  int
		ResultInfo  string
	}
	type getUserLevelRsp struct {
		UserProfileItem []UserProfileItemType
		RespStruct
		ErrorDisplay string
	}

	response := getUserLevelRsp{}
	if err := json.Unmarshal(result, &response); err != nil {
		return "", err
	}

	mylogrus.MyLog.Info("GetUserHiloInfo: ", response)

	if response.ErrorCode == 0 {
		for _, i := range response.UserProfileItem {
			if i.To_Account == userId && i.ResultCode == 0 {
				for _, j := range i.ProfileItem {
					if j.Tag == TAG_PROFILE_IM_HILO {
						return j.Value, nil
					}
				}
			}
		}
		return "", nil
	} else {
		return "", fmt.Errorf(response.ErrorDisplay)
	}
}

func SetUserHiloInfo(userId string, value string) error {
	/*	err := SetUserHiloInfoBy(userId, value, overseaSetProfileUrl,
			func() (string, error) {
				return getOverseaAdminSig()
			}, config.GetTxOverSeaAppId())
		if err != nil {
			return err
		}
	*/
	if !config.AppIsRelease() {
		return SetUserHiloInfoBy(userId, value, setProfileUrl,
			func() (string, error) {
				return getAdminSig()
			}, config.GetTencentyunAppId())
	}
	return nil
}

func SetUserHiloInfoBy(userId string, value string, reqUrl string, getAdminSig func() (string, error), appId int) error {
	mylogrus.MyLog.Infof("SetUserHiloInfo: user %s, value: %s", userId, value)

	type ProfileItemType struct {
		Tag   string
		Value string
	}
	type BodyStruct struct {
		From_Account string
		ProfileItem  []ProfileItemType
	}

	sig, err := getAdminSig()
	if err != nil {
		return err
	}
	url := strings.ReplaceAll(strings.ReplaceAll(strings.ReplaceAll(reqUrl,
		"{appid}", strconv.Itoa(appId)),
		"{userSig}", sig),
		"{random}", strconv.FormatUint(uint64(rand.Uint32()), 10))

	body := BodyStruct{
		From_Account: userId,
		ProfileItem:  []ProfileItemType{{Tag: TAG_PROFILE_IM_HILO, Value: value}},
	}

	jsonStr, err := json.Marshal(body)
	if err != nil {
		return err
	}

	rsp, err := http.Post(url, "application/json", bytes.NewBuffer(jsonStr))
	if err != nil {
		return err
	}
	defer rsp.Body.Close()

	result, err := ioutil.ReadAll(rsp.Body)
	if err != nil {
		return err
	}

	type setUserLevelRsp struct {
		RespStruct
		ErrorDisplay string
	}

	response := setUserLevelRsp{}
	if err := json.Unmarshal(result, &response); err != nil {
		return err
	}

	mylogrus.MyLog.Info("SetUserHiloInfo: ", response)

	if response.ErrorCode == 0 {
		return nil
	} else {
		return fmt.Errorf(response.ErrorDisplay)
	}
}

// 重置用户信息
// 头像+昵称
func ResetUserInfo(userId, nick string) error {
	if config.AppIsRelease() {
		return ResetUserInfoBy(userId, nick, overseaSetProfileUrl,
			func() (string, error) {
				return getOverseaAdminSig()
			}, config.GetTxOverSeaAppId())
	}
	return ResetUserInfoBy(userId, nick, setProfileUrl,
		func() (string, error) {
			return getAdminSig()
		}, config.GetTencentyunAppId())
}

func ResetUserInfoBy(userId, nick string, reqUrl string, getAdminSig func() (string, error), appId int) error {
	type ProfileItemType struct {
		Tag   string
		Value string
	}
	type BodyStruct struct {
		From_Account string
		ProfileItem  []ProfileItemType
	}

	sig, err := getAdminSig()
	if err != nil {
		return err
	}
	url := strings.ReplaceAll(strings.ReplaceAll(strings.ReplaceAll(reqUrl,
		"{appid}", strconv.Itoa(appId)),
		"{userSig}", sig),
		"{random}", strconv.FormatUint(uint64(rand.Uint32()), 10))

	body := BodyStruct{
		From_Account: userId,
		ProfileItem: []ProfileItemType{
			{Tag: TAG_PROFILE_IM_NICK, Value: nick},
			{Tag: TAG_PROFILE_IM_IMAGE, Value: utils.MakeFullUrl(utils.DefaultAvatarMan)},
		},
	}

	jsonStr, err := json.Marshal(body)
	if err != nil {
		return err
	}

	rsp, err := http.Post(url, "application/json", bytes.NewBuffer(jsonStr))
	if err != nil {
		return err
	}
	defer rsp.Body.Close()

	result, err := ioutil.ReadAll(rsp.Body)
	if err != nil {
		return err
	}

	type setUserNickRsp struct {
		RespStruct
		ErrorDisplay string
	}

	response := setUserNickRsp{}
	if err := json.Unmarshal(result, &response); err != nil {
		return err
	}

	mylogrus.MyLog.Info("SetUserNick: ", response)

	if response.ErrorCode == 0 {
		return nil
	} else {
		return fmt.Errorf(response.ErrorDisplay)
	}
}

func SetUserNick(userId string, nick string) error {
	if config.AppIsRelease() {
		return SetUserNickBy(userId, nick, overseaSetProfileUrl,
			func() (string, error) {
				return getOverseaAdminSig()
			}, config.GetTxOverSeaAppId())
	}
	return SetUserNickBy(userId, nick, setProfileUrl,
		func() (string, error) {
			return getAdminSig()
		}, config.GetTencentyunAppId())
}

func SetUserNickBy(userId string, nick string, reqUrl string, getAdminSig func() (string, error), appId int) error {
	type ProfileItemType struct {
		Tag   string
		Value string
	}
	type BodyStruct struct {
		From_Account string
		ProfileItem  []ProfileItemType
	}

	sig, err := getAdminSig()
	if err != nil {
		return err
	}
	url := strings.ReplaceAll(strings.ReplaceAll(strings.ReplaceAll(reqUrl,
		"{appid}", strconv.Itoa(appId)),
		"{userSig}", sig),
		"{random}", strconv.FormatUint(uint64(rand.Uint32()), 10))

	body := BodyStruct{
		From_Account: userId,
		ProfileItem:  []ProfileItemType{{Tag: TAG_PROFILE_IM_NICK, Value: nick}},
	}

	jsonStr, err := json.Marshal(body)
	if err != nil {
		return err
	}

	rsp, err := http.Post(url, "application/json", bytes.NewBuffer(jsonStr))
	if err != nil {
		return err
	}
	defer rsp.Body.Close()

	result, err := ioutil.ReadAll(rsp.Body)
	if err != nil {
		return err
	}

	type setUserNickRsp struct {
		RespStruct
		ErrorDisplay string
	}

	response := setUserNickRsp{}
	if err := json.Unmarshal(result, &response); err != nil {
		return err
	}

	mylogrus.MyLog.Info("SetUserNick: ", response)

	if response.ErrorCode == 0 {
		return nil
	} else {
		return fmt.Errorf(response.ErrorDisplay)
	}
}

func SetUserAttr(logger *logrus.Entry, externalId string, key string, value string) error {
	if config.AppIsRelease() {
		return SetUserAttrBy(logger, externalId, key, value, overseaSetAttrUrl,
			func() (string, error) {
				return getOverseaAdminSig()
			}, config.GetTxOverSeaAppId())
	} else {
		return SetUserAttrBy(logger, externalId, key, value, setAttrUrl,
			func() (string, error) {
				return getAdminSig()
			}, config.GetTencentyunAppId())
	}
}

func SetUserAttrBy(logger *logrus.Entry, externalId string, key string, value string, reqUrl string, getAdminSig func() (string, error), appId int) error {
	logger.Infof("SetUserAttrBy, externalId %s, (%s, %s)", externalId, key, value)

	type userAttrElem struct {
		ToAccount string            `json:"To_Account"`
		Attrs     map[string]string `json:"Attrs"`
	}
	type BodyStruct struct {
		UserAttrs []userAttrElem `json:"UserAttrs"`
	}

	sig, err := getAdminSig()
	if err != nil {
		return err
	}
	url := strings.ReplaceAll(strings.ReplaceAll(strings.ReplaceAll(reqUrl,
		"{appid}", strconv.Itoa(appId)),
		"{userSig}", sig),
		"{random}", strconv.FormatUint(uint64(rand.Uint32()), 10))

	body := BodyStruct{UserAttrs: []userAttrElem{{ToAccount: externalId, Attrs: map[string]string{key: value}}}}
	logger.Info("SetUserAttrBy, body: ", body)

	jsonStr, err := json.Marshal(body)
	if err != nil {
		return err
	}

	rsp, err := http.Post(url, "application/json", bytes.NewBuffer(jsonStr))
	if err != nil {
		return err
	}
	defer rsp.Body.Close()

	result, err := ioutil.ReadAll(rsp.Body)
	if err != nil {
		return err
	}
	response := RespStruct{}
	if err := json.Unmarshal(result, &response); err != nil {
		return err
	}
	logger.Info("SetUserAttrBy, rsp: ", response)

	if response.ErrorCode == 0 {
		return nil
	} else {
		return fmt.Errorf("%v", response.ErrorInfo)
	}
}

func C2cSendMsg(model *domain.Model, syncOtherMachine int8, from_Account string, to_Account string, msgType string, text string) error {
	if config.AppIsRelease() {
		return C2cSendMsgBy(model, syncOtherMachine, from_Account, to_Account, msgType, text, overseaC2cSendMsg,
			func() (string, error) {
				return getOverseaAdminSig()
			}, config.GetTxOverSeaAppId())
	} else {
		return C2cSendMsgBy(model, syncOtherMachine, from_Account, to_Account, msgType, text, c2cSendMsg,
			func() (string, error) {
				return getAdminSig()
			}, config.GetTencentyunAppId())
	}
}

//服务器 单聊发消息
//1:把消息同步到 From_Account 在线终端和漫游上; 2:消息不同步至 From_Account;
func C2cSendMsgBy(model *domain.Model, syncOtherMachine int8, from_Account string, to_Account string, msgType string, text string,
	reqUrl string, getAdminSig func() (string, error), appId int) error {
	type MsgContentStruct struct {
		Text string
	}

	type MsgBodyStruct struct {
		MsgType    string
		MsgContent MsgContentStruct
	}

	type BodyStruct struct {
		SyncOtherMachine      int8
		From_Account          string
		To_Account            string
		MsgRandom             int64
		MsgTimeStamp          int64
		ForbidCallbackControl []string
		MsgBody               []MsgBodyStruct
	}

	sig, err := getAdminSig()
	if err != nil {
		return err
	}
	url := strings.ReplaceAll(strings.ReplaceAll(strings.ReplaceAll(reqUrl,
		"{appid}", strconv.Itoa(appId)),
		"{userSig}", sig),
		"{random}", strconv.FormatUint(uint64(rand.Uint32()), 10))
	msgBodys := []MsgBodyStruct{}
	msgBodys = append(msgBodys, MsgBodyStruct{
		MsgType: msgType,
		MsgContent: MsgContentStruct{
			Text: text,
		},
	})
	body := BodyStruct{
		SyncOtherMachine:      syncOtherMachine,
		From_Account:          from_Account,
		To_Account:            to_Account,
		MsgRandom:             time.Now().Unix(),
		MsgTimeStamp:          time.Now().Unix(),
		ForbidCallbackControl: []string{"ForbidAfterSendMsgCallback"},
		MsgBody:               msgBodys,
	}

	jsonStr, err := json.Marshal(body)
	if err != nil {
		return err
	}

	rsp, err := http.Post(url, "application/json", bytes.NewBuffer(jsonStr))
	if err != nil {
		return err
	}
	defer rsp.Body.Close()

	result, err := ioutil.ReadAll(rsp.Body)
	if err != nil {
		return err
	}

	response := RespStruct{}
	if err := json.Unmarshal(result, &response); err != nil {
		return err
	}

	model.Log.Infof("C2cSendMsg send:%v, resp:%v ", bytes.NewBuffer(jsonStr), response)

	if response.ErrorCode == 0 {
		return nil
	} else {
		return fmt.Errorf(fmt.Sprintf("tencentyun C2cSendMsg rsp ErrorCode:%v, ErrorInfo:%v", response.ErrorCode, response.ErrorInfo))
	}
}

//服务器 查询单聊消息
func C2cGetMsg(model *domain.Model, fromAccount, toAccount string, maxCnt int, minTime, maxTime int64) (int64, error) {
	type BodyStruct struct {
		From_Account string
		To_Account   string
		MaxCnt       int
		MinTime      int64
		MaxTime      int64
	}

	sig, err := getOverseaAdminSig()
	if err != nil {
		return 0, err
	}
	url := strings.ReplaceAll(strings.ReplaceAll(strings.ReplaceAll(overseaC2cGetMsg,
		"{appid}", strconv.Itoa(config.GetTxOverSeaAppId())),
		"{userSig}", sig),
		"{random}", strconv.FormatUint(uint64(rand.Uint32()), 10))
	body := BodyStruct{
		From_Account: fromAccount,
		To_Account:   toAccount,
		MaxCnt:       maxCnt,
		MinTime:      minTime,
		MaxTime:      maxTime,
	}

	jsonStr, err := json.Marshal(body)
	if err != nil {
		return 0, err
	}

	rsp, err := http.Post(url, "application/json", bytes.NewBuffer(jsonStr))
	if err != nil {
		return 0, err
	}
	defer rsp.Body.Close()

	result, err := ioutil.ReadAll(rsp.Body)
	if err != nil {
		return 0, err
	}

	type msgContentItem struct {
		Text string
	}
	type msgBodyItem struct {
		MsgType    string
		MsgContent msgContentItem
	}

	type msgListType struct {
		FromAccount     string `json:"From_Account"`
		ToAccount       string `json:"To_Account"`
		MsgSeq          uint64
		MsgRandom       int64
		MsgTimeStamp    int64
		MsgFlagBits     uint
		MsgKey          string
		MsgBody         []msgBodyItem
		CloudCustomData string
	}
	type c2cGetMsgRsp struct {
		RespStruct
		Complete    int
		MsgCnt      uint
		LastMsgTime int64
		LastMsgKey  string
		MsgList     []msgListType
	}

	response := c2cGetMsgRsp{}
	if err := json.Unmarshal(result, &response); err != nil {
		return 0, err
	}

	model.Log.Infof("C2cGetMsg send:%v, resp:%+v ", bytes.NewBuffer(jsonStr), response)

	if response.ErrorCode == 0 {
		return response.LastMsgTime, nil
	} else {
		return 0, fmt.Errorf(fmt.Sprintf("tencentyun C2cGetMsg rsp ErrorCode:%v, ErrorInfo:%v", response.ErrorCode, response.ErrorInfo))
	}
}

func C2cSendCustomMsg(model *domain.Model, syncOtherMachine int8, fromAccount *string, toAccount string, data string, pushContent string) error {
	if config.AppIsRelease() {
		return C2cSendCustomMsgBy(model, syncOtherMachine, fromAccount, toAccount, data, pushContent, overseaC2cSendMsg,
			func() (string, error) {
				return getOverseaAdminSig()
			}, config.GetTxOverSeaAppId())
	} else {
		return C2cSendCustomMsgBy(model, syncOtherMachine, fromAccount, toAccount, data, pushContent, c2cSendMsg,
			func() (string, error) {
				return getAdminSig()
			}, config.GetTencentyunAppId())
	}
}

//服务器 单聊发消息, 自定义消息
//1:把消息同步到 From_Account 在线终端和漫游上; 2:消息不同步至 From_Account;
func C2cSendCustomMsgBy(model *domain.Model, syncOtherMachine int8, fromAccount *string, toAccount string, data string, pushContent string,
	reqUrl string, getAdminSig func() (string, error), appId int) error {
	type MsgContentStruct struct {
		Data  string
		Desc  string
		Ext   string
		Sound string
	}

	type MsgBodyStruct struct {
		MsgType    string
		MsgContent MsgContentStruct
	}

	type MsgOfflinePushInfo struct {
		PushFlag int
		Title    string
		Desc     string
		Ext      string
	}

	type BodyStruct struct {
		SyncOtherMachine      int8
		From_Account          *string
		To_Account            string
		MsgRandom             int64
		ForbidCallbackControl []string
		MsgBody               []MsgBodyStruct
		OfflinePushInfo       MsgOfflinePushInfo
	}

	sig, err := getAdminSig()
	if err != nil {
		return err
	}
	url := strings.ReplaceAll(strings.ReplaceAll(strings.ReplaceAll(reqUrl,
		"{appid}", strconv.Itoa(appId)),
		"{userSig}", sig),
		"{random}", strconv.FormatUint(uint64(rand.Uint32()), 10))
	msgBodys := []MsgBodyStruct{}
	msgBodys = append(msgBodys, MsgBodyStruct{
		MsgType: "TIMCustomElem",
		MsgContent: MsgContentStruct{
			Data: data,
		},
	})
	body := BodyStruct{
		SyncOtherMachine:      syncOtherMachine,
		To_Account:            toAccount,
		From_Account:          fromAccount,
		MsgRandom:             time.Now().Unix(),
		ForbidCallbackControl: []string{"ForbidAfterSendMsgCallback"},
		MsgBody:               msgBodys,
		OfflinePushInfo: MsgOfflinePushInfo{
			PushFlag: 0,
			Desc:     pushContent,
			Ext:      pushContent,
		},
	}

	jsonStr, err := json.Marshal(body)
	if err != nil {
		return err
	}

	rsp, err := http.Post(url, "application/json", bytes.NewBuffer(jsonStr))
	if err != nil {
		return err
	}
	defer rsp.Body.Close()

	result, err := ioutil.ReadAll(rsp.Body)
	if err != nil {
		return err
	}

	type ErrorListItem struct {
		To_Account string
		ErrorCode  int
	}
	type batchSendMsgRsp struct {
		RespStruct
		ErrorList []ErrorListItem
	}
	response := batchSendMsgRsp{}
	if err := json.Unmarshal(result, &response); err != nil {
		return err
	}

	model.Log.Infof("C2cSendCustomMsg send:%v, resp:%+v ", bytes.NewBuffer(jsonStr), response)

	if response.ErrorCode == 0 {
		// 存在部分错误的可能
		if response.ActionStatus != "OK" {
			model.Log.Infof("C2cSendCustomMsg: user %s, err: %v", fromAccount, response.ErrorList)
		}
		return nil
	} else {
		return fmt.Errorf(fmt.Sprintf("tencentyun C2cSendCustomMsg rsp ErrorCode:%v, ErrorInfo:%v", response.ErrorCode, response.ErrorInfo))
	}
}

// (分批)批量发自定义单聊消息
func BatchSendCustomMsg(model *domain.Model, syncOtherMachine int8, fromAccount string, toAccount []string, data string, pushContent string) error {
	for start := 0; start < len(toAccount); start += IM_GET_BATCHSIZE {
		end := start + IM_GET_BATCHSIZE
		if end > len(toAccount) {
			end = len(toAccount)
		}
		toAccounts := toAccount[start:end]
		model.Log.Infof("BatchSendCustomMsg, from %s, offset = %d, size = %d", fromAccount, start, len(toAccounts))

		if err := c2cBatchSendCustomMsg(model, syncOtherMachine, fromAccount, toAccounts, data, pushContent); err != nil {
			return err
		}
	}
	return nil
}

func c2cBatchSendCustomMsg(model *domain.Model, syncOtherMachine int8, fromAccount string, toAccount []string, data string, pushContent string) error {
	if config.AppIsRelease() {
		return c2cBatchSendCustomMsgBy(model, syncOtherMachine, fromAccount, toAccount, data, pushContent, overseaC2cBatchSendMsg,
			func() (string, error) {
				return getOverseaAdminSig()
			}, config.GetTxOverSeaAppId())
	} else {
		return c2cBatchSendCustomMsgBy(model, syncOtherMachine, fromAccount, toAccount, data, pushContent, c2cBatchSendMsg,
			func() (string, error) {
				return getAdminSig()
			}, config.GetTencentyunAppId())
	}
}

//1:把消息同步到 From_Account 在线终端和漫游上; 2:消息不同步至 From_Account;
func c2cBatchSendCustomMsgBy(model *domain.Model, syncOtherMachine int8, fromAccount string, toAccount []string, data string, pushContent string,
	reqUrl string, getAdminSig func() (string, error), appId int) error {
	type MsgContentStruct struct {
		Data  string
		Desc  string
		Ext   string
		Sound string
	}

	type MsgBodyStruct struct {
		MsgType    string
		MsgContent MsgContentStruct
	}

	type MsgOfflinePushInfo struct {
		PushFlag int
		Title    string
		Desc     string
		Ext      string
	}

	type BodyStruct struct {
		SyncOtherMachine int8
		From_Account     string
		To_Account       []string
		MsgRandom        int64
		MsgBody          []MsgBodyStruct
		OfflinePushInfo  MsgOfflinePushInfo
	}

	sig, err := getAdminSig()
	if err != nil {
		return err
	}
	url := strings.ReplaceAll(strings.ReplaceAll(strings.ReplaceAll(reqUrl,
		"{appid}", strconv.Itoa(appId)),
		"{userSig}", sig),
		"{random}", strconv.FormatUint(uint64(rand.Uint32()), 10))
	msgBodys := []MsgBodyStruct{}
	msgBodys = append(msgBodys, MsgBodyStruct{
		MsgType: "TIMCustomElem",
		MsgContent: MsgContentStruct{
			Data: data,
		},
	})
	body := BodyStruct{
		SyncOtherMachine: syncOtherMachine,
		From_Account:     fromAccount,
		To_Account:       toAccount,
		MsgRandom:        time.Now().Unix(),
		MsgBody:          msgBodys,
		OfflinePushInfo: MsgOfflinePushInfo{
			PushFlag: 0,
			Desc:     pushContent,
			Ext:      pushContent,
		},
	}

	jsonStr, err := json.Marshal(body)
	if err != nil {
		return err
	}

	rsp, err := http.Post(url, "application/json", bytes.NewBuffer(jsonStr))
	if err != nil {
		return err
	}
	defer rsp.Body.Close()

	result, err := ioutil.ReadAll(rsp.Body)
	if err != nil {
		return err
	}

	type ErrorListItem struct {
		To_Account string
		ErrorCode  int
	}
	type batchSendMsgRsp struct {
		RespStruct
		ErrorList []ErrorListItem
	}
	response := batchSendMsgRsp{}
	if err := json.Unmarshal(result, &response); err != nil {
		return err
	}

	model.Log.Infof("C2cBatchSendMsg send:%v, resp:%v ", bytes.NewBuffer(jsonStr), response)

	if response.ErrorCode == 0 {
		// 存在部分错误的可能
		if response.ActionStatus != "OK" {
			model.Log.Infof("c2cBatchSendCustomMsg: user %d, err: %v", fromAccount, response.ErrorList)
		}
		return nil
	} else {
		return fmt.Errorf(fmt.Sprintf("tencentyun C2cBatchSendMsg rsp ErrorCode:%v, ErrorInfo:%v", response.ErrorCode, response.ErrorInfo))
	}
}

func BatchSendTextMsg(model *domain.Model, syncOtherMachine int8, fromAccount string, toAccount []string, text string, pushContent string) (int, error) {
	failedCount := 0
	for start := 0; start < len(toAccount); start += IM_GET_BATCHSIZE {
		end := start + IM_GET_BATCHSIZE
		if end > len(toAccount) {
			end = len(toAccount)
		}
		fc, err := batchSendTextMsg(model, syncOtherMachine, fromAccount, toAccount[start:end], text, pushContent)
		if err != nil {
			return failedCount, err
		}
		failedCount += fc
	}
	return failedCount, nil
}

func batchSendTextMsg(model *domain.Model, syncOtherMachine int8, fromAccount string, toAccount []string, text string, pushContent string) (int, error) {
	if config.AppIsRelease() {
		return batchSendTextMsgBy(model, syncOtherMachine, fromAccount, toAccount, text, pushContent, overseaC2cBatchSendMsg,
			func() (string, error) {
				return getOverseaAdminSig()
			}, config.GetTxOverSeaAppId())
	} else {
		return batchSendTextMsgBy(model, syncOtherMachine, fromAccount, toAccount, text, pushContent, c2cBatchSendMsg,
			func() (string, error) {
				return getAdminSig()
			}, config.GetTencentyunAppId())
	}
}

// 批量发送单聊文字消息
func batchSendTextMsgBy(model *domain.Model, syncOtherMachine int8, fromAccount string, toAccount []string, text string, pushContent string,
	reqUrl string, getAdminSig func() (string, error), appId int) (int, error) {
	type MsgContentStruct struct {
		Text string
	}

	type MsgBodyStruct struct {
		MsgType    string
		MsgContent MsgContentStruct
	}

	type MsgOfflinePushInfo struct {
		PushFlag int
		Title    string
		Desc     string
		Ext      string
	}

	type BodyStruct struct {
		SyncOtherMachine int8
		From_Account     string
		To_Account       []string
		MsgRandom        int64
		MsgBody          []MsgBodyStruct
		OfflinePushInfo  MsgOfflinePushInfo
	}

	sig, err := getAdminSig()
	if err != nil {
		return 0, err
	}
	url := strings.ReplaceAll(strings.ReplaceAll(strings.ReplaceAll(reqUrl,
		"{appid}", strconv.Itoa(appId)),
		"{userSig}", sig),
		"{random}", strconv.FormatUint(uint64(rand.Uint32()), 10))
	msgBodys := []MsgBodyStruct{}
	msgBodys = append(msgBodys, MsgBodyStruct{
		MsgType: "TIMTextElem",
		MsgContent: MsgContentStruct{
			Text: text,
		},
	})
	body := BodyStruct{
		SyncOtherMachine: syncOtherMachine,
		From_Account:     fromAccount,
		To_Account:       toAccount,
		MsgRandom:        time.Now().Unix(),
		MsgBody:          msgBodys,
		OfflinePushInfo: MsgOfflinePushInfo{
			PushFlag: 0,
			Desc:     pushContent,
			Ext:      pushContent,
		},
	}

	jsonStr, err := json.Marshal(body)
	if err != nil {
		return 0, err
	}

	rsp, err := http.Post(url, "application/json", bytes.NewBuffer(jsonStr))
	if err != nil {
		return 0, err
	}
	defer rsp.Body.Close()

	result, err := ioutil.ReadAll(rsp.Body)
	if err != nil {
		return 0, err
	}

	type ErrorListItem struct {
		To_Account string
		ErrorCode  int
	}
	type batchSendMsgRsp struct {
		RespStruct
		ErrorList []ErrorListItem
	}
	response := batchSendMsgRsp{}
	if err := json.Unmarshal(result, &response); err != nil {
		return 0, err
	}

	model.Log.Infof("batchSendTextMsgBy send:%v, resp:%v ", bytes.NewBuffer(jsonStr), response)

	if response.ErrorCode == 0 {
		// 存在部分错误的可能
		if response.ActionStatus != "OK" {
			model.Log.Infof("batchSendTextMsgBy: user %s, err: %v", fromAccount, response.ErrorList)
		}
		return len(response.ErrorList), nil
	} else {
		return 0, fmt.Errorf(fmt.Sprintf("tencentyun batchSendTextMsgBy ErrorInfo:%v", response.ErrorInfo))
	}
}

// 海外版和国内版之间交叉发消息
func C2cCrossSendMsg(model *domain.Model, appId int, syncOtherMachine int8, from_Account string, to_Account string, msgType string, msgContent json.RawMessage) error {
	if appId == config.GetTencentyunAppId() {
		return C2cCrossSendMsgBy(model, syncOtherMachine, from_Account, to_Account, msgType, msgContent,
			overseaC2cSendMsg,
			func() (string, error) {
				return getOverseaAdminSig()
			}, config.GetTxOverSeaAppId())
	} else if appId == config.GetTxOverSeaAppId() {
		return C2cCrossSendMsgBy(model, syncOtherMachine, from_Account, to_Account, msgType, msgContent,
			c2cSendMsg,
			func() (string, error) {
				return getAdminSig()
			}, config.GetTencentyunAppId())
	}
	return errors.New("illegal appid")
}

func C2cCrossSendMsgBy(model *domain.Model, syncOtherMachine int8, fromAccount, toAccount string, msgType string, msgContent json.RawMessage,
	reqUrl string, getAdminSig func() (string, error), appId int) error {

	type MsgBodyStruct struct {
		MsgType    string
		MsgContent json.RawMessage
	}

	type MsgOfflinePushInfo struct {
		PushFlag int
		Title    string
		Desc     string
		Ext      string
	}

	type BodyStruct struct {
		SyncOtherMachine      int8
		From_Account          string
		To_Account            string
		MsgRandom             int64
		ForbidCallbackControl []string
		MsgBody               []MsgBodyStruct
	}

	sig, err := getAdminSig()
	if err != nil {
		return err
	}
	url := strings.ReplaceAll(strings.ReplaceAll(strings.ReplaceAll(reqUrl,
		"{appid}", strconv.Itoa(appId)),
		"{userSig}", sig),
		"{random}", strconv.FormatUint(uint64(rand.Uint32()), 10))
	msgBodys := []MsgBodyStruct{}
	msgBodys = append(msgBodys, MsgBodyStruct{
		MsgType:    msgType,
		MsgContent: msgContent,
	})
	body := BodyStruct{
		SyncOtherMachine:      syncOtherMachine,
		To_Account:            toAccount,
		From_Account:          fromAccount,
		MsgRandom:             time.Now().Unix(),
		ForbidCallbackControl: []string{"ForbidAfterSendMsgCallback"},
		MsgBody:               msgBodys,
	}
	model.Log.Infof("C2cCrossSendMsgBy url: %+v, body:%+v", url, body)

	jsonStr, err := json.Marshal(body)
	if err != nil {
		return err
	}

	rsp, err := http.Post(url, "application/json", bytes.NewBuffer(jsonStr))
	if err != nil {
		return err
	}
	defer rsp.Body.Close()

	result, err := ioutil.ReadAll(rsp.Body)
	if err != nil {
		return err
	}

	type ErrorListItem struct {
		To_Account string
		ErrorCode  int
	}
	type batchSendMsgRsp struct {
		RespStruct
		ErrorList []ErrorListItem
	}
	response := batchSendMsgRsp{}
	if err := json.Unmarshal(result, &response); err != nil {
		return err
	}

	model.Log.Infof("C2cCrossSendMsgBy send:%+v, resp:%+v ", bytes.NewBuffer(jsonStr), response)

	if response.ErrorCode == 0 {
		// 存在部分错误的可能
		if response.ActionStatus != "OK" {
			model.Log.Infof("C2cCrossSendMsgBy: user %s, err: %v", fromAccount, response.ErrorList)
		}
		return nil
	} else {
		return fmt.Errorf(fmt.Sprintf("tencentyun C2cCrossSendMsgBy rsp ErrorCode:%v, ErrorInfo:%v", response.ErrorCode, response.ErrorInfo))
	}
}

const (
	ImageFormatJpg   = 1
	ImageFormatGif   = 2
	ImageFormatPng   = 3
	ImageFormatBmp   = 4
	ImageFormatOther = 255
)

func GetImageFormat(format string) int {
	if format == "jpg" {
		return ImageFormatJpg
	} else if format == "gif" {
		return ImageFormatGif
	} else if format == "png" {
		return ImageFormatPng
	} else if format == "bmp" {
		return ImageFormatBmp
	} else {
		return ImageFormatOther
	}
}

const (
	ImageTypeOriginal = 1
	ImageTypeLarge    = 2
	ImageTypeSmall    = 3
)

type ImageInfo struct {
	Type          int
	Size          uint
	Width         uint
	Height        uint
	URL           string
	Download_Flag int
}