package wecom

import (
	"bytes"
	"crypto/aes"
	"crypto/cipher"
	"crypto/sha1"
	"crypto/subtle"
	"encoding/base64"
	"encoding/binary"
	"encoding/hex"
	"sort"
	"strings"

	"github.com/break/junhong_cmp_fiber/pkg/errors"
)

// CallbackCrypto 实现企微回调要求的 SHA-1 签名校验和 AES-256-CBC 解密。
type CallbackCrypto struct {
	token     string
	aesKey    []byte
	receiveID string
}

// NewCallbackCrypto 创建企业微信回调加密器。
func NewCallbackCrypto(token, encodingAESKey, receiveID string) (*CallbackCrypto, error) {
	key, err := base64.StdEncoding.DecodeString(strings.TrimSpace(encodingAESKey) + "=")
	if err != nil || len(key) != 32 || strings.TrimSpace(token) == "" || strings.TrimSpace(receiveID) == "" {
		return nil, errors.New(errors.CodeWeComCredentialInvalid, "企业微信回调 Token、EncodingAESKey 或 receiveid 无效")
	}
	return &CallbackCrypto{token: token, aesKey: key, receiveID: receiveID}, nil
}

// VerifyAndDecrypt 校验签名并解密企业微信回调密文。
func (c *CallbackCrypto) VerifyAndDecrypt(signature, timestamp, nonce, encrypted string) ([]byte, error) {
	if c == nil || !c.validSignature(signature, timestamp, nonce, encrypted) {
		return nil, errors.New(errors.CodeUnauthorized, "企业微信回调签名无效")
	}
	ciphertext, err := base64.StdEncoding.DecodeString(encrypted)
	if err != nil || len(ciphertext) == 0 || len(ciphertext)%aes.BlockSize != 0 {
		return nil, errors.New(errors.CodeInvalidParam, "企业微信回调密文无效")
	}
	block, err := aes.NewCipher(c.aesKey)
	if err != nil {
		return nil, errors.Wrap(errors.CodeInternalError, err, "初始化企业微信回调解密器失败")
	}
	plaintext := make([]byte, len(ciphertext))
	cipher.NewCBCDecrypter(block, c.aesKey[:aes.BlockSize]).CryptBlocks(plaintext, ciphertext)
	plaintext, err = removePKCS7Padding(plaintext)
	if err != nil || len(plaintext) < 20 {
		return nil, errors.New(errors.CodeInvalidParam, "企业微信回调填充无效")
	}
	messageLength := int(binary.BigEndian.Uint32(plaintext[16:20]))
	if messageLength < 0 || 20+messageLength > len(plaintext) {
		return nil, errors.New(errors.CodeInvalidParam, "企业微信回调消息长度无效")
	}
	message := plaintext[20 : 20+messageLength]
	receiveID := plaintext[20+messageLength:]
	if subtle.ConstantTimeCompare(receiveID, []byte(c.receiveID)) != 1 {
		return nil, errors.New(errors.CodeUnauthorized, "企业微信回调 receiveid 不匹配")
	}
	return append([]byte(nil), message...), nil
}

func (c *CallbackCrypto) validSignature(signature, timestamp, nonce, encrypted string) bool {
	parts := []string{c.token, timestamp, nonce, encrypted}
	sort.Strings(parts)
	hash := sha1.Sum([]byte(strings.Join(parts, "")))
	expected := hex.EncodeToString(hash[:])
	return subtle.ConstantTimeCompare([]byte(strings.ToLower(signature)), []byte(expected)) == 1
}

func removePKCS7Padding(value []byte) ([]byte, error) {
	if len(value) == 0 {
		return nil, errors.New(errors.CodeInvalidParam)
	}
	padding := int(value[len(value)-1])
	if padding <= 0 || padding > 32 || padding > len(value) {
		return nil, errors.New(errors.CodeInvalidParam)
	}
	if !bytes.Equal(value[len(value)-padding:], bytes.Repeat([]byte{byte(padding)}, padding)) {
		return nil, errors.New(errors.CodeInvalidParam)
	}
	return value[:len(value)-padding], nil
}
