package weapp

import (
	"bytes"
	"crypto/aes"
	"crypto/cipher"
	"crypto/rand"
	"crypto/sha1"
	"encoding/hex"
	"errors"
	"io"
	"sort"
	"strings"
)

const pkcs7blocksize = 32

// pkcs7encode 对需要加密的明文进行填充补位
// plaintext 需要进行填充补位操作的明文
// 返回补齐明文字符串
func pkcs7encode(plaintext []byte) []byte {
	//计算需要填充的位数
	pad := pkcs7blocksize - len(plaintext)%pkcs7blocksize
	if pad == 0 {
		pad = pkcs7blocksize
	}

	//获得补位所用的字符
	text := bytes.Repeat([]byte{byte(pad)}, pad)

	return append(plaintext, text...)
}

// pkcs7decode 对解密后的明文进行补位删除
// plaintext 解密后的明文
// 返回删除填充补位后的明文和
func pkcs7decode(plaintext []byte) []byte {
	ln := len(plaintext)

	// 获取最后一个字符的 ASCII
	pad := int(plaintext[ln-1])
	if pad < 1 || pad > pkcs7blocksize {
		pad = 0
	}

	return plaintext[:(ln - pad)]
}

// 对加密数据包进行签名校验，确保数据的完整性。
func validateSignature(signature string, parts ...string) bool {
	return signature == createSignature(parts...)
}

// 校验用户数据数据
func validateUserInfo(signature, rawData, ssk string) bool {
	raw := sha1.Sum([]byte(rawData + ssk))
	return signature == hex.EncodeToString(raw[:])
}

// 拼凑签名
func createSignature(parts ...string) string {
	sort.Strings(parts)
	raw := sha1.Sum([]byte(strings.Join(parts, "")))

	return hex.EncodeToString(raw[:])
}

// cbcEncrypt CBC 加密数据
func cbcEncrypt(key, plaintext, iv []byte) ([]byte, error) {
	if len(plaintext)%aes.BlockSize != 0 {
		return nil, errors.New("plaintext is not a multiple of the block size")
	}

	block, err := aes.NewCipher(key)
	if err != nil {
		return nil, err
	}

	ciphertext := make([]byte, aes.BlockSize+len(plaintext))
	iv = iv[:aes.BlockSize]
	if _, err := io.ReadFull(rand.Reader, iv); err != nil {
		return nil, err
	}

	mode := cipher.NewCBCEncrypter(block, iv)
	mode.CryptBlocks(ciphertext[aes.BlockSize:], plaintext)

	return ciphertext, nil
}

// CBC解密数据
func cbcDecrypt(key, ciphertext, iv []byte) ([]byte, error) {
	block, err := aes.NewCipher(key)
	if err != nil {
		return nil, err
	}

	size := aes.BlockSize
	iv = iv[:size]
	// ciphertext = ciphertext[size:] TODO: really useless?

	if len(ciphertext) < size {
		return nil, errors.New("ciphertext too short")
	}

	if len(ciphertext)%size != 0 {
		return nil, errors.New("ciphertext is not a multiple of the block size")
	}

	mode := cipher.NewCBCDecrypter(block, iv)
	mode.CryptBlocks(ciphertext, ciphertext)

	return pkcs7decode(ciphertext), nil
}
