package crypto

import (
	"crypto/sha512"
	"crypto/subtle"
	"encoding/base64"

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

const Pbdkf2Sha512EncryptorName = "pbkdf2-sha512"

type Pbdkf2Sha512Encryptor struct{}

func NewPbdkf2Sha512Encryptor() *Pbdkf2Sha512Encryptor {
	return &Pbdkf2Sha512Encryptor{}
}

// Encrypt encrypts the given password with the given salt and iterations.
func (e *Pbdkf2Sha512Encryptor) Encrypt(password, salt string, iterations int) string {
	dk := pbkdf2.Key(
		[]byte(password),
		[]byte(salt),
		iterations, sha512.Size, sha512.New,
	)
	hash := base64.RawStdEncoding.EncodeToString(dk)
	pbkdf2Hash := &PasswordHash{
		Algorithm:  Pbdkf2Sha512EncryptorName,
		Iterations: iterations,
		Salt:       salt,
		Hash:       hash,
	}
	encrypted := pbkdf2Hash.String()
	return encrypted
}

// Compare compares the given password with the given encrypted password.
func (e *Pbdkf2Sha512Encryptor) Compare(password, encryptedPassword string) bool {
	pbkdf2Hash, err := ParsePasswordHash(encryptedPassword)
	if err != nil {
		return false
	}
	algorithm := pbkdf2Hash.Algorithm
	iterations := pbkdf2Hash.Iterations
	salt := pbkdf2Hash.Salt
	if algorithm != Pbdkf2Sha512EncryptorName {
		return false
	}
	newEncryptedPassword := e.Encrypt(password, salt, iterations)
	// Compare the two passwords in constant time to avoid timing attacks.
	return subtle.ConstantTimeCompare([]byte(newEncryptedPassword), []byte(encryptedPassword)) == 1
}
