package libtrust

import (
	"crypto"
	"crypto/ecdsa"
	"crypto/rsa"
	"crypto/x509"
	"encoding/json"
	"encoding/pem"
	"errors"
	"fmt"
	"io"
)

// PublicKey is a generic interface for a Public Key.
type PublicKey interface {
	// KeyType returns the key type for this key. For elliptic curve keys,
	// this value should be "EC". For RSA keys, this value should be "RSA".
	KeyType() string
	// KeyID returns a distinct identifier which is unique to this Public Key.
	// The format generated by this library is a base32 encoding of a 240 bit
	// hash of the public key data divided into 12 groups like so:
	//    ABCD:EFGH:IJKL:MNOP:QRST:UVWX:YZ23:4567:ABCD:EFGH:IJKL:MNOP
	KeyID() string
	// Verify verifyies the signature of the data in the io.Reader using this
	// Public Key. The alg parameter should identify the digital signature
	// algorithm which was used to produce the signature and should be
	// supported by this public key. Returns a nil error if the signature
	// is valid.
	Verify(data io.Reader, alg string, signature []byte) error
	// CryptoPublicKey returns the internal object which can be used as a
	// crypto.PublicKey for use with other standard library operations. The type
	// is either *rsa.PublicKey or *ecdsa.PublicKey
	CryptoPublicKey() crypto.PublicKey
	// These public keys can be serialized to the standard JSON encoding for
	// JSON Web Keys. See section 6 of the IETF draft RFC for JOSE JSON Web
	// Algorithms.
	MarshalJSON() ([]byte, error)
	// These keys can also be serialized to the standard PEM encoding.
	PEMBlock() (*pem.Block, error)
	// The string representation of a key is its key type and ID.
	String() string
	AddExtendedField(string, interface{})
	GetExtendedField(string) interface{}
}

// PrivateKey is a generic interface for a Private Key.
type PrivateKey interface {
	// A PrivateKey contains all fields and methods of a PublicKey of the
	// same type. The MarshalJSON method also outputs the private key as a
	// JSON Web Key, and the PEMBlock method outputs the private key as a
	// PEM block.
	PublicKey
	// PublicKey returns the PublicKey associated with this PrivateKey.
	PublicKey() PublicKey
	// Sign signs the data read from the io.Reader using a signature algorithm
	// supported by the private key. If the specified hashing algorithm is
	// supported by this key, that hash function is used to generate the
	// signature otherwise the the default hashing algorithm for this key is
	// used. Returns the signature and identifier of the algorithm used.
	Sign(data io.Reader, hashID crypto.Hash) (signature []byte, alg string, err error)
	// CryptoPrivateKey returns the internal object which can be used as a
	// crypto.PublicKey for use with other standard library operations. The
	// type is either *rsa.PublicKey or *ecdsa.PublicKey
	CryptoPrivateKey() crypto.PrivateKey
}

// FromCryptoPublicKey returns a libtrust PublicKey representation of the given
// *ecdsa.PublicKey or *rsa.PublicKey. Returns a non-nil error when the given
// key is of an unsupported type.
func FromCryptoPublicKey(cryptoPublicKey crypto.PublicKey) (PublicKey, error) {
	switch cryptoPublicKey := cryptoPublicKey.(type) {
	case *ecdsa.PublicKey:
		return fromECPublicKey(cryptoPublicKey)
	case *rsa.PublicKey:
		return fromRSAPublicKey(cryptoPublicKey), nil
	default:
		return nil, fmt.Errorf("public key type %T is not supported", cryptoPublicKey)
	}
}

// FromCryptoPrivateKey returns a libtrust PrivateKey representation of the given
// *ecdsa.PrivateKey or *rsa.PrivateKey. Returns a non-nil error when the given
// key is of an unsupported type.
func FromCryptoPrivateKey(cryptoPrivateKey crypto.PrivateKey) (PrivateKey, error) {
	switch cryptoPrivateKey := cryptoPrivateKey.(type) {
	case *ecdsa.PrivateKey:
		return fromECPrivateKey(cryptoPrivateKey)
	case *rsa.PrivateKey:
		return fromRSAPrivateKey(cryptoPrivateKey), nil
	default:
		return nil, fmt.Errorf("private key type %T is not supported", cryptoPrivateKey)
	}
}

// UnmarshalPublicKeyPEM parses the PEM encoded data and returns a libtrust
// PublicKey or an error if there is a problem with the encoding.
func UnmarshalPublicKeyPEM(data []byte) (PublicKey, error) {
	pemBlock, _ := pem.Decode(data)
	if pemBlock == nil {
		return nil, errors.New("unable to find PEM encoded data")
	} else if pemBlock.Type != "PUBLIC KEY" {
		return nil, fmt.Errorf("unable to get PublicKey from PEM type: %s", pemBlock.Type)
	}

	return pubKeyFromPEMBlock(pemBlock)
}

// UnmarshalPublicKeyPEMBundle parses the PEM encoded data as a bundle of
// PEM blocks appended one after the other and returns a slice of PublicKey
// objects that it finds.
func UnmarshalPublicKeyPEMBundle(data []byte) ([]PublicKey, error) {
	pubKeys := []PublicKey{}

	for {
		var pemBlock *pem.Block
		pemBlock, data = pem.Decode(data)
		if pemBlock == nil {
			break
		} else if pemBlock.Type != "PUBLIC KEY" {
			return nil, fmt.Errorf("unable to get PublicKey from PEM type: %s", pemBlock.Type)
		}

		pubKey, err := pubKeyFromPEMBlock(pemBlock)
		if err != nil {
			return nil, err
		}

		pubKeys = append(pubKeys, pubKey)
	}

	return pubKeys, nil
}

// UnmarshalPrivateKeyPEM parses the PEM encoded data and returns a libtrust
// PrivateKey or an error if there is a problem with the encoding.
func UnmarshalPrivateKeyPEM(data []byte) (PrivateKey, error) {
	pemBlock, _ := pem.Decode(data)
	if pemBlock == nil {
		return nil, errors.New("unable to find PEM encoded data")
	}

	var key PrivateKey

	switch {
	case pemBlock.Type == "RSA PRIVATE KEY":
		rsaPrivateKey, err := x509.ParsePKCS1PrivateKey(pemBlock.Bytes)
		if err != nil {
			return nil, fmt.Errorf("unable to decode RSA Private Key PEM data: %s", err)
		}
		key = fromRSAPrivateKey(rsaPrivateKey)
	case pemBlock.Type == "EC PRIVATE KEY":
		ecPrivateKey, err := x509.ParseECPrivateKey(pemBlock.Bytes)
		if err != nil {
			return nil, fmt.Errorf("unable to decode EC Private Key PEM data: %s", err)
		}
		key, err = fromECPrivateKey(ecPrivateKey)
		if err != nil {
			return nil, err
		}
	default:
		return nil, fmt.Errorf("unable to get PrivateKey from PEM type: %s", pemBlock.Type)
	}

	addPEMHeadersToKey(pemBlock, key.PublicKey())

	return key, nil
}

// UnmarshalPublicKeyJWK unmarshals the given JSON Web Key into a generic
// Public Key to be used with libtrust.
func UnmarshalPublicKeyJWK(data []byte) (PublicKey, error) {
	jwk := make(map[string]interface{})

	err := json.Unmarshal(data, &jwk)
	if err != nil {
		return nil, fmt.Errorf(
			"decoding JWK Public Key JSON data: %s\n", err,
		)
	}

	// Get the Key Type value.
	kty, err := stringFromMap(jwk, "kty")
	if err != nil {
		return nil, fmt.Errorf("JWK Public Key type: %s", err)
	}

	switch {
	case kty == "EC":
		// Call out to unmarshal EC public key.
		return ecPublicKeyFromMap(jwk)
	case kty == "RSA":
		// Call out to unmarshal RSA public key.
		return rsaPublicKeyFromMap(jwk)
	default:
		return nil, fmt.Errorf(
			"JWK Public Key type not supported: %q\n", kty,
		)
	}
}

// UnmarshalPublicKeyJWKSet parses the JSON encoded data as a JSON Web Key Set
// and returns a slice of Public Key objects.
func UnmarshalPublicKeyJWKSet(data []byte) ([]PublicKey, error) {
	rawKeys, err := loadJSONKeySetRaw(data)
	if err != nil {
		return nil, err
	}

	pubKeys := make([]PublicKey, 0, len(rawKeys))

	for _, rawKey := range rawKeys {
		pubKey, err := UnmarshalPublicKeyJWK(rawKey)
		if err != nil {
			return nil, err
		}
		pubKeys = append(pubKeys, pubKey)
	}

	return pubKeys, nil
}

// UnmarshalPrivateKeyJWK unmarshals the given JSON Web Key into a generic
// Private Key to be used with libtrust.
func UnmarshalPrivateKeyJWK(data []byte) (PrivateKey, error) {
	jwk := make(map[string]interface{})

	err := json.Unmarshal(data, &jwk)
	if err != nil {
		return nil, fmt.Errorf(
			"decoding JWK Private Key JSON data: %s\n", err,
		)
	}

	// Get the Key Type value.
	kty, err := stringFromMap(jwk, "kty")
	if err != nil {
		return nil, fmt.Errorf("JWK Private Key type: %s", err)
	}

	switch {
	case kty == "EC":
		// Call out to unmarshal EC private key.
		return ecPrivateKeyFromMap(jwk)
	case kty == "RSA":
		// Call out to unmarshal RSA private key.
		return rsaPrivateKeyFromMap(jwk)
	default:
		return nil, fmt.Errorf(
			"JWK Private Key type not supported: %q\n", kty,
		)
	}
}
