// Package jwk implements JWK as described in https://tools.ietf.org/html/rfc7517
package jwk

import (
	"crypto/ecdsa"
	"crypto/rsa"
	"encoding/json"
	"errors"
	"fmt"

	"github.com/open-policy-agent/opa/internal/jwx/jwa"
)

// GetPublicKey returns the public key based on the private key type.
// For rsa key types *rsa.PublicKey is returned; for ecdsa key types *ecdsa.PublicKey;
// for byte slice (raw) keys, the key itself is returned. If the corresponding
// public key cannot be deduced, an error is returned
func GetPublicKey(key interface{}) (interface{}, error) {
	if key == nil {
		return nil, errors.New("jwk.New requires a non-nil key")
	}

	switch v := key.(type) {
	// Mental note: although Public() is defined in both types,
	// you can not coalesce the clauses for rsa.PrivateKey and
	// ecdsa.PrivateKey, as then `v` becomes interface{}
	// b/c the compiler cannot deduce the exact type.
	case *rsa.PrivateKey:
		return v.Public(), nil
	case *ecdsa.PrivateKey:
		return v.Public(), nil
	case []byte:
		return v, nil
	default:
		return nil, fmt.Errorf("invalid key type %T", key)
	}
}

// GetKeyTypeFromKey creates a jwk.Key from the given key.
func GetKeyTypeFromKey(key interface{}) jwa.KeyType {

	switch key.(type) {
	case *rsa.PrivateKey, *rsa.PublicKey:
		return jwa.RSA
	case *ecdsa.PrivateKey, *ecdsa.PublicKey:
		return jwa.EC
	case []byte:
		return jwa.OctetSeq
	default:
		return jwa.InvalidKeyType
	}
}

// New creates a jwk.Key from the given key.
func New(key interface{}) (Key, error) {
	if key == nil {
		return nil, errors.New("jwk.New requires a non-nil key")
	}

	switch v := key.(type) {
	case *rsa.PrivateKey:
		return newRSAPrivateKey(v)
	case *rsa.PublicKey:
		return newRSAPublicKey(v)
	case *ecdsa.PrivateKey:
		return newECDSAPrivateKey(v)
	case *ecdsa.PublicKey:
		return newECDSAPublicKey(v)
	case []byte:
		return newSymmetricKey(v)
	default:
		return nil, fmt.Errorf("invalid key type %T", key)
	}
}

func parse(jwkSrc string) (*Set, error) {

	var jwkKeySet Set
	var jwkKey Key
	rawKeySetJSON := &RawKeySetJSON{}
	err := json.Unmarshal([]byte(jwkSrc), rawKeySetJSON)
	if err != nil {
		return nil, fmt.Errorf("failed to unmarshal JWK Set: %w", err)
	}
	if len(rawKeySetJSON.Keys) == 0 {

		// It might be a single key
		rawKeyJSON := &RawKeyJSON{}
		err := json.Unmarshal([]byte(jwkSrc), rawKeyJSON)
		if err != nil {
			return nil, fmt.Errorf("failed to unmarshal JWK: %w", err)
		}
		jwkKey, err = rawKeyJSON.GenerateKey()
		if err != nil {
			return nil, fmt.Errorf("failed to generate key: %w", err)
		}
		// Add to set
		jwkKeySet.Keys = append(jwkKeySet.Keys, jwkKey)
	} else {
		for i := range rawKeySetJSON.Keys {
			rawKeyJSON := rawKeySetJSON.Keys[i]
			if rawKeyJSON.Algorithm != nil && *rawKeyJSON.Algorithm == jwa.Unsupported {
				continue
			}
			jwkKey, err = rawKeyJSON.GenerateKey()
			if err != nil {
				return nil, fmt.Errorf("failed to generate key: %w", err)
			}
			jwkKeySet.Keys = append(jwkKeySet.Keys, jwkKey)
		}
	}
	return &jwkKeySet, nil
}

// ParseBytes parses JWK from the incoming byte buffer.
func ParseBytes(buf []byte) (*Set, error) {
	return parse(string(buf))
}

// ParseString parses JWK from the incoming string.
func ParseString(s string) (*Set, error) {
	return parse(s)
}

// GenerateKey creates an internal representation of a key from a raw JWK JSON
func (r *RawKeyJSON) GenerateKey() (Key, error) {

	var key Key

	switch r.KeyType {
	case jwa.RSA:
		if r.D != nil {
			key = &RSAPrivateKey{}
		} else {
			key = &RSAPublicKey{}
		}
	case jwa.EC:
		if r.D != nil {
			key = &ECDSAPrivateKey{}
		} else {
			key = &ECDSAPublicKey{}
		}
	case jwa.OctetSeq:
		key = &SymmetricKey{}
	default:
		return nil, errors.New("unrecognized key type")
	}
	err := key.GenerateKey(r)
	if err != nil {
		return nil, fmt.Errorf("failed to generate key from JWK: %w", err)
	}
	return key, nil
}
