package comm

import (
	"crypto/aes"
	"crypto/cipher"
	"crypto/rsa"
	"crypto/sha1"
	"crypto/sha256"
	"crypto/x509"
	"encoding/base64"
	"encoding/pem"
	"errors"
	"fmt"
	"hash"
	"io"
	"math"

	"crypto/rand"

	mrand "math/rand"
	"strconv"
	"strings"
	"time"
)

// 实现php的sha1()方法，返回的是byte转换的字符串，如果需要转成16进制，可以使用hex.EncodeToString
func GetSha1(str string) string {
	h := sha1.New()
	io.WriteString(h, str)
	return string(h.Sum(nil))
}

// 右边补全字符串实现方法，主要实现php的str_pad()方法
func StrPadRight(input string, padLength int, padString string) string {
	output := ""
	inputLen := len(input)
	if inputLen >= padLength {
		return input
	}
	ll := padLength - inputLen
	for i := 1; i <= ll; i = i + len(padString) {
		output += padString
	}
	return input + output
}

// url安全模式decode字符串
func UrlSafeB64decode(str string) (result []byte) {
	str = strings.Replace(str, "-", "+", -1)
	str = strings.Replace(str, "_", "/", -1)
	mod4 := len(str) % 4
	if mod4 != 0 {
		str = str + "===="[0:mod4]
	}
	result, _ = base64.StdEncoding.DecodeString(str)
	return result
}

// base64字符串，替换转换为安全模式
func UrlSafeB64encode(str string) (result string) {
	str = strings.Replace(str, "+", "-", -1)
	str = strings.Replace(str, "/", "_", -1)
	return str
}

// 使用aes-256-gcm方式解密字符串，主要针对php的openssl_decrypt()方法，注意在php7.1后增加了tag和add参数
func DecodeAesGcm(encryptData string, hex_key string, hex_add string) ([]byte, error) {
	tagSize := 16 //nonceSize,tag的长度，用于open时候生成tag,默认12
	key := []byte(hex_key)
	add := []byte(hex_add)
	block, err := aes.NewCipher(key) //生成加解密用的block
	if err != nil {
		return []byte(""), err
	}
	//根据不同加密算法，也有不同tag长度的方法设定和调用，比如NewGCMWithTagSize、newGCMWithNonceAndTagSize
	aesgcm, err := cipher.NewGCMWithNonceSize(block, tagSize)
	if err != nil {
		return []byte(""), err
	}
	decodeEncryptStr := UrlSafeB64decode(encryptData)
	ciphertext := decodeEncryptStr
	if len(ciphertext) <= aesgcm.NonceSize() { // 长度应该>iv
		return []byte(""), errors.New("string: too short") //解密失败
	}
	iv := ciphertext[:aesgcm.NonceSize()]        //分离出IV
	ciphertext = ciphertext[aesgcm.NonceSize():] // 密文,tag是调用open方法时候通过密文和前面new时候传的size来进行截取的
	plaintext, err := aesgcm.Open(nil, iv, ciphertext, add)
	return plaintext, err
}

// 使用aes-256-gcm加密数据，主要针对php的openssl_encrypt()方法，注意在php7.1后增加了tag和add参数
func EncodeAesGcm(data string, hex_key string, hex_add string) (result string, error error) {
	tagSize := 16 //nonceSize,tag的长度，用于open时候生成tag,默认12
	key := []byte(hex_key)
	add := []byte(hex_add)
	block, err := aes.NewCipher(key) //生成加解密用的block
	if err != nil {
		return result, err
	}
	//根据不同加密算法，也有不同tag长度的方法设定和调用，比如NewGCMWithTagSize、newGCMWithNonceAndTagSize
	aesgcm, err := cipher.NewGCMWithNonceSize(block, tagSize)
	if err != nil {
		return result, err
	}
	plaintext := []byte(data)
	iv := make([]byte, tagSize)                       // NonceSize=12
	rand.Read(iv)                                     //获取随机值
	ciphertext := aesgcm.Seal(iv, iv, plaintext, add) //加密,密文为:iv+密文+tag
	result = base64.StdEncoding.EncodeToString(ciphertext)
	result = UrlSafeB64encode(result)
	return result, nil // 生成的BS64
}

func GenSecretKey(appId string, appSecret string) string {

	key := appId + "&" + appSecret
	sha1_str := GetSha1(key)
	str10 := "0"
	pack64, _ := strconv.ParseUint(str10, 10, 32) //对应php的pack()方法，字符串转换为uint类型

	pack32 := uint32(pack64)
	str_pad := StrPadRight(sha1_str, 32, fmt.Sprintf("%d", pack32))
	return base64.StdEncoding.EncodeToString([]byte(str_pad))
}

// 加密
func Encode(secretKey string, data []byte) (string, error) {
	decodeSecretKeyByte, _ := base64.StdEncoding.DecodeString(secretKey)
	c, e := EncodeAesGcm(string(data), string(decodeSecretKeyByte), "")
	if e != nil {
		return "", e
	}
	return c, nil
}

// 加密
func Decode(secretKey string, encryptData string) ([]byte, error) {
	decodeSecretKeyByte, _ := base64.StdEncoding.DecodeString(secretKey)
	decodeSecretKey := string(decodeSecretKeyByte)
	//$data = openssl_decrypt(substr($decodeEncrypt, 16, -16), 'aes-256-gcm', $decodeSecret, 1, substr($decodeEncrypt, 0, 16), substr($decodeEncrypt, -16, 16));
	p, e := DecodeAesGcm(encryptData, decodeSecretKey, "")
	if e != nil {
		return nil, e
	}
	return p, nil
}

func decryptOAEP(pritKey []byte, cipherdata []byte, label []byte) ([]byte, error) {

	block, _ := pem.Decode(pritKey)
	if block == nil {
		err := fmt.Errorf("failed to parse certificate PEM")
		return nil, err
	}

	priv, err := x509.ParsePKCS1PrivateKey(block.Bytes) // ASN.1 PKCS#1 DER encoded form.
	if err != nil {
		return nil, err
	}

	h := sha256.New()
	return DecryptOAEPLong(h, rand.Reader, priv, cipherdata, label)
}

func EncryptOAEPLong(hash hash.Hash, random io.Reader, public *rsa.PublicKey, msg []byte, label []byte) ([]byte, error) {

	msgLen := len(msg)

	step := public.Size() - 2*hash.Size() - 2

	var encryptedBytes []byte

	for start := 0; start < msgLen; start += step {

		finish := start + step

		if finish > msgLen {

			finish = msgLen

		}

		encryptedBlockBytes, err := rsa.EncryptOAEP(hash, random, public, msg[start:finish], label)

		if err != nil {

			return nil, err

		}

		encryptedBytes = append(encryptedBytes, encryptedBlockBytes...)

	}

	return encryptedBytes, nil

}

func DecryptOAEPLong(hash hash.Hash, random io.Reader, private *rsa.PrivateKey, msg []byte, label []byte) ([]byte, error) {

	msgLen := len(msg)

	step := private.PublicKey.Size()

	var decryptedBytes []byte

	for start := 0; start < msgLen; start += step {

		finish := start + step

		if finish > msgLen {

			finish = msgLen

		}

		decryptedBlockBytes, err := rsa.DecryptOAEP(hash, random, private, msg[start:finish], label)

		if err != nil {

			return nil, err

		}

		decryptedBytes = append(decryptedBytes, decryptedBlockBytes...)

	}
	return decryptedBytes, nil
}

func encryptOAEP(pubkey []byte, msg []byte, label []byte) ([]byte, error) {
	block, _ := pem.Decode(pubkey)
	if block == nil {
		err := fmt.Errorf("failed to parse certificate PEM")
		return nil, err
	}

	pub, err := x509.ParsePKIXPublicKey(block.Bytes)
	if err != nil {
		return nil, err
	}
	rsaPublicKey := pub.(*rsa.PublicKey)

	h := sha256.New() // sha1.New() or md5.New()
	return EncryptOAEPLong(h, rand.Reader, rsaPublicKey, msg, label)
}

func radomInt() int32 {
	mrand.Seed(time.Now().UnixNano())
	return mrand.Int31n(math.MaxInt32)
}
