package crypto

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

	"golang.org/x/crypto/scrypt"
)

// Scrypt KDF params - tune for security! These work for small PBKDF2 workloads.
const (
	scryptN         = 1 << 15 // 32k -- increase for more security, decrease for slow machines
	scryptR         = 8
	scryptP         = 1
	keyLen          = 32 // 256-bit key for AES-256
	saltLen         = 16
	aesGCMNonceSize = 12
)

// DeriveKey derives a 256-bit key from password+salt using scrypt
func DeriveKey(password, salt []byte) ([]byte, error) {
	return scrypt.Key(password, salt, scryptN, scryptR, scryptP, keyLen)
}

// GenerateSalt creates a new [saltLen]-byte salt
func GenerateSalt() ([]byte, error) {
	salt := make([]byte, saltLen)
	_, err := io.ReadFull(rand.Reader, salt)
	return salt, err
}

// Encrypt plaintext using AES-GCM with the given key.
// Returns base64(IV) and base64(ciphertext)
func Encrypt(plaintext, key []byte) (string, string, error) {
	block, err := aes.NewCipher(key)
	if err != nil {
		return "", "", err
	}
	gcm, err := cipher.NewGCM(block)
	if err != nil {
		return "", "", err
	}
	iv := make([]byte, aesGCMNonceSize)
	if _, err := io.ReadFull(rand.Reader, iv); err != nil {
		return "", "", err
	}
	ciphertext := gcm.Seal(nil, iv, plaintext, nil)
	return base64.StdEncoding.EncodeToString(iv), base64.StdEncoding.EncodeToString(ciphertext), nil
}

// Decrypt base64(ciphertext) using base64(IV) and the given key.
func Decrypt(ciphertextB64, ivB64 string, key []byte) ([]byte, error) {
	iv, err := base64.StdEncoding.DecodeString(ivB64)
	if err != nil {
		return nil, err
	}
	ciphertext, err := base64.StdEncoding.DecodeString(ciphertextB64)
	if err != nil {
		return nil, err
	}
	block, err := aes.NewCipher(key)
	if err != nil {
		return nil, err
	}
	gcm, err := cipher.NewGCM(block)
	if err != nil {
		return nil, err
	}
	plaintext, err := gcm.Open(nil, iv, ciphertext, nil)
	if err != nil {
		return nil, err
	}
	return plaintext, nil
}
