package encoder

import (
	"crypto/rand"
	"encoding/base64"
	"fmt"

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

type Encoder struct {
	Options
}

func NewEncoder(opts ...OptFunc) (*Encoder, *Options) {
	o := defaultOptions
	for _, fn := range opts {
		fn(&o)
	}

	return &Encoder{o}, &o
}

func (encoder *Encoder) generateRandomBytes(n uint32) ([]byte, error) {
	b := make([]byte, n)
	_, err := rand.Read(b)
	if err != nil {
		return nil, err
	}

	return b, nil
}

func (encoder *Encoder) HashString(password string) (encodedHash string, err error) {
	salt, err := encoder.generateRandomBytes(encoder.SaltLength)
	if err != nil {
		return "", err
	}
	hash := argon2.IDKey(
		[]byte(password),
		salt,
		encoder.Iterations,
		encoder.Memory,
		encoder.Parallelism,
		encoder.KeyLength,
	)
	b64Salt := base64.RawStdEncoding.EncodeToString(salt)
	b64Hash := base64.RawStdEncoding.EncodeToString(hash)
	encodedHash = fmt.Sprintf(
		"$argon2id$v=%d$m=%d,t=%d,p=%d$%s$%s",
		argon2.Version,
		encoder.Memory,
		encoder.Iterations,
		encoder.Parallelism,
		b64Salt,
		b64Hash,
	)

	return encodedHash, nil
}

func (encoder *Encoder) RandomString(s int) (string, error) {
	b, err := encoder.generateRandomBytes(uint32(s))
	return base64.URLEncoding.EncodeToString(b), err
}