package service

import (
	"context"
	"encoding/json"
	"errors"
	"f5"
	"main/constant"
	"main/mt"
	"q5"
	"time"

	"github.com/wechatpay-apiv3/wechatpay-go/core"
)

type TokenInfo struct {
	GameId int64  `json:"gameid"`
	Token  string `json:"token"`
	Expire int64  `json:"expire"`
}

type expireSession struct {
	SessionTime int64
	Time        int64
}
type wxpay struct {
	gamesGoods   q5.ConcurrentMap[int64, map[int64]GoodsInfo] //[gameid, [goodsid]goodsinfo]
	accessTokens q5.ConcurrentMap[int64, TokenInfo]           //[gameid, TokenInfo]
	expireInfo   q5.ConcurrentMap[string, *expireSession]     //[accountid, expireSession]
	mediaInfo    q5.ConcurrentMap[int64, string]              //[gameid, mediaid]
	refreshflag  bool
	ctx          context.Context
	client       *core.Client
	payhtmlstr   string
}

type WxQuery struct {
	AccessToken string `json:"access_token"`
	Signature   string `json:"signature"`
	SigMethod   string `json:"sig_method"`
	PaySig      string `json:"pay_sig"`
}

type WxBalanceRsp struct {
	ErrCode     int32  `json:"errcode"`
	ErrMsg      string `json:"errmsg"`
	Balance     int64  `json:"balance"`
	GiftBalance int64  `json:"present_balance"`
	SumSave     int64  `json:"sum_save"`
	SumGift     int64  `json:"sum_present"`
	SumBalance  int64  `json:"sum_balance"`
	SumCost     int64  `json:"sum_cost"`
	FirstSave   bool   `json:"first_save"`
}

type WxPayRsp struct {
	ErrCode  int32  `json:"errcode"`
	ErrMsg   string `json:"errmsg"`
	BillNo   string `json:"bill_no"`
	Balance  int64  `json:"balance"`
	UsedGift int64  `json:"used_present_amount"`
}

type WxQueryOrderRsp struct {
	ErrCode      int32  `json:"errcode"`
	ErrMsg       string `json:"errmsg"`
	ProductId    string `json:"product_id"`      //道具id
	PayState     int32  `json:"pay_state"`       //1:未支付;2:已支付
	DeliverState int32  `json:"deliver_state"`   //1:未发货;2:已发货
	PayTime      int64  `json:"pay_finish_time"` //支付完成时间
	TrandNo      string `json:"out_trade_no"`    //用户订单号
	MchOrderNo   string `json:"mch_order_no"`    //微信支付商户单
	Trans        string `json:"transaction_id"`  //微信支付订单号
}

type WxPurchaseNotify struct {
	ToUserName   string `json:"ToUserName"`   //小游戏原始ID
	FromUserName string `json:"FromUserName"` //该事件消息的openid，道具发货场景固定为微信官方的openid
	CreateTime   int64  `json:"CreateTime"`   //消息发送时间
	MsgType      string `json:"MsgType"`      //消息类型，道具发货场景固定为：event
	Event        string `json:"Event"`        //事件类型 商城道具场景固定为：minigame_h5_goods_deliver_notify 道具直购（游戏内）场景固定为：minigame_game_pay_goods_deliver_notify
	MiniGame     struct {
		Payload     string `json:"Payload"`     //	携带的具体内容，格式为json，具体内容如下表格Payload（因为这里需要对消息内容统一签名，所以统一把消息内容设计成json格式）
		PayEventSig string `json:"PayEventSig"` //见https://docs.qq.com/doc/DVVZZdHFsYkttYmxl（PayEventSig）
		IsMock      bool   `json:"IsMock"`      //True: 模拟测试推送  False：真实推送
	} `json:"MiniGame"` //道具直购发货参数
}

type WxPayload struct {
	OpenId     string `json:"OpenId"`     //接收道具的玩家openid
	Env        int32  `json:"Env"`        //环境配置 0：现网环境（也叫正式环境） 1：沙箱环境
	OutTradeNo string `json:"OutTradeNo"` // 订单号
	GoodsInfo  struct {
		ProductId   string `json:"ProductId"`   //游戏道具id标识
		Quantity    int64  `json:"Quantity"`    //购买道具数量
		ZoneId      string `json:"ZoneId"`      //分区
		OrigPrice   int64  `json:"OrigPrice"`   //物品原始价格 （单位：分）
		ActualPrice int64  `json:"ActualPrice"` //物品实际支付价格（单位：分）
		Attach      string `json:"Attach"`      //透传数据
		OrderSource int64  `json:"OrderSource"` //	1 游戏内 2 商城下单 3 商城测试下单
	} `json:"GoodsInfo"` //发货道具
	WeChatPayInfo struct {
		MchOrderNo    string `json:"MchOrderNo"`    //	微信支付商户单号
		TransactionId string `json:"TransactionId"` //	交易单号（微信支付订单号）
	} `json:"WeChatPayInfo"` //微信支付信息（仅微信支付渠道）

}

type GoodsInfo struct {
	Name  string
	Count int64
	Price int64 //
}

func (wp *wxpay) init() {
	wp.gamesGoods = q5.ConcurrentMap[int64, map[int64]GoodsInfo]{}
	wp.accessTokens = q5.ConcurrentMap[int64, TokenInfo]{}
	wp.expireInfo = q5.ConcurrentMap[string, *expireSession]{}

	mt.Table.Wxconfig.Traverse(func(w *mt.Wxconfig) bool {
		filename := "../res/gamegoods/" + q5.SafeToString(w.GetGameid()) + ".json"
		str, err := f5.ReadJsonFile(filename)
		if err != nil {
			return true
		}
		data := []map[string]interface{}{}
		if json.Unmarshal([]byte(str), &data) != nil {
			f5.GetSysLog().Error("parse [%s] error.", filename)
		} else {
			gamegoods := map[int64]GoodsInfo{}
			for _, data := range data {
				gamegoods[q5.SafeToInt64(data["id"])] = GoodsInfo{
					Name:  q5.SafeToString(data["name"]),
					Count: q5.SafeToInt64(data["count"]),
					Price: q5.SafeToInt64(data["price"]) * 100,
				}
			}
			wp.gamesGoods.Store(w.GetGameid(), gamegoods)
			wp.accessTokens.Store(w.GetGameid(), TokenInfo{})
		}
		return true
	})

	go wp.checkExpireInfo()

	wp.refreshflag = true
	go wp.checkAccessToken()

	wp.initMch()
}

func (wp *wxpay) unInit() {
}

func (wp *wxpay) freshAccessToken(gameid int64) (token string) {
	cfg := mt.Table.Wxconfig.GetById(gameid)
	params := map[string]string{
		"grant_type": "client_credential",
		"appid":      cfg.GetAppid(),
		"secret":     cfg.GetAppsecret(),
	}
	urls := mt.Table.Config.GetWxUrl()
	queryuri := "/cgi-bin/token"
	for _, wxhost := range urls {
		url := "https://" + wxhost + queryuri
		sendok := false
		f5.GetHttpCliMgr().SendGoStyleRequest(
			url,
			params,
			func(hcr f5.HttpCliResponse) {
				if hcr.GetErr() != nil {
					return
				}

				rspObj := struct {
					Token   string `json:"access_token"`
					Expire  int64  `json:"expires_in"`
					ErrCode int64  `json:"errcode"`
					ErrMsg  string `json:"errmsg"`
				}{}

				sendok = true
				f5.GetSysLog().Debug("wx get access token rsp:%s", hcr.GetRawData())
				if json.Unmarshal([]byte(hcr.GetRawData()), &rspObj) != nil {
					return
				}

				if rspObj.ErrCode == 0 {
					tokenitem, _ := wp.accessTokens.Load(gameid)
					tokenitem.GameId = gameid
					tokenitem.Token = rspObj.Token
					tokenitem.Expire = rspObj.Expire + f5.GetApp().GetRealSeconds()
					wp.accessTokens.Store(gameid, *tokenitem)
				}

			})

		if sendok {
			break
		}
	}

	return token
}

func (wp *wxpay) getAccessToken(gameid int64) (token string) {
	cfg, exist := wp.accessTokens.Load(gameid)
	nowTime := f5.GetApp().GetRealSeconds()
	if exist {
		if cfg.Expire > nowTime+6 {
			if cfg.Expire < nowTime+30 && !wp.refreshflag {
				wp.refreshflag = true
			}
			return cfg.Token
		}

		if !wp.refreshflag {
			wp.refreshflag = true
		}
		return ""
	}
	return ""
}

func (wp *wxpay) GetGoodsCount(gameid int64, goodsid int64) (count int64, err error) {
	goods, ok := wp.gamesGoods.Load(gameid)
	if !ok {
		return 0, errors.New("no game")
	}

	info, ok := (*goods)[goodsid]
	if !ok {
		return 0, errors.New("no goods")
	}

	return info.Count, nil
}

func (wp *wxpay) QueryBalance(openid string, gameid int64, userip string, sessionkey string) (balance int64, wxerrcode int32, err error) {
	cfg := mt.Table.Wxconfig.GetById(gameid)
	postbody := struct {
		OpenId  string `json:"openid"`
		OfferId string `json:"offer_id"`
		Ts      int64  `json:"ts"`
		ZoneId  string `json:"zone_id"`
		Env     int32  `json:"env"`
		UserIp  string `json:"user_ip"`
	}{
		OpenId:  openid,
		OfferId: cfg.GetOfferid(),
		Ts:      f5.GetApp().GetRealSeconds(),
		ZoneId:  cfg.GetZoneid(),
		UserIp:  userip,
	}

	if !f5.IsOnlineEnv() {
		postbody.Env = 1
	}

	poststr := q5.EncodeJson(postbody)

	queryuri := "/wxa/game/getbalance"
	query := WxQuery{
		AccessToken: wp.getAccessToken(gameid),
		Signature:   wp.GenSHA256Signature(poststr, sessionkey),
		SigMethod:   "hmac_sha256",
		PaySig:      wp.GenSHA256Signature(queryuri+"&"+poststr, cfg.GetAppkey()),
	}

	params := map[string]string{}
	data, _ := json.Marshal(query)
	json.Unmarshal(data, &params)

	sendRequest := false
	urls := mt.Table.Config.GetWxUrl()
	for _, wxhost := range urls {
		url := "https://" + wxhost + queryuri
		f5.GetHttpCliMgr().SendGoStylePost(
			url,
			params,
			"Content-Type: application/json",
			poststr,
			func(rsp f5.HttpCliResponse) {
				if rsp.GetErr() != nil {
					return
				}

				sendRequest = true
				rspJson := WxBalanceRsp{}
				f5.GetSysLog().Debug("wx balance rsp:%s", rsp.GetRawData())
				err = q5.DecodeJson(rsp.GetRawData(), &rspJson)
				if err != nil {
					return
				}

				wxerrcode = rspJson.ErrCode
				switch rspJson.ErrCode {
				case constant.WX_ERRCODE_OK:
					balance = rspJson.Balance
				case constant.WX_ERRCODE_BUSY:
					sendRequest = false
				default:
					f5.GetSysLog().Info("err msg:%s", rspJson.ErrMsg)
					wp.checkErrorCode(rspJson.ErrCode)
				}

			})
		if sendRequest {
			break
		}
	}

	return balance, wxerrcode, err
}

func (wp *wxpay) QueryPay(openid string, gameid int64, userip string, sessionkey string, amount int32, billno string) (wxerrcode int32) {
	cfg := mt.Table.Wxconfig.GetById(gameid)
	postbody := struct {
		OpenId  string `json:"openid"`
		OfferId string `json:"offer_id"`
		Ts      int64  `json:"ts"`
		ZoneId  string `json:"zone_id"`
		Env     int32  `json:"env"`
		UserIp  string `json:"user_ip"`
		Amount  int32  `json:"amount"`
		BillNo  string `json:"bill_no"`
	}{
		OpenId:  openid,
		OfferId: cfg.GetOfferid(),
		Ts:      f5.GetApp().GetRealSeconds(),
		ZoneId:  cfg.GetZoneid(),
		UserIp:  userip,
		Amount:  amount,
		BillNo:  billno,
	}

	if !f5.IsOnlineEnv() {
		postbody.Env = 1
	}

	poststr := q5.EncodeJson(postbody)

	queryuri := "/wxa/game/pay"
	query := WxQuery{
		AccessToken: wp.getAccessToken(gameid),
		Signature:   wp.GenSHA256Signature(poststr, sessionkey),
		SigMethod:   "hmac_sha256",
		PaySig:      wp.GenSHA256Signature(queryuri+"&"+poststr, cfg.GetAppkey()),
	}

	params := map[string]string{}
	data, _ := json.Marshal(query)
	json.Unmarshal(data, &params)

	sendRequest := false
	urls := mt.Table.Config.GetWxUrl()
	for _, wxhost := range urls {
		url := "https://" + wxhost + queryuri
		f5.GetHttpCliMgr().SendGoStylePost(
			url,
			params,
			"Content-Type: application/json",
			poststr,
			func(rsp f5.HttpCliResponse) {
				if rsp.GetErr() != nil {
					return
				}

				sendRequest = true
				rspJson := WxPayRsp{}
				f5.GetSysLog().Debug("wx pay rsp:%s", rsp.GetRawData())
				err := q5.DecodeJson(rsp.GetRawData(), &rspJson)
				if err != nil {
					return
				}

				wxerrcode = rspJson.ErrCode
				switch rspJson.ErrCode {
				case constant.WX_ERRCODE_OK:
				case constant.WX_ERRCODE_BUSY:
					sendRequest = false
				default:
					f5.GetSysLog().Info("err msg:%s", rspJson.ErrMsg)
					wp.checkErrorCode(rspJson.ErrCode)
				}

			})
		if sendRequest {
			break
		}
	}

	return wxerrcode
}

func (wp *wxpay) QueryPurchase(openid string, gameid int64, userip string, sessionkey string, amount int32, tradeno string) (wxerrcode int32) {
	cfg := mt.Table.Wxconfig.GetById(gameid)
	postbody := struct {
		OpenId  string `json:"openid"`
		Ts      int64  `json:"ts"`
		Env     int32  `json:"env"`
		TradeNo string `json:"out_trade_no"`
		BizId   int32  `json:"biz_id"`
		OfferId string `json:"offer_id"`
	}{
		OpenId:  openid,
		Ts:      f5.GetApp().GetRealSeconds(),
		BizId:   2, //1代币 2道具直购
		TradeNo: tradeno,
		OfferId: cfg.GetOfferid(),
	}

	if !f5.IsOnlineEnv() {
		postbody.Env = 1
	}

	poststr := q5.EncodeJson(postbody)

	queryuri := "/wxa/game/queryorderinfo"
	query := WxQuery{
		AccessToken: wp.getAccessToken(gameid),
		Signature:   wp.GenSHA256Signature(poststr, sessionkey),
		SigMethod:   "hmac_sha256",
		PaySig:      wp.GenSHA256Signature(queryuri+"&"+poststr, cfg.GetAppkey()),
	}

	params := map[string]string{}
	data, _ := json.Marshal(query)
	json.Unmarshal(data, &params)

	sendRequest := false
	urls := mt.Table.Config.GetWxUrl()
	for _, wxhost := range urls {
		url := "https://" + wxhost + queryuri
		f5.GetHttpCliMgr().SendGoStylePost(
			url,
			params,
			"Content-Type: application/json",
			poststr,
			func(rsp f5.HttpCliResponse) {
				if rsp.GetErr() != nil {
					return
				}

				sendRequest = true
				rspJson := WxQueryOrderRsp{}
				f5.GetSysLog().Debug("wx query order rsp:%s", rsp.GetRawData())
				err := q5.DecodeJson(rsp.GetRawData(), &rspJson)
				if err != nil {
					return
				}

				wxerrcode = rspJson.ErrCode
				switch rspJson.ErrCode {
				case constant.WX_ERRCODE_OK:
				case constant.WX_ERRCODE_BUSY:
					sendRequest = false
				default:
					f5.GetSysLog().Info("err msg:%s", rspJson.ErrMsg)
					wp.checkErrorCode(rspJson.ErrCode)
				}

			})
		if sendRequest {
			break
		}
	}

	return wxerrcode
}

func (wp *wxpay) AddExpireInfo(accountid string, sessiontime int64) {
	info := expireSession{
		SessionTime: sessiontime,
		Time:        f5.GetApp().GetRealSeconds(),
	}

	wp.expireInfo.Store(accountid, &info)
}

func (wp *wxpay) CheckExpireCache(accountid string, expire int64) bool {
	info, exist := wp.expireInfo.Load(accountid)
	if !exist {
		return false
	}

	if (*info).SessionTime < expire {
		wp.expireInfo.Delete(accountid)
		return false
	}

	return true
}

func (wp *wxpay) GetAccessTokenList(data *[]TokenInfo) {
	wp.accessTokens.Range(func(key int64, value TokenInfo) bool {
		*data = append(*data, value)
		return true
	})
}

func (wp *wxpay) checkExpireInfo() {
	for {
		time.Sleep(time.Minute * 30)
		nowTime := f5.GetApp().GetRealSeconds()
		deletelist := map[string]int64{}
		wp.expireInfo.Range(func(accountid string, value *expireSession) bool {
			if value.Time+3600 < nowTime {
				deletelist[accountid] = value.Time
			}
			return true
		})

		for accountid := range deletelist {
			wp.expireInfo.Delete(accountid)
		}
	}
}

func (wp *wxpay) checkAccessToken() {
	for {
		time.Sleep(time.Second)

		if f5.IsOnlineEnv() {
			wp.gamesGoods.Range(func(gameid int64, value map[int64]GoodsInfo) bool {
				cfg, _ := wp.accessTokens.Load(gameid)
				if wp.refreshflag || (cfg != nil && cfg.Expire <= f5.GetApp().GetRealSeconds()) {
					wp.freshAccessToken(gameid)
				}
				return true
			})
		} else {
			if !wp.refreshflag {
				wp.accessTokens.Range(func(key int64, value TokenInfo) bool {
					if value.Expire <= f5.GetApp().GetRealSeconds() {
						wp.refreshflag = true
						return false
					}
					return true
				})

				if !wp.refreshflag {
					continue
				}
			}
			url := "https://payservice.kingsome.cn/api/service/refresh"

			nowtimestr := q5.SafeToString(f5.GetApp().GetRealSeconds())
			params := map[string]string{
				"time": nowtimestr,
				"sign": wp.GenSHA256Signature(nowtimestr+constant.GLOBAL_SALT, constant.GLOBAL_SALT),
			}
			f5.GetHttpCliMgr().SendGoStyleRequest(
				url,
				params,
				func(hcr f5.HttpCliResponse) {
					if hcr.GetErr() != nil {
						return
					}

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

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

					if rspObj.ErrCode == 0 {
						for _, dataitem := range rspObj.Data {
							wp.accessTokens.Store(dataitem.GameId, dataitem)
						}
					}
				})
		}

		wp.refreshflag = false
	}
}

func (wp *wxpay) checkErrorCode(errcode int32) {
	if errcode == constant.WX_ERRCODE_EXPIRE_ACCESSTOKEN ||
		errcode == constant.WX_ERRCODE_MISSING_ACCESSTOKEN ||
		errcode == constant.WX_ERRCODE_UNAUTH_ACCESSTOKEN {
		if !wp.refreshflag {
			wp.refreshflag = true
		}

	}
}
