package cryptoutil

import (
	"crypto"
	"crypto/ecdsa"
	"crypto/elliptic"
	"crypto/rand"
	"crypto/rsa"
	"crypto/x509"
	"encoding/pem"
	"fmt"
)

const (
	ecKeyType  = "EC PRIVATE KEY"
	rsaKeyType = "RSA PRIVATE KEY"
)

// PasswordFunc is the signature for passing password while
// PEM encoding/decoding private keys
type PasswordFunc func() ([]byte, error)

// NoPassword should be used when the private key need not be encrypted
var NoPassword = func() ([]byte, error) {
	return nil, nil
}

// EncodePrivateKey PEM encodes private key
// when password is not empty private key is encrypted with password
func EncodePrivateKey(privKey crypto.PrivateKey, f PasswordFunc) ([]byte, error) {
	password, err := f()
	if err != nil {
		return nil, fmt.Errorf("unable to get password %s", err.Error())
	}

	switch privKey.(type) {
	case *ecdsa.PrivateKey:
		ecPrivKey := privKey.(*ecdsa.PrivateKey)
		b, err := x509.MarshalECPrivateKey(ecPrivKey)
		if err != nil {
			return nil, err
		}

		var p *pem.Block

		if len(password) == 0 {
			p = &pem.Block{
				Type:  ecKeyType,
				Bytes: b,
			}
		} else {
			p, err = x509.EncryptPEMBlock(rand.Reader, ecKeyType, b, password, x509.PEMCipherAES256)
			if err != nil {
				return nil, err
			}
		}
		return pem.EncodeToMemory(p), nil
	case *rsa.PrivateKey:
		rsaPrivKey := privKey.(*rsa.PrivateKey)
		b := x509.MarshalPKCS1PrivateKey(rsaPrivKey)

		var p *pem.Block

		if len(password) == 0 {
			p = &pem.Block{
				Type:  rsaKeyType,
				Bytes: b,
			}
		} else {
			p, err = x509.EncryptPEMBlock(rand.Reader, rsaKeyType, b, password, x509.PEMCipherAES256)
			if err != nil {
				return nil, err
			}
		}
		return pem.EncodeToMemory(p), nil
	default:
		return nil, fmt.Errorf("unsupported private key %T", privKey)

	}

}

// DecodePrivateKey decodes PEM encoded private key
// when PasswordFunc is provied private key is decrypted with password
func DecodePrivateKey(privKey []byte, f PasswordFunc) (crypto.PrivateKey, error) {
	p, err := decodePEM(privKey)
	if err != nil {
		return nil, err
	}

	password, err := f()
	if err != nil {
		return nil, err
	}

	var b []byte

	switch {
	case x509.IsEncryptedPEMBlock(p):
		b, err = x509.DecryptPEMBlock(p, password)
		if err != nil {
			fmt.Print("DecryptPEMBlock here is the error", err.Error())
			return nil, err
		}
	default:
		b = p.Bytes
	}

	fmt.Print("p.Type", p.Type)

	switch p.Type {
	case ecKeyType:
		return x509.ParseECPrivateKey(b)
	case rsaKeyType:
		return x509.ParsePKCS1PrivateKey(b)
	default:
		return nil, fmt.Errorf("type %s is not suported", p.Type)
	}

}

// DecryptPrivateKeyAsPem returns a decrypted private key in PEM encoding
func DecryptPrivateKeyAsPem(privKey []byte, f PasswordFunc) ([]byte, error) {
	pk, err := DecodePrivateKey(privKey, f)
	if err != nil {
		return nil, err
	}

	return EncodePrivateKey(pk, NoPassword)
}

// GenerateECDSAPrivateKey generates new ECDSA private key
func GenerateECDSAPrivateKey() (*ecdsa.PrivateKey, error) {
	ecKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
	if err != nil {
		return nil, err
	}

	return ecKey, err

}
