package utility

import (
	"bytes"
	"crypto/aes"
	"crypto/cipher"
	"crypto/md5"
	"crypto/rand"
	"encoding/base64"
	"encoding/hex"
	"fmt"
	"golang.org/x/crypto/sha3"
	"io"
	"strings"
)

// 암호화
var key = []byte{0x17, 0xc0, 0xcc, 0x00, 0x32, 0x88, 0x11, 0xa1, 0x51, 0xfe, 0xff, 0x81, 0x9c, 0xdc, 0x9f, 0xea, 0x60, 0x2f, 0x71, 0x28, 0x16, 0x1f, 0x41, 0x7a, 0xa5, 0xc4, 0xac, 0xdd, 0x50, 0x78, 0x08, 0x3f}

func HashPassword(pw string) string {
	bin := sha3.Sum256([]byte(pw))
	str := hex.EncodeToString(bin[:])
	return str
}

func Encrypt(buff []byte) ([]byte, error) {
	block, err := aes.NewCipher(key)
	if err != nil {
		return nil, err
	}

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

	cfb := cipher.NewCFBEncrypter(block, iv)
	cfb.XORKeyStream(ciphertext[aes.BlockSize:], buff)
	return ciphertext, nil
}

func EncryptString(s string) (string, error) {
	result, err := Encrypt([]byte(s))
	if err != nil {
		return "", err
	}

	return hex.EncodeToString(result), nil
}

func Decrypt(buff []byte) ([]byte, error) {
	block, err := aes.NewCipher(key)
	if err != nil {
		return nil, err
	}
	if len(buff) < aes.BlockSize {
		return nil, fmt.Errorf("ciphertext too short")
	}
	iv := buff[:aes.BlockSize]
	buff = buff[aes.BlockSize:]
	cfb := cipher.NewCFBDecrypter(block, iv)
	cfb.XORKeyStream(buff, buff)

	return buff, nil
}

func DecryptString(s string) (string, error) {
	buf, err := hex.DecodeString(s)
	if err != nil {
		return "", err
	}

	result, err := Decrypt(buf)
	if err != nil {
		return "", err
	}

	return string(result), nil
}

func MakeMD5(text string) string {
	hasher := md5.New()
	hasher.Write([]byte(text))
	return string(hasher.Sum(nil))
}

func AesEncrypt(plainText string, key string, iv string) (string, error) {
	if strings.TrimSpace(plainText) == "" {
		return plainText, nil
	}

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

	encrypter := cipher.NewCBCEncrypter(block, []byte(iv))
	paddedPlainText := padPKCS7([]byte(plainText), encrypter.BlockSize())

	cipherText := make([]byte, len(paddedPlainText))
	// CryptBlocks 함수에 데이터(paddedPlainText)와 암호화 될 데이터를 저장할 슬라이스(cipherText)를 넣으면 암호화가 된다.
	encrypter.CryptBlocks(cipherText, paddedPlainText)

	return base64.StdEncoding.EncodeToString(cipherText), nil
}

func AesDecrypt(cipherText string, key string, iv string) (string, error) {
	if strings.TrimSpace(cipherText) == "" {
		return cipherText, nil
	}

	decodedCipherText, err := base64.StdEncoding.DecodeString(cipherText)
	if err != nil {
		return "", err
	}

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

	decrypter := cipher.NewCBCDecrypter(block, []byte(iv))
	plainText := make([]byte, len(decodedCipherText))

	decrypter.CryptBlocks(plainText, decodedCipherText)
	trimmedPlainText := trimPKCS5(plainText)

	return string(trimmedPlainText), nil
}

func padPKCS7(plainText []byte, blockSize int) []byte {
	padding := blockSize - len(plainText)%blockSize
	padText := bytes.Repeat([]byte{byte(padding)}, padding)
	return append(plainText, padText...)
}

func trimPKCS5(text []byte) []byte {
	padding := text[len(text)-1]
	return text[:len(text)-int(padding)]
}
