package crypto

import (
	"crypto/aes"
	"crypto/cipher"
	"crypto/rand"
	"encoding/base64"
	"errors"
	"io"
)

type EncryptType string

const (
	EncryptTypeNone EncryptType = "none"
	EncryptTypeAES  EncryptType = "aes"
	EncryptTypeRC4  EncryptType = "rc4"
)

type CryptoManager struct {
	encryptType EncryptType
	secretKey   string
}

func NewCryptoManager(encryptType EncryptType, secretKey string) *CryptoManager {
	return &CryptoManager{
		encryptType: encryptType,
		secretKey:   secretKey,
	}
}

func (cm *CryptoManager) GetEncryptType() EncryptType {
	return cm.encryptType
}

func (cm *CryptoManager) GetSecretKey() string {
	return cm.secretKey
}

func (cm *CryptoManager) Encrypt(plaintext string) (string, error) {
	if cm.encryptType == EncryptTypeNone || cm.secretKey == "" {
		return plaintext, nil
	}

	switch cm.encryptType {
	case EncryptTypeAES:
		return cm.encryptAES(plaintext)
	case EncryptTypeRC4:
		return cm.encryptRC4(plaintext)
	default:
		return plaintext, nil
	}
}

func (cm *CryptoManager) Decrypt(ciphertext string) (string, error) {
	if cm.encryptType == EncryptTypeNone || cm.secretKey == "" {
		return ciphertext, nil
	}

	switch cm.encryptType {
	case EncryptTypeAES:
		return cm.decryptAES(ciphertext)
	case EncryptTypeRC4:
		return cm.decryptRC4(ciphertext)
	default:
		return ciphertext, nil
	}
}

func (cm *CryptoManager) encryptAES(plaintext string) (string, error) {
	key := []byte(cm.secretKey)
	if len(key) != 16 && len(key) != 24 && len(key) != 32 {
		key = cm.padKey(key, 32)
	}

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

	gcm, err := cipher.NewGCM(block)
	if err != nil {
		return "", err
	}

	nonce := make([]byte, gcm.NonceSize())
	if _, err = io.ReadFull(rand.Reader, nonce); err != nil {
		return "", err
	}

	ciphertext := gcm.Seal(nonce, nonce, []byte(plaintext), nil)
	return base64.StdEncoding.EncodeToString(ciphertext), nil
}

func (cm *CryptoManager) decryptAES(ciphertext string) (string, error) {
	key := []byte(cm.secretKey)
	if len(key) != 16 && len(key) != 24 && len(key) != 32 {
		key = cm.padKey(key, 32)
	}

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

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

	gcm, err := cipher.NewGCM(block)
	if err != nil {
		return "", err
	}

	nonceSize := gcm.NonceSize()
	if len(data) < nonceSize {
		return "", errors.New("ciphertext too short")
	}

	nonce, ciphertextBytes := data[:nonceSize], data[nonceSize:]
	plaintext, err := gcm.Open(nil, nonce, ciphertextBytes, nil)
	if err != nil {
		return "", err
	}

	return string(plaintext), nil
}

func (cm *CryptoManager) encryptRC4(plaintext string) (string, error) {
	key := []byte(cm.secretKey)
	if len(key) == 0 {
		return "", errors.New("secret key is empty")
	}

	cipher, err := NewRC4(key)
	if err != nil {
		return "", err
	}

	ciphertext := make([]byte, len(plaintext))
	cipher.XORKeyStream(ciphertext, []byte(plaintext))

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

func (cm *CryptoManager) decryptRC4(ciphertext string) (string, error) {
	key := []byte(cm.secretKey)
	if len(key) == 0 {
		return "", errors.New("secret key is empty")
	}

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

	cipher, err := NewRC4(key)
	if err != nil {
		return "", err
	}

	plaintext := make([]byte, len(data))
	cipher.XORKeyStream(plaintext, data)

	return string(plaintext), nil
}

func (cm *CryptoManager) padKey(key []byte, targetLen int) []byte {
	if len(key) >= targetLen {
		return key[:targetLen]
	}

	padded := make([]byte, targetLen)
	copy(padded, key)
	for i := len(key); i < targetLen; i++ {
		padded[i] = key[i%len(key)]
	}
	return padded
}

type RC4 struct {
	s    [256]byte
	i, j uint8
}

func NewRC4(key []byte) (*RC4, error) {
	if len(key) == 0 {
		return nil, errors.New("RC4 key cannot be empty")
	}

	rc4 := &RC4{}
	for i := 0; i < 256; i++ {
		rc4.s[i] = byte(i)
	}

	j := uint8(0)
	for i := 0; i < 256; i++ {
		j = j + rc4.s[i] + key[i%len(key)]
		rc4.s[i], rc4.s[j] = rc4.s[j], rc4.s[i]
	}

	return rc4, nil
}

func (rc4 *RC4) XORKeyStream(dst, src []byte) {
	for i := range src {
		rc4.i++
		rc4.j += rc4.s[rc4.i]
		rc4.s[rc4.i], rc4.s[rc4.j] = rc4.s[rc4.j], rc4.s[rc4.i]
		dst[i] = src[i] ^ rc4.s[(rc4.s[rc4.i]+rc4.s[rc4.j])]
	}
}

func GenerateRandomKey(length int) (string, error) {
	key := make([]byte, length)
	if _, err := io.ReadFull(rand.Reader, key); err != nil {
		return "", err
	}
	return base64.StdEncoding.EncodeToString(key), nil
}

func GenerateAESKey() (string, error) {
	return GenerateRandomKey(32)
}

func GenerateRC4Key() (string, error) {
	return GenerateRandomKey(16)
}
