package ole

import (
	"crypto/aes"
	"crypto/cipher"
	"crypto/rand"

	"code.justin.tv/amzn/TwitchOLE/ole/internal/encryptedobject"
	"code.justin.tv/amzn/TwitchOLE/ole/internal/encryptioncontext"
)

// Algorithm is an encryption algorithm
type Algorithm interface {
	// algorithm enum
	ID() string

	Encrypt(key, plaintext []byte, encryptionContext map[string]string) (ciphertext, iv []byte, err error)
	Decrypt(key, ciphertext, iv, aad []byte) ([]byte, error)
}

// AES256GCM is the default algorithm: AES_256_GCM
type AES256GCM struct{}

// ID returns algorithm id
func (a AES256GCM) ID() string {
	return encryptedobject.Algorithm_AES_256_GCM.String()
}

// Decrypt decrypts an encrypted blob
func (a AES256GCM) Decrypt(key, ciphertext, nonce, aad []byte) ([]byte, error) {
	block, err := aes.NewCipher(key)
	if err != nil {
		return nil, err
	}
	gcm, err := cipher.NewGCM(block)
	if err != nil {
		return nil, err
	}

	return gcm.Open(nil, nonce, ciphertext, aad)
}

// Encrypt encrypts plaintext using the provided key and encryptionContext
func (a AES256GCM) Encrypt(key, plaintext []byte, encryptionContext map[string]string) (ciphertext, iv []byte, err error) {
	block, err := aes.NewCipher(key)
	if err != nil {
		return nil, nil, err
	}

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

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

	return gcm.Seal(nil, nonce, plaintext, encryptioncontext.Serialize(encryptionContext)), nonce, nil
}
