package mainservice

import (
	"crypto/sha1"
	"encoding/hex"
	"encoding/json"
	"f5"
	"fmt"
	"io"
	"main/constant"
	"main/model"
	"main/mt"
	"main/service"
	"q5"
	"sort"
	"strings"

	"github.com/gin-gonic/gin"
)

type MainServiceApi struct {
}

func (this *MainServiceApi) RefreshToken(c *gin.Context) {
	reqtime := q5.SafeToInt64(c.Query("time"))
	reqsign := c.Query("sign")
	nowTime := f5.GetApp().GetRealSeconds()
	if reqtime > nowTime+30 || reqtime+30 < nowTime {
		f5.RspErr(c, 400, "request error")
		return
	}

	sign := service.Wxpay.GenSHA256Signature(c.Query("time")+constant.GLOBAL_SALT, constant.GLOBAL_SALT)
	if reqsign != sign {
		f5.RspErr(c, 400, "request error 2")
		return
	}

	rspObj := struct {
		Data    []service.TokenInfo `json:"data"`
		ErrCode int64               `json:"errcode"`
		ErrMsg  string              `json:"errmsg"`
	}{}

	service.Wxpay.GetAccessTokenList(&rspObj.Data)
	c.JSON(200, rspObj)
}

func (this *MainServiceApi) WxTNotify(c *gin.Context) {
	f5.GetSysLog().Debug("wx test notify:%s", c.Request.URL.RawQuery)

	signature := c.Query("signature")
	timestamp := c.Query("timestamp")
	nonce := c.Query("nonce")
	echostr := c.Query("echostr")
	strs := []string{mt.Table.Config.GetWxNotifyToken(), timestamp, nonce}
	sort.Strings(strs)
	sb := strings.Builder{}
	sb.WriteString(strs[0])
	sb.WriteString(strs[1])
	sb.WriteString(strs[2])
	m := sha1.New()
	io.WriteString(m, sb.String())
	sign := string(hex.EncodeToString(m.Sum(nil)))

	f5.GetSysLog().Debug("wx sign:%s, %s", sign, signature)

	if sign != signature {
		c.String(200, "wrong")
		return
	}
	c.String(200, echostr)
}

func (this *MainServiceApi) WxNotifyPurchase(c *gin.Context) {
	f5.GetSysLog().Debug("wx notify purchase:%s", c.Request.URL.RawQuery)

	timestamp := c.Query("timestamp")
	nonce := c.Query("nonce")

	rspObj := struct {
		ErrorCode int32  `json:"ErrCode"`
		ErrMsg    string `json:"ErrMsg"`
	}{
		ErrorCode: 99999,
		ErrMsg:    "internal error",
	}

	msg_signature := c.Query("msg_signature")
	if msg_signature != "" {
		postObj := struct {
			Encrypt    string `json:"Encrypt"`
			ToUserName string `json:"ToUserName"`
		}{}

		if err := c.ShouldBindJSON(&postObj); err != nil {
			rspObj.ErrorCode = 401
			rspObj.ErrMsg = "post data error"
			c.JSON(200, rspObj)
			return
		}

		smsg, appid := service.Wxpay.DecryptMsg(msg_signature, timestamp, nonce, postObj.Encrypt, mt.Table.Config.GetWxNotifyToken(), mt.Table.Config.GetWxNotifyEncodingAesKey())
		if len(smsg) == 0 || len(appid) == 0 {
			f5.GetSysLog().Debug("decrypt data error")
			c.JSON(200, rspObj)
			return
		}

		f5.GetSysLog().Debug("wx decrypt msg:%s", smsg)

		wxnotifyobj := service.WxPurchaseNotify{}
		if json.Unmarshal(smsg, &wxnotifyobj) != nil {
			f5.GetSysLog().Debug("unmarshal data error")
			c.JSON(200, rspObj)
			return
		}

		gameid := int64(0)
		appkey := ""
		notifyurl := ""
		mt.Table.Wxconfig.Traverse(func(w *mt.Wxconfig) bool {
			if w.GetAppid() == string(appid) {
				gameid = w.GetGameid()
				appkey = w.GetAppkey()
				notifyurl = w.GetNotifyurl()
				return false
			}
			return true
		})

		if appkey == "" {
			f5.GetSysLog().Error("wx app config error:%s", appid)
			c.JSON(200, rspObj)
			return
		}

		oristr := wxnotifyobj.Event + "&" + wxnotifyobj.MiniGame.Payload
		sig := service.Wxpay.GenSHA256Signature(oristr, appkey)
		if sig != wxnotifyobj.MiniGame.PayEventSig {
			f5.GetSysLog().Error("pay event sig error:%s, %s, %s", appid, sig, wxnotifyobj.MiniGame.PayEventSig)
			c.JSON(200, rspObj)
			return
		}

		if wxnotifyobj.MiniGame.IsMock {
			rspObj.ErrorCode = 0
			rspObj.ErrMsg = "Success"
			c.JSON(200, rspObj)
			return
		}

		payloadobj := new(service.WxPayload)
		if json.Unmarshal([]byte(wxnotifyobj.MiniGame.Payload), &payloadobj) != nil {
			c.JSON(200, rspObj)
			return
		}

		envpass := true
		if f5.IsOnlineEnv() {
			if payloadobj.Env != 0 {
				f5.GetSysLog().Error("notify test info to prod url")
				envpass = false
			}
		} else {
			if payloadobj.Env != 1 {
				f5.GetSysLog().Error("notify prod info to test url")
				envpass = false
			}
		}

		if !envpass {
			c.JSON(200, rspObj)
			return
		}

		orderModel := new(model.InAppOrder)
		if err, found := orderModel.FindByOrderId(payloadobj.OutTradeNo); err != nil {
			c.JSON(200, rspObj)
			return
		} else if !found {
			c.JSON(200, rspObj)
			return
		}

		if orderModel.ItemId != q5.SafeToInt32(payloadobj.GoodsInfo.ProductId) {
			c.JSON(200, rspObj)
			return
		}

		if orderModel.Status > 1 {
			rspObj.ErrorCode = 0
			rspObj.ErrMsg = "Success"
			c.JSON(200, rspObj)
			return
		}

		rediskey := "ls:accountid:" + orderModel.AccountId
		str, err := service.Redis.Get(constant.LOGIN_REDIS, rediskey)
		if err != nil {
			c.JSON(200, rspObj)
			return
		}

		data := map[string]interface{}{}
		if json.Unmarshal([]byte(str), &data) != nil {
			c.JSON(200, rspObj)
			return
		}

		openid := q5.SafeToString(data["openid"])
		if openid != payloadobj.OpenId {
			c.JSON(200, rspObj)
			return
		}

		orderModel.GameId = int32(gameid)
		f5.GetSysLog().Debug("notify url:%s, %s", appid, notifyurl)

		nowtimestr := q5.SafeToString(f5.GetApp().GetRealSeconds())
		originstr := "account_id=" + orderModel.AccountId
		originstr += "&goodsid=" + payloadobj.GoodsInfo.ProductId
		originstr += "&orderid=" + orderModel.OrderId
		originstr += "&amount=" + q5.SafeToString(payloadobj.GoodsInfo.ActualPrice)
		originstr += ":" + nowtimestr + constant.NOFITY_GAMESERVER_SALT
		params := map[string]string{
			"c":          "Recharge",
			"a":          "purchaseNotify",
			"account_id": orderModel.AccountId,
			"orderid":    orderModel.OrderId,
			"timestamp":  nowtimestr,
			"goodsid":    payloadobj.GoodsInfo.ProductId,
			"amount":     q5.SafeToString(payloadobj.GoodsInfo.ActualPrice),
			"sign":       q5.Md5Str(originstr),
		}
		f5.GetHttpCliMgr().SendGoStyleRequest(
			notifyurl,
			params,
			func(hcr f5.HttpCliResponse) {
				if hcr.GetErr() != nil {
					return
				}

				gamerspObj := struct {
					ErrCode int64  `json:"errcode"`
					ErrMsg  string `json:"errmsg"`
				}{}

				f5.GetSysLog().Debug("get game rsp:%s", hcr.GetRawData())
				if json.Unmarshal([]byte(hcr.GetRawData()), &gamerspObj) != nil {
					return
				}

				if gamerspObj.ErrCode == 0 {
					orderModel.Status = 2
					orderModel.UpdateFields([]string{"status"})
					rspObj.ErrorCode = 0
					rspObj.ErrMsg = "Success"
				}
			})
	}

	c.JSON(200, rspObj)
}

func (this *MainServiceApi) WxMsgTNotify(c *gin.Context) {
	f5.GetSysLog().Debug("wx msg test notify:%s", c.Request.URL.RawQuery)

	signature := c.Query("signature")
	timestamp := c.Query("timestamp")
	nonce := c.Query("nonce")
	echostr := c.Query("echostr")
	strs := []string{mt.Table.Config.GetWxMsgNotifyToken(), timestamp, nonce}
	sort.Strings(strs)
	sb := strings.Builder{}
	sb.WriteString(strs[0])
	sb.WriteString(strs[1])
	sb.WriteString(strs[2])
	m := sha1.New()
	io.WriteString(m, sb.String())
	sign := string(hex.EncodeToString(m.Sum(nil)))

	f5.GetSysLog().Debug("wx t msg sign:%s, %s", sign, signature)

	if sign != signature {
		c.String(200, "wrong")
		return
	}
	c.String(200, echostr)
}

func (this *MainServiceApi) WxMsgNotify(c *gin.Context) {
	f5.GetSysLog().Debug("wx msg notify:%s", c.Request.URL.RawQuery)

	timestamp := c.Query("timestamp")
	nonce := c.Query("nonce")

	rspObj := struct {
		ErrorCode int32  `json:"ErrCode"`
		ErrMsg    string `json:"ErrMsg"`
	}{
		ErrorCode: 99999,
		ErrMsg:    "internal error",
	}

	msg_signature := c.Query("msg_signature")
	if msg_signature != "" {
		postObj := struct {
			ToUserName string `json:"ToUserName"` //小程序的原始 ID
			Encrypt    string `json:"Encrypt"`
		}{}

		if err := c.ShouldBindJSON(&postObj); err != nil {
			f5.GetSysLog().Debug("post data error ")
			c.JSON(200, rspObj)
			return
		}

		smsg, appid := service.Wxpay.DecryptMsg(msg_signature, timestamp, nonce, postObj.Encrypt, mt.Table.Config.GetWxMsgNotifyToken(), mt.Table.Config.GetWxMsgNotifyEncodingAesKey())
		if len(smsg) == 0 || len(appid) == 0 {
			f5.GetSysLog().Debug("decrypt msg data error")
			c.JSON(200, rspObj)
			return
		}

		rspObj.ErrorCode = 0
		rspObj.ErrMsg = "success"
		defer c.JSON(200, rspObj)

		wxmsg := struct {
			ToUserName   string `json:"ToUserName"`   //小程序的原始 ID
			FromUserName string `json:"FromUserName"` //发送者的 openid
			CreateTime   int64  `json:"CreateTime"`
			MsgType      string `json:"MsgType"`
			Content      string `json:"Content"`
			MediaId      string `json:"MediaId"`
			PicUrl       string `json:"PicUrl"`
			MsgId        int64  `json:"MsgId"`
			Event        string `json:"Event"`
			SessionFrom  string `json:"SessionFrom"`
		}{}

		f5.GetSysLog().Debug("wx msg notify decrypted data:%s", smsg)
		if err := json.Unmarshal(smsg, &wxmsg); err != nil {
			f5.GetSysLog().Debug("decrypt msg data error")
			return
		}

		if wxmsg.MsgType != "event" || wxmsg.Event != "user_enter_tempsession" {
			return
		}

		strs := q5.StrSplit(wxmsg.SessionFrom, ":")
		if len(strs) < 2 || strs[0] != "prepare_pay_from_game" {
			return
		}

		orderid := strs[1]
		orderModel := new(model.InAppOrder)
		if err, found := orderModel.FindByOrderId(orderid); err != nil || !found {
			return
		}

		thumburl := service.Wxpay.GenThumburl(int64(orderModel.GameId))

		// 发给客服系统的clicklink
		clickurl := fmt.Sprintf("%s/wx/clicknotify?orderid=%s", mt.Table.Config.GetWxNofityHost(), orderid)
		req := struct {
			ToUser  string      `json:"touser"`
			MsgType string      `json:"msgtype"`
			Link    interface{} `json:"link"`
		}{
			ToUser:  wxmsg.FromUserName,
			MsgType: "link",
			Link: struct {
				Title       string `json:"title"`
				Description string `json:"description"`
				Url         string `json:"url"`
				Thumb_url   string `json:"thumb_url"`
			}{
				Title:       mt.Table.Config.GetWxLinkTitle(),
				Description: mt.Table.Config.GetWxLinkDescription(),
				Url:         clickurl,
				Thumb_url:   thumburl,
			},
		}

		postbytes, err := json.Marshal(req)

		dstUrl := service.Wxpay.GenSendWxMsgUrl(int64(orderModel.GameId))
		f5.GetHttpCliMgr().SendGoStylePost(
			dstUrl,
			map[string]string{},
			"Content-Type: application/json",
			string(postbytes),
			func(hcr f5.HttpCliResponse) {
				if hcr.GetErr() != nil {
					f5.GetSysLog().Error("Post wx custom msg err:%v dstUrl:%v", hcr.GetErr().Error(), dstUrl)
					return
				}

				rspJson := struct {
					Errcode int    `json:"errcode"` // 0为成功
					Errmsg  string `json:"errmsg"`  //
				}{}

				f5.GetSysLog().Debug("send wx custom msg rsp:%s", hcr.GetRawData())
				err = q5.DecodeJson(hcr.GetRawData(), &rspJson)
				if err != nil {
					return
				}

				if rspJson.Errcode != 0 {
					f5.GetSysLog().Error("send wx custom msg error:%v", rspJson.Errmsg)
				}
			})
	} else {
		c.JSON(200, rspObj)
	}
}

func (this *MainServiceApi) WxClickNotify(c *gin.Context) {
	// balabala 校验代码
	orderid := c.DefaultQuery("orderid", "")
	if len(orderid) == 0 {
		c.String(200, "")
		return
	}

	orderModel := new(model.InAppOrder)
	if err, found := orderModel.FindByOrderId(orderid); err != nil || !found {
		c.String(200, "")
		return
	}

	strs := strings.Split(orderModel.AccountId, "_")
	if len(strs) < 3 {
		c.String(200, "")
		return
	}

	rediskey := "ls:accountid:" + orderModel.AccountId
	str, err := service.Redis.Get(constant.LOGIN_REDIS, rediskey)
	if err != nil {
		c.String(200, "")
		return
	}

	data := map[string]interface{}{}
	if json.Unmarshal([]byte(str), &data) != nil {
		c.String(200, "")
		return
	}

	rspstr := service.Wxpay.GetPrepayInfoStr(q5.SafeToString(data["openid"]), int64(orderModel.GameId), orderModel.IP, orderid, int64(orderModel.ItemId))

	c.Header("Content-Type", "text/html")
	c.String(200, rspstr)
}

func (this *MainServiceApi) WxPayNotify(c *gin.Context) {
	f5.GetSysLog().Debug("wx pay notify:%s", c.Request.URL.RawQuery)

	failrspobj := struct {
		Code string `json:"code"`

		Msg string `json:"message"`
	}{
		Code: "FAIL",
		Msg:  "失败",
	}

	payserial := c.GetHeader("Wechatpay-Serial") //验签的微信支付平台证书序列号/微信支付公钥ID
	if mt.Table.Config.GetWxCertificateSn() != payserial {
		f5.GetSysLog().Alert("!!!!!!!!!!cerificate Serial error")
		c.JSON(501, failrspobj)
		return
	}

	paysign := c.GetHeader("Wechatpay-Signature")      //验签的签名值
	paytimestamp := c.GetHeader("Wechatpay-Timestamp") //验签的时间戳
	paynonce := c.GetHeader("Wechatpay-Nonce")         //验签的随机字符串
	nowtime := f5.GetApp().GetRealSeconds()
	paytime := q5.SafeToInt64(paytimestamp)
	if paytime < nowtime-60 || paytime > nowtime+60 {
		c.JSON(501, failrspobj)
		return
	}

	rawdata, err := c.GetRawData()
	if err != nil {
		c.JSON(501, failrspobj)
		return
	}

	f5.GetSysLog().Debug("wx pay post data:%s", rawdata)
	rawstr := fmt.Sprintf("%s\n%s\n%s", paytimestamp, paynonce, rawdata)
	if !service.Wxpay.VerifyPaySign(rawstr, paysign) {
		c.JSON(501, failrspobj)
		return
	}

	postbodyObj := struct {
		ID           string `json:"id"`
		CreateTime   string `json:"create_time"`
		ResourceType string `json:"resource_type"`
		EventType    string `json:"event_type"`
		Summary      string `json:"summary"`
		Resource     struct {
			OT        string `json:"original_type"`
			Algorithm string `json:"algorithm"`
			CT        string `json:"ciphertext"`
			AD        string `json:"associated_data"`
			Nonce     string `json:"nonce"`
		} `json:"resource"`
	}{}

	if json.Unmarshal(rawdata, &postbodyObj) != nil {
		f5.GetSysLog().Debug("unmarshal wx pay post data errpr")
		c.JSON(501, failrspobj)
		return
	}

	res := postbodyObj.Resource
	decryptedData, err := service.Wxpay.DecryptPaydata(res.AD, res.Nonce, res.CT)
	if err != nil {
		f5.GetSysLog().Debug("decrypte pay data error:%s, %s, %s", res.AD, res.Nonce, res.CT)
		c.JSON(501, failrspobj)
		return
	}

	f5.GetSysLog().Debug("pay decripted data:%s", decryptedData)
	/*
			{
		    "transaction_id":"1217752501201407033233368018",
		    "amount":{
		        "payer_total":100,
		        "total":100,
		        "currency":"CNY",
		        "payer_currency":"CNY"
		    },
		    "mchid":"1230000109",
		    "trade_state":"SUCCESS",
		    "bank_type":"CMC",
		    "promotion_detail":[
		        {
		            "amount":100,
		            "wechatpay_contribute":0,
		            "coupon_id":"109519",
		            "scope":"GLOBAL",
		            "merchant_contribute":0,
		            "name":"单品惠-6",
		            "other_contribute":0,
		            "currency":"CNY",
		            "stock_id":"931386",
		            "goods_detail":[
		                {
		                    "goods_remark":"商品备注信息",
		                    "quantity":1,
		                    "discount_amount":1,
		                    "goods_id":"M1006",
		                    "unit_price":100
		                },
		                {
		                    "goods_remark":"商品备注信息",
		                    "quantity":1,
		                    "discount_amount":1,
		                    "goods_id":"M1006",
		                    "unit_price":100
		                }
		            ]
		        },
		        {
		            "amount":100,
		            "wechatpay_contribute":0,
		            "coupon_id":"109519",
		            "scope":"GLOBAL",
		            "merchant_contribute":0,
		            "name":"单品惠-6",
		            "other_contribute":0,
		            "currency":"CNY",
		            "stock_id":"931386",
		            "goods_detail":[
		                {
		                    "goods_remark":"商品备注信息",
		                    "quantity":1,
		                    "discount_amount":1,
		                    "goods_id":"M1006",
		                    "unit_price":100
		                },
		                {
		                    "goods_remark":"商品备注信息",
		                    "quantity":1,
		                    "discount_amount":1,
		                    "goods_id":"M1006",
		                    "unit_price":100
		                }
		            ]
		        }
		    ],
		    "success_time":"2018-06-08T10:34:56+08:00",
		    "payer":{
		        "openid":"oUpF8uMuAJO_M2pxb1Q9zNjWeS6o"
		    },
		    "out_trade_no":"1217752501201407033233368018",
		    "appid":"wxd678efh567hg6787",
		    "trade_state_desc":"支付成功",
		    "trade_type":"APP",
		    "attach":"自定义数据",
		    "scene_info":{
		        "device_id":"013467007045764"
		    }
		}

	*/
	resObj := struct {
		TransId string `json:"transaction_id"`
		Amount  struct {
			PayerTotal int64 `json:"payer_total"`
			Total      int64 `json:"total"`
		} `json:"amount"`
		MchId      string `json:"mchid"`
		TradeState string `json:"trade_state"`
		Payer      struct {
			Openid string `json:"openid"`
		} `json:"payer"`
		OrderId   string `json:"out_trade_no"`
		AppId     string `json:"appid"`
		SceneInfo struct {
			DeviceId string `json:"device_id"`
		} `json:"scene_info"`
	}{}
	err = json.Unmarshal([]byte(decryptedData), &resObj)
	if err != nil {
		f5.GetSysLog().Debug("unmarshal resObj err:%s", err.Error())
		c.JSON(501, failrspobj)
		return
	}

	if resObj.TradeState != "SUCCESS" {
		c.JSON(501, failrspobj)
		return
	}

	if resObj.OrderId == "" ||
		resObj.AppId == "" ||
		resObj.MchId != mt.Table.Config.GetWxMerchantId() {
		c.JSON(501, failrspobj)
		return
	}

	gameid := int64(0)
	notifyurl := ""
	mt.Table.Wxconfig.Traverse(func(w *mt.Wxconfig) bool {
		if w.GetAppid() == string(resObj.AppId) {
			gameid = w.GetGameid()
			notifyurl = w.GetNotifyurl()
			return false
		}
		return true
	})

	if gameid == 0 {
		c.JSON(501, failrspobj)
		return
	}

	orderModel := new(model.InAppOrder)
	if err, found := orderModel.FindByOrderId(resObj.OrderId); err != nil || !found {
		c.JSON(501, failrspobj)
		return
	}

	if orderModel.Status > 1 {
		c.String(200, "")
		return
	}

	rediskey := "ls:accountid:" + orderModel.AccountId
	sessionstr, err := service.Redis.Get(constant.LOGIN_REDIS, rediskey)
	if err != nil {
		c.JSON(501, failrspobj)
		return
	}

	data := map[string]interface{}{}
	if json.Unmarshal([]byte(sessionstr), &data) != nil {
		c.JSON(501, failrspobj)
		return
	}

	openid := q5.SafeToString(data["openid"])
	if openid != resObj.Payer.Openid {
		c.JSON(501, failrspobj)
		return
	}

	f5.GetSysLog().Debug("notify url:%d, %s", gameid, notifyurl)
	if len(notifyurl) > 0 {
		goodsidstr := q5.SafeToString(orderModel.ItemId)
		totalamountstr := q5.SafeToString(resObj.Amount.Total)

		nowtimestr := q5.SafeToString(f5.GetApp().GetRealSeconds())
		originstr := "account_id=" + orderModel.AccountId
		originstr += "&goodsid=" + goodsidstr
		originstr += "&orderid=" + orderModel.OrderId
		originstr += "&amount=" + totalamountstr
		originstr += ":" + nowtimestr + constant.NOFITY_GAMESERVER_SALT
		params := map[string]string{
			"c":          "Recharge",
			"a":          "purchaseNotify",
			"account_id": orderModel.AccountId,
			"orderid":    orderModel.OrderId,
			"timestamp":  nowtimestr,
			"goodsid":    goodsidstr,
			"amount":     totalamountstr,
			"sign":       q5.Md5Str(originstr),
		}
		f5.GetHttpCliMgr().SendGoStyleRequest(
			notifyurl,
			params,
			func(hcr f5.HttpCliResponse) {
				if hcr.GetErr() != nil {
					return
				}

				gamerspObj := struct {
					ErrCode int64  `json:"errcode"`
					ErrMsg  string `json:"errmsg"`
				}{}

				f5.GetSysLog().Debug("get game rsp:%s", hcr.GetRawData())
				if json.Unmarshal([]byte(hcr.GetRawData()), &gamerspObj) != nil {
					return
				}

				if gamerspObj.ErrCode == 0 {
					orderModel.Status = 2
				} else {
					orderModel.Status = 1
				}
			})
	} else {
		orderModel.Status = 1
	}
	orderModel.SpOrderId = resObj.TransId
	count, _ := service.Wxpay.GetGoodsCount(gameid, int64(orderModel.ItemId))
	orderModel.SpAmount = int32(count)
	orderModel.UpdateFields([]string{"status", "sp_orderid", "sp_amount"})

	c.String(200, "")
}
