package shared

import (
	"bytes"
	"compress/flate"
	"crypto/aes"
	"crypto/cipher"
	"crypto/rand"
	"encoding/base64"
	"encoding/binary"
	"fmt"
	"io"
)

func GenerateRandomString(length int) string {
	b := make([]byte, length)
	_, err := rand.Read(b)
	if err != nil {
		panic(err)
	}
	return base64.StdEncoding.EncodeToString(b)
}

func CovertUintToByte32(id uint) [32]byte {
	b := make([]byte, 32)
	binary.LittleEndian.PutUint32(b, uint32(id))
	return [32]byte(b)
}

type EncryptionClient interface {
	Encrypt(string) (string, error)
	Decrypt(string) (string, error)
}

type encryptionClient struct {
	Secret string
}

func NewEncryptionClient(secret string) EncryptionClient {
	return &encryptionClient{Secret: secret}
}

func compress(text string) ([]byte, error) {
	var compressedBytes bytes.Buffer
	w, err := flate.NewWriter(&compressedBytes, flate.BestCompression)
	w.Write([]byte(text))
	w.Close()
	return compressedBytes.Bytes(), err
}

func decompress(compressedBytes []byte) (string, error) {
	reader := flate.NewReader(bytes.NewReader(compressedBytes))
	defer reader.Close()
	textBytes := new(bytes.Buffer)
	_, err := textBytes.ReadFrom(reader)
	return textBytes.String(), err
}

// Encrypt method is to encrypt or hide any classified text
func (e encryptionClient) Encrypt(text string) (string, error) {
	block, err := aes.NewCipher([]byte(e.Secret))
	if err != nil {
		return "", err
	}
	compressedText, err := compress(text)
	if err != nil {
		return "", err
	}
	gcm, err := cipher.NewGCM(block)
	if err != nil {
		return "", err
	}
	bytes := make([]byte, gcm.NonceSize())
	if _, err = io.ReadFull(rand.Reader, bytes); err != nil {
		return "", err
	}
	encodedText := gcm.Seal(bytes, bytes, compressedText, nil)
	return base64.StdEncoding.EncodeToString(encodedText), nil
}

// Decrypt method is to extract back the encrypted text
func (e encryptionClient) Decrypt(text string) (string, error) {
	block, err := aes.NewCipher([]byte(e.Secret))
	if err != nil {
		return "", err
	}
	cipherText, err := base64.StdEncoding.DecodeString(text)
	if err != nil {
		return "", err
	}
	gcm, err := cipher.NewGCM(block)
	if err != nil {
		return "", err
	}
	if len(cipherText) < gcm.NonceSize() {
		return "", fmt.Errorf("text is too small")
	}
	bytes, cipherText := cipherText[:gcm.NonceSize()], cipherText[gcm.NonceSize():]
	compressedTextBytes, err := gcm.Open(nil, bytes, cipherText, nil)
	if err != nil {
		return "", err
	}
	return decompress(compressedTextBytes)
}
