package jwt

import (
	"crypto/rand"
	"crypto/rsa"
	"crypto/x509"
	"encoding/pem"
	"errors"
	"io/ioutil"
	"os"
)

// LoadRSA tries to read RSA Private Key from "fname" system file,
// if does not exist then it generates a new random one based on "bits" (e.g. 2048, 4096)
// and exports it to a new "fname" file.
func LoadRSA(fname string, bits int) (key *rsa.PrivateKey, err error) {
	exists := fileExists(fname)
	if exists {
		key, err = importFromFile(fname)
	} else {
		key, err = rsa.GenerateKey(rand.Reader, bits)
	}

	if err != nil {
		return
	}

	if !exists {
		err = exportToFile(key, fname)
	}

	return
}

func exportToFile(key *rsa.PrivateKey, filename string) error {
	b := x509.MarshalPKCS1PrivateKey(key)
	encoded := pem.EncodeToMemory(
		&pem.Block{
			Type:  "RSA PRIVATE KEY",
			Bytes: b,
		},
	)

	return ioutil.WriteFile(filename, encoded, 0600)
}

func importFromFile(filename string) (*rsa.PrivateKey, error) {
	b, err := ioutil.ReadFile(filename)
	if err != nil {
		return nil, err
	}

	return ParseRSAPrivateKey(b, nil)
}

func fileExists(filename string) bool {
	info, err := os.Stat(filename)
	if os.IsNotExist(err) {
		return false
	}
	return !info.IsDir()
}

var (
	// ErrNotPEM is an error type of the `ParseXXX` function(s) fired
	// when the data are not PEM-encoded.
	ErrNotPEM = errors.New("key must be PEM encoded")
	// ErrInvalidKey is an error type of the `ParseXXX` function(s) fired
	// when the contents are not type of rsa private key.
	ErrInvalidKey = errors.New("key is not of type *rsa.PrivateKey")
)

// ParseRSAPrivateKey encodes a PEM-encoded PKCS1 or PKCS8 private key protected with a password.
func ParseRSAPrivateKey(key, password []byte) (*rsa.PrivateKey, error) {
	block, _ := pem.Decode(key)
	if block == nil {
		return nil, ErrNotPEM
	}

	var (
		parsedKey interface{}
		err       error
	)

	var blockDecrypted []byte
	if len(password) > 0 {
		if blockDecrypted, err = x509.DecryptPEMBlock(block, password); err != nil {
			return nil, err
		}
	} else {
		blockDecrypted = block.Bytes
	}

	if parsedKey, err = x509.ParsePKCS1PrivateKey(blockDecrypted); err != nil {
		if parsedKey, err = x509.ParsePKCS8PrivateKey(blockDecrypted); err != nil {
			return nil, err
		}
	}

	privateKey, ok := parsedKey.(*rsa.PrivateKey)
	if !ok {
		return nil, ErrInvalidKey
	}

	return privateKey, nil
}
