package utils

import (
	"bytes"
	"crypto"
	"crypto/rand"
	"crypto/rsa"
	"crypto/sha1"
	"crypto/sha256"
	"crypto/x509"
	"encoding/base64"
	"encoding/hex"
	"encoding/json"
	"encoding/pem"
	"errors"
	"fmt"
	"io"
	"net/http"
	"net/url"
	"os"
	"sort"
	"strings"
	"time"
)

const (
	Host       = "https://api.mch.weixin.qq.com"
	MethodGET  = "GET"
	MethodPOST = "POST"
)

// MchConfig 商户信息配置，用于调用商户API
// https://pay.weixin.qq.com/doc/v3/merchant/4012716434
//
//	引用微信支付工具库 参考:https://pay.weixin.qq.com/doc/v3/merchant/4015119334
type MchConfig struct {
	mchId                      string
	certificateSerialNo        string
	privateKeyFilePath         string
	wechatPayPublicKeyId       string
	wechatPayPublicKeyFilePath string
	privateKey                 *rsa.PrivateKey
	wechatPayPublicKey         *rsa.PublicKey

	aesKey string
}

// MchId 商户号
func (c *MchConfig) MchId() string {
	return c.mchId
}

// CertificateSerialNo 商户API证书序列号
func (c *MchConfig) CertificateSerialNo() string {
	return c.certificateSerialNo
}

// PrivateKey 商户API证书对应的私钥
func (c *MchConfig) PrivateKey() *rsa.PrivateKey {
	return c.privateKey
}

// WechatPayPublicKeyId 微信支付公钥ID
func (c *MchConfig) WechatPayPublicKeyId() string {
	return c.wechatPayPublicKeyId
}

// WechatPayPublicKey 微信支付公钥
func (c *MchConfig) WechatPayPublicKey() *rsa.PublicKey {
	return c.wechatPayPublicKey
}

// CreateMchConfig MchConfig 构造函数
func CreateMchConfig(
	mchId string,
	certificateSerialNo string,
	privateKeyFilePath string,
	wechatPayPublicKeyId string,
	wechatPayPublicKeyFilePath string,
	aesKey string,
) (*MchConfig, error) {
	mchConfig := &MchConfig{
		mchId:                      mchId,
		certificateSerialNo:        certificateSerialNo,
		privateKeyFilePath:         privateKeyFilePath,
		wechatPayPublicKeyId:       wechatPayPublicKeyId,
		wechatPayPublicKeyFilePath: wechatPayPublicKeyFilePath,
		aesKey:                     aesKey,
	}
	privateKey, err := LoadPrivateKeyWithPath(mchConfig.privateKeyFilePath)
	if err != nil {
		return nil, err
	}
	mchConfig.privateKey = privateKey
	wechatPayPublicKey, err := LoadPublicKeyWithPath(mchConfig.wechatPayPublicKeyFilePath)
	if err != nil {
		return nil, err
	}
	mchConfig.wechatPayPublicKey = wechatPayPublicKey
	return mchConfig, nil
}

// LoadPrivateKey 通过私钥的文本内容加载私钥
func LoadPrivateKey(privateKeyStr string) (privateKey *rsa.PrivateKey, err error) {
	block, _ := pem.Decode([]byte(privateKeyStr))
	if block == nil {
		return nil, fmt.Errorf("decode private key err")
	}
	if block.Type != "PRIVATE KEY" {
		return nil, fmt.Errorf("the kind of PEM should be PRVATE KEY")
	}
	key, err := x509.ParsePKCS8PrivateKey(block.Bytes)
	if err != nil {
		return nil, fmt.Errorf("parse private key err:%s", err.Error())
	}
	privateKey, ok := key.(*rsa.PrivateKey)
	if !ok {
		return nil, fmt.Errorf("not a RSA private key")
	}
	return privateKey, nil
}

// LoadPublicKey 通过公钥的文本内容加载公钥
func LoadPublicKey(publicKeyStr string) (publicKey *rsa.PublicKey, err error) {
	block, _ := pem.Decode([]byte(publicKeyStr))
	if block == nil {
		return nil, errors.New("decode public key error")
	}
	if block.Type != "PUBLIC KEY" {
		return nil, fmt.Errorf("the kind of PEM should be PUBLIC KEY")
	}
	key, err := x509.ParsePKIXPublicKey(block.Bytes)
	if err != nil {
		return nil, fmt.Errorf("parse public key err:%s", err.Error())
	}
	publicKey, ok := key.(*rsa.PublicKey)
	if !ok {
		return nil, fmt.Errorf("%s is not rsa public key", publicKeyStr)
	}
	return publicKey, nil
}

// LoadPrivateKeyWithPath 通过私钥的文件路径内容加载私钥
func LoadPrivateKeyWithPath(path string) (privateKey *rsa.PrivateKey, err error) {
	privateKeyBytes, err := os.ReadFile(path)
	if err != nil {
		return nil, fmt.Errorf("read private pem file err:%s", err.Error())
	}
	return LoadPrivateKey(string(privateKeyBytes))
}

// LoadPublicKeyWithPath 通过公钥的文件路径加载公钥
func LoadPublicKeyWithPath(path string) (publicKey *rsa.PublicKey, err error) {
	publicKeyBytes, err := os.ReadFile(path)
	if err != nil {
		return nil, fmt.Errorf("read certificate pem file err:%s", err.Error())
	}
	return LoadPublicKey(string(publicKeyBytes))
}

// EncryptOAEPWithPublicKey 使用 OAEP padding方式用公钥进行加密
func EncryptOAEPWithPublicKey(message string, publicKey *rsa.PublicKey) (ciphertext string, err error) {
	if publicKey == nil {
		return "", fmt.Errorf("you should input *rsa.PublicKey")
	}
	ciphertextByte, err := rsa.EncryptOAEP(sha1.New(), rand.Reader, publicKey, []byte(message), nil)
	if err != nil {
		return "", fmt.Errorf("encrypt message with public key err:%s", err.Error())
	}
	ciphertext = base64.StdEncoding.EncodeToString(ciphertextByte)
	return ciphertext, nil
}

// SignSHA256WithRSA 通过私钥对字符串以 SHA256WithRSA 算法生成签名信息
func SignSHA256WithRSA(source string, privateKey *rsa.PrivateKey) (signature string, err error) {
	if privateKey == nil {
		return "", fmt.Errorf("private key should not be nil")
	}
	h := crypto.Hash.New(crypto.SHA256)
	_, err = h.Write([]byte(source))
	if err != nil {
		return "", nil
	}
	hashed := h.Sum(nil)
	signatureByte, err := rsa.SignPKCS1v15(rand.Reader, privateKey, crypto.SHA256, hashed)
	if err != nil {
		return "", err
	}
	return base64.StdEncoding.EncodeToString(signatureByte), nil
}

// VerifySHA256WithRSA 通过公钥对字符串和签名结果以 SHA256WithRSA 验证签名有效性
func VerifySHA256WithRSA(source string, signature string, publicKey *rsa.PublicKey) error {
	if publicKey == nil {
		return fmt.Errorf("public key should not be nil")
	}

	sigBytes, err := base64.StdEncoding.DecodeString(signature)
	if err != nil {
		return fmt.Errorf("verify failed: signature is not base64 encoded")
	}
	hashed := sha256.Sum256([]byte(source))
	err = rsa.VerifyPKCS1v15(publicKey, crypto.SHA256, hashed[:], sigBytes)
	if err != nil {
		return fmt.Errorf("verify signature with public key error:%s", err.Error())
	}
	return nil
}

// GenerateNonce 生成一个长度为 NonceLength 的随机字符串（只包含大小写字母与数字）
func GenerateNonce() (string, error) {
	const (
		// NonceSymbols 随机字符串可用字符集
		NonceSymbols = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
		// NonceLength 随机字符串的长度
		NonceLength = 32
	)

	bs := make([]byte, NonceLength)
	_, err := rand.Read(bs)
	if err != nil {
		return "", err
	}
	symbolsByteLength := byte(len(NonceSymbols))
	for i, b := range bs {
		bs[i] = NonceSymbols[b%symbolsByteLength]
	}
	return string(bs), nil
}

// BuildAuthorization 构建请求头中的 Authorization 信息
func BuildAuthorization(
	mchid string,
	certificateSerialNo string,
	privateKey *rsa.PrivateKey,
	method string,
	canonicalURL string,
	body []byte,
) (string, error) {
	const (
		SignatureMessageFormat = "%s\n%s\n%d\n%s\n%s\n" // 数字签名原文格式
		// HeaderAuthorizationFormat 请求头中的 Authorization 拼接格式
		HeaderAuthorizationFormat = "WECHATPAY2-SHA256-RSA2048 mchid=\"%s\",nonce_str=\"%s\",timestamp=\"%d\",serial_no=\"%s\",signature=\"%s\""
	)

	nonce, err := GenerateNonce()
	if err != nil {
		return "", err
	}
	timestamp := time.Now().Unix()
	message := fmt.Sprintf(SignatureMessageFormat, method, canonicalURL, timestamp, nonce, body)
	signature, err := SignSHA256WithRSA(message, privateKey)
	if err != nil {
		return "", err
	}
	authorization := fmt.Sprintf(
		HeaderAuthorizationFormat,
		mchid, nonce, timestamp, certificateSerialNo, signature,
	)
	return authorization, nil
}

// ExtractResponseBody 提取应答报文的 Body
func ExtractResponseBody(response *http.Response) ([]byte, error) {
	if response.Body == nil {
		return nil, nil
	}

	body, err := io.ReadAll(response.Body)
	if err != nil {
		return nil, fmt.Errorf("read response HttpBody err:[%s]", err.Error())
	}
	response.Body = io.NopCloser(bytes.NewBuffer(body))
	return body, nil
}

const (
	WechatPayTimestamp = "Wechatpay-Timestamp" // 微信支付回包时间戳
	WechatPayNonce     = "Wechatpay-Nonce"     // 微信支付回包随机字符串
	WechatPaySignature = "Wechatpay-Signature" // 微信支付回包签名信息
	WechatPaySerial    = "Wechatpay-Serial"    // 微信支付回包平台序列号
	RequestID          = "Request-Id"          // 微信支付回包请求ID
)

// ValidateResponse 验证微信支付回包的签名信息
func ValidateResponse(
	wechatpayPublicKeyId string,
	wechatpayPublicKey *rsa.PublicKey,
	headers *http.Header,
	body []byte,
) error {
	requestID := headers.Get(RequestID)
	timestampStr := headers.Get(WechatPayTimestamp)
	serialNo := headers.Get(WechatPaySerial)
	signature := headers.Get(WechatPaySignature)
	nonce := headers.Get(WechatPayNonce)

	// 拒绝过期请求
	//timestamp, err := strconv.ParseInt(timestampStr, 10, 64)
	//if err != nil {
	//	return fmt.Errorf("invalid timestamp: %v", err)
	//}
	//if time.Now().Sub(time.Unix(timestamp, 0)) > 5*time.Minute {
	//	return errors.New("invalid timestamp")
	//}

	if serialNo != wechatpayPublicKeyId {
		return fmt.Errorf(
			"serial-no mismatch: got %s, expected %s, request-id: %s",
			serialNo,
			wechatpayPublicKeyId,
			requestID,
		)
	}

	message := fmt.Sprintf("%s\n%s\n%s\n", timestampStr, nonce, body)
	if err := VerifySHA256WithRSA(message, signature, wechatpayPublicKey); err != nil {
		return fmt.Errorf("invalid signature: %v, request-id: %s", err, requestID)
	}

	return nil
}

// ApiException 微信支付API错误异常，发送HTTP请求成功，但返回状态码不是 2XX 时抛出本异常
type ApiException struct {
	HttpStatusCode int         // 应答报文的 HTTP 状态码
	HttpHeader     http.Header // 应答报文的 Header 信息
	HttpBody       []byte      // 应答报文的 Body 原文
	ErrCode        string      // 微信支付回包的错误码
	ErrMessage     string      // 微信支付回包的错误信息
}

func (c *ApiException) Error() string {
	buf := bytes.NewBuffer(nil)
	buf.WriteString(fmt.Sprintf("srv error:[StatusCode: %d, Body: %s", c.HttpStatusCode, string(c.HttpBody)))
	if len(c.HttpHeader) > 0 {
		buf.WriteString(" Header: ")
		for key, value := range c.HttpHeader {
			buf.WriteString(fmt.Sprintf("\n - %v=%v", key, value))
		}
		buf.WriteString("\n")
	}
	buf.WriteString("]")
	return buf.String()
}

func (c *ApiException) StatusCode() int {
	return c.HttpStatusCode
}

func (c *ApiException) Header() http.Header {
	return c.HttpHeader
}

func (c *ApiException) Body() []byte {
	return c.HttpBody
}

func (c *ApiException) ErrorCode() string {
	return c.ErrCode
}

func (c *ApiException) ErrorMessage() string {
	return c.ErrMessage
}

func NewApiException(statusCode int, header http.Header, body []byte) error {
	ret := &ApiException{
		HttpStatusCode: statusCode,
		HttpHeader:     header,
		HttpBody:       body,
	}

	bodyObject := map[string]interface{}{}
	if err := json.Unmarshal(body, &bodyObject); err == nil {
		if val, ok := bodyObject["code"]; ok {
			ret.ErrCode = val.(string)
		}
		if val, ok := bodyObject["message"]; ok {
			ret.ErrMessage = val.(string)
		}
	}

	return ret
}

// Time 复制 time.Time 对象，并返回复制体的指针
func Time(t time.Time) *time.Time {
	return &t
}

// String 复制 string 对象，并返回复制体的指针
func String(s string) *string {
	return &s
}

// Bool 复制 bool 对象，并返回复制体的指针
func Bool(b bool) *bool {
	return &b
}

// Float64 复制 float64 对象，并返回复制体的指针
func Float64(f float64) *float64 {
	return &f
}

// Float32 复制 float32 对象，并返回复制体的指针
func Float32(f float32) *float32 {
	return &f
}

// Int64 复制 int64 对象，并返回复制体的指针
func Int64(i int64) *int64 {
	return &i
}

// Int32 复制 int64 对象，并返回复制体的指针
func Int32(i int32) *int32 {
	return &i
}

func (srv *MchConfig) Request(host, method, path string, reqBody []byte) (response []byte, err error) {

	reqUrl, err := url.Parse(fmt.Sprintf("%s%s", host, path))
	if err != nil {
		return nil, err
	}

	fmt.Print(reqUrl.Path)

	httpRequest, err := http.NewRequest(method, reqUrl.String(), bytes.NewReader(reqBody))

	if err != nil {
		return nil, err
	}

	//httpRequest.Header.Set("mchid", srv.mchId)
	httpRequest.Header.Set("Accept", "application/json")
	httpRequest.Header.Set("Wechatpay-Serial", srv.WechatPayPublicKeyId())
	httpRequest.Header.Set("Content-Type", "application/json")

	authorization, err := BuildAuthorization(
		srv.MchId(),
		srv.CertificateSerialNo(),
		srv.PrivateKey(),
		method,
		reqUrl.Path,
		reqBody,
	)
	if err != nil {
		return nil, err
	}

	httpRequest.Header.Set("Authorization", authorization)

	client := &http.Client{}
	httpResponse, err := client.Do(httpRequest)
	if err != nil {
		return nil, err
	}

	respBody, err := ExtractResponseBody(httpResponse)
	if err != nil {
		return nil, err
	}

	if httpResponse.StatusCode >= 200 && httpResponse.StatusCode < 300 {
		// 2XX 成功，验证应答签名
		err = ValidateResponse(
			srv.WechatPayPublicKeyId(),
			srv.WechatPayPublicKey(),
			&httpResponse.Header,
			respBody,
		)
		if err != nil {
			return nil, err
		}

		return respBody, nil
	}

	return nil, &ApiException{
		HttpStatusCode: httpResponse.StatusCode,
		HttpHeader:     httpResponse.Header,
		HttpBody:       respBody,
	}
}

func (srv *MchConfig) Request2(host, method, path string, reqBody []byte) (response []byte, err error) {

	reqUrl, err := url.Parse(fmt.Sprintf("%s%s", host, path))
	if err != nil {
		return nil, err
	}

	httpRequest, err := http.NewRequest(method, reqUrl.String(), bytes.NewReader(reqBody))

	if err != nil {
		return nil, err
	}

	httpRequest.Header.Set("Accept", "application/json")
	httpRequest.Header.Set("Wechatpay-Serial", srv.WechatPayPublicKeyId())
	httpRequest.Header.Set("Content-Type", "application/json")
	httpRequest.Header.Set("mchid", "application/json")

	authorization, err := BuildAuthorization(
		srv.MchId(),
		srv.CertificateSerialNo(),
		srv.PrivateKey(),
		method,
		path,
		reqBody,
	)
	if err != nil {
		return nil, err
	}

	httpRequest.Header.Set("Authorization", authorization)

	client := &http.Client{}
	httpResponse, err := client.Do(httpRequest)
	if err != nil {
		return nil, err
	}

	respBody, err := ExtractResponseBody(httpResponse)
	if err != nil {
		return nil, err
	}

	//hs, _ := json.Marshal(httpRequest.Header)
	//fmt.Printf("\npath=%s\nreqBody=%s\nheaders=%s\n", path, string(reqBody), string(hs))
	//fmt.Printf("\nrespBody=%s\n", string(respBody))

	if httpResponse.StatusCode >= 200 && httpResponse.StatusCode < 300 {
		// 2XX 成功，验证应答签名
		err = ValidateResponse(
			srv.WechatPayPublicKeyId(),
			srv.WechatPayPublicKey(),
			&httpResponse.Header,
			respBody,
		)
		if err != nil {
			return nil, err
		}

		return respBody, nil
	}

	return nil, &ApiException{
		HttpStatusCode: httpResponse.StatusCode,
		HttpHeader:     httpResponse.Header,
		HttpBody:       respBody,
	}
}

func (srv *MchConfig) Verify(request *http.Request) (string, error) {

	respBody, err := io.ReadAll(request.Body)
	if err != nil {
		return "", fmt.Errorf("read request HttpBody err:[%s]", err.Error())
	}

	err = ValidateResponse(
		srv.WechatPayPublicKeyId(),
		srv.WechatPayPublicKey(),
		&request.Header,
		respBody,
	)
	if err != nil {
		return "", err
	}

	return EncryptOAEPWithPublicKey(string(respBody), srv.wechatPayPublicKey)
}

func (srv *MchConfig) GetNotifyBody(headers *http.Header, respBody []byte) (*WxNotifyBody, error) {

	if respBody == nil {
		return nil, fmt.Errorf("request HttpBody is nil")
	}

	err := ValidateResponse(
		srv.WechatPayPublicKeyId(),
		srv.WechatPayPublicKey(),
		headers,
		respBody,
	)
	if err != nil {
		return nil, err
	}

	var wxNotifyBody WxNotifyBody
	if err = json.Unmarshal(respBody, &wxNotifyBody); err != nil {
		return nil, err
	}

	return &wxNotifyBody, nil
}

func (srv *MchConfig) DecodeBody(wxNotifyBody *WxNotifyBody) (string, error) {

	aesUtil, err := NewAesUtil(srv.aesKey)
	if err != nil {
		return "", err
	}

	decryptedText, err := aesUtil.DecryptToString(
		wxNotifyBody.Resource.AssociatedData,
		wxNotifyBody.Resource.Nonce,
		wxNotifyBody.Resource.Ciphertext,
	)
	if err != nil {
		return "", err
	}

	return decryptedText, nil
}

// BuildSortedQueryString 函数接受一个 map，返回按照字段名排序后的 URL 键值对格式字符串
func BuildSortedQueryString(params map[string]any) string {
	// 创建一个字符串切片，用于保存所有的键名
	var keys []string
	for key := range params {
		keys = append(keys, key)
	}

	// 对键名进行 ASCII 字典顺序排序
	sort.Strings(keys)

	// 构建一个 URL 键值对字符串
	var queryStrings []string
	for _, key := range keys {
		// 拼接 key=value
		queryStrings = append(queryStrings, fmt.Sprintf("%s=%v", key, params[key]))
	}

	// 使用 & 连接所有的 key=value 对
	return strings.Join(queryStrings, "&")
}

func Sha1(data string) string {
	// 创建一个 SHA-1 哈希对象
	hash := sha1.New()
	// 写入数据
	hash.Write([]byte(data))
	// 计算并获取加密后的结果
	hashBytes := hash.Sum(nil)
	// 将结果转换为十六进制字符串
	hashString := hex.EncodeToString(hashBytes)
	// 打印加密后的 SHA-1 值
	return hashString
}
