/**
 * Copyright (c) 2022 Yansen Zhang
 * wxcomponent is licensed under Mulan PSL v2.
 * You can use this software according to the terms and conditions of the Mulan PSL v2.
 * You may obtain a copy of Mulan PSL v2 at:
 *          http://license.coscl.org.cn/MulanPSL2
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
 * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
 * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 * See the Mulan PSL v2 for more details.
**/

package wxcomponent

import (
	"crypto/sha1"
	"encoding/base64"
	"encoding/binary"
	"encoding/xml"
	"fmt"
	"sort"
	"strconv"
	"strings"
	"time"

	"gitee.com/yansen_zh/wxcomponent/api/authorization"
	"gitee.com/yansen_zh/wxcomponent/api/event"
	"gitee.com/yansen_zh/wxcomponent/config"
	"gitee.com/yansen_zh/wxcomponent/utils"
	"gitee.com/yansen_zh/wxcomponent/utils/encrypt"
	"gitee.com/yansen_zh/wxcomponent/utils/log"
)

// DecodeMessage 解密消息
func DecodeMessage(src []byte) ([]byte, error) {
	log.Info("解码 xml 数据: ", string(src))

	msg := event.ReceivedEncryptMessage{}
	if err := xml.Unmarshal(src, &msg); err != nil {
		log.Error("解码 xml 数据失败: ", err.Error())
		return nil, err
	}

	bt1, e1 := base64.StdEncoding.DecodeString(msg.Encrypt)
	if e1 != nil {
		log.Error("解码 base 数据失败: ", e1.Error())
		log.Error("原始 base64 数据: ", msg.Encrypt)
		return nil, e1
	}

	bt2, e2 := encrypt.AESDecode(bt1, aesKey)
	if e2 != nil {
		log.Error("解码加密数据失败: ", e2.Error())
		log.Error("待解密数据: ", string(bt1))
		return nil, e2
	}

	return bt2, nil
}

// toBigEndian 转换为网络大端序
func toBigEndian(i int) []byte {
	bt := make([]byte, 4)
	binary.BigEndian.PutUint32(bt, uint32(i))
	return bt
}

// EncodeMessage 加密消息
func EncodeMessage(appID string, src []byte) ([]byte, error) {
	log.Info("待加密数据APPID: ", appID)
	log.Info("待加密数据: ", string(src))

	rand16 := []byte(utils.RandStr(16))
	msgLen := toBigEndian(len(src))

	// 待加密数据 = random(16B)+ msg_len(4B) + msg + appid
	data := append(rand16, msgLen...)
	data = append(data, msgLen...)
	data = append(data, src...)
	data = append(data, []byte(appID)...)

	bt1, e1 := encrypt.AESEncode(data, aesKey)
	if e1 != nil {
		log.Error("加密数据失败: ", e1.Error())
		log.Error("待加密数据: ", string(bt1))
		return nil, e1
	}

	encryptStr := base64.StdEncoding.EncodeToString(bt1)
	timestamp := time.Now().Unix()
	timestampStr := strconv.FormatInt(timestamp, 10)
	nonceStr := utils.RandStr(16)

	strs := []string{
		timestampStr,
		nonceStr,
		config.GetAccessToken(),
		encryptStr,
	}
	sort.Strings(strs)
	signature := fmt.Sprintf("%x", sha1.Sum([]byte(strings.Join(strs, ""))))

	evt := event.SendEncryptMessage{
		Encrypt:      event.CDATA{Value: encryptStr},
		MsgSignature: signature,
		TimeStamp:    timestamp,
		Nonce:        nonceStr,
	}

	res, err := xml.Marshal(&evt)
	if err != nil {
		log.Error("转换 xml 数据失败: ", err.Error())
		log.Error("待转换 xml 数据: ", evt)
		return nil, e1
	}

	return res, nil
}

// DecodeAuthEvent 解密事件
func DecodeAuthEvent(src []byte) (*event.AuthEvent, []byte, error) {
	log.Info("待解密数据: ", string(src))

	bt, e1 := DecodeMessage(src)
	if e1 != nil {
		return nil, nil, e1
	}

	evt := event.AuthEvent{}
	if err := xml.Unmarshal(bt, evt); err != nil {
		log.Error("解码 xml 数据失败: ", err.Error())
		log.Error("待解码数据: ", string(bt))
	}

	// 如果是授权成功或者刷新授权, 则更新相关内容
	if evt.InfoType == event.INFO_TYPE_AUTHORIZED || evt.InfoType == event.INFO_TYPE_UPDATEAUTHORIZED {
		if err := handleAuthorized(bt); err != nil {
			return nil, nil, err
		}
	}

	// 如果是取消授权
	if evt.InfoType == event.INFO_TYPE_UNAUTHORIZED {
		handleUnauthorized(bt)
	}

	// 如果是刷新票据
	if evt.InfoType == event.INFO_TYPE_COMPONENT_VERIFY_TICKET {
		if err := handleComponentVerifyTicket(bt); err != nil {
			return nil, nil, err
		}
	}

	return &evt, bt, nil
}

// DecodeAuthorizerEvent 解密授权业务方的消息与事件
func DecodeAuthorizerEvent(appID string, src []byte) (*event.AuthorizerEvent, []byte, error) {
	log.Info("待解密数据APPID: ", appID)
	log.Info("待解密数据: ", string(src))

	bt, e1 := DecodeMessage(src)
	if e1 != nil {
		return nil, nil, e1
	}

	evt := event.AuthorizerEvent{}
	if err := xml.Unmarshal(bt, evt); err != nil {
		log.Error("解码 xml 数据失败: ", err.Error())
		log.Error("待解码数据: ", string(bt))
	}

	authorizer := config.GetAuthorizer()
	e2 := authorizer.ProcessMessage(appID, evt.MsgType.Value, evt.Event.Value, bt)
	if e2 != nil {
		log.Error("处理事件或者消息失败: ", e2.Error())
		log.Error("待处理消息: ", string(bt))
	}

	return &evt, bt, nil
}

// handleAuthorized 授权成功或刷新授权
func handleAuthorized(data []byte) error {
	authorizer := config.GetAuthorizer()
	evtData := event.AuthorizedEvent{}
	xml.Unmarshal(data, &evtData)

	// 获取授权信息
	queryAuth, e1 := authorization.GetQueryAuth(evtData.AuthorizationCode)
	if e1 != nil {
		log.Error("获取授权信息失败: ", e1.Error())
		return e1
	}

	appID := evtData.AuthorizerAppID
	authInfo := queryAuth.AuthorizationInfo
	expire := utils.GetExpireTime(authInfo.ExpiresIn)
	funcLst := make([]int, 0)
	if nil != authInfo.FuncInfo && len(authInfo.FuncInfo) > 0 {
		for _, info := range authInfo.FuncInfo {
			funcLst = append(funcLst, info.FuncscopeCategory.ID)
		}
	}

	// 刷新平台授权状态
	authorizer.RefreshAuthStatus(appID, evtData.InfoType)
	// 刷新授权集
	authorizer.RefreshFuncInfo(appID, funcLst)
	// 刷新 token
	e2 := authorizer.RefreshToken(appID, authInfo.AuthorizerAccessToken, authInfo.AuthorizerRefreshToken, expire)
	if e2 != nil {
		log.Error("刷新公众号或者小程序 Token 失败: ", e2.Error())
		return e2
	}

	return nil
}

// handleUnauthorized 取消授权
func handleUnauthorized(data []byte) error {
	authorizer := config.GetAuthorizer()
	evtData := event.UnauthorizedEvent{}
	xml.Unmarshal(data, &evtData)
	appID := evtData.AuthorizerAppID

	// 刷新平台授权状态
	authorizer.RefreshAuthStatus(appID, evtData.InfoType)
	return nil
}

// handleComponentVerifyTicket 验证票据
func handleComponentVerifyTicket(data []byte) error {
	evtData := event.ComponentVerifyTicketEvent{}
	xml.Unmarshal(data, &evtData)

	expire := utils.GetExpireTime(12 * 3600) // component_verify_ticket 的有效时间为12小时
	err := config.RefreshVerifyTicket(evtData.ComponentVerifyTicket, expire)
	if err != nil {
		log.Error("刷新票据信息失败: ", err.Error())
		return err
	}

	return nil
}