/*
Copyright 2022 The Dapr Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
    http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package crypto

import (
	"bytes"
	"crypto/ecdsa"
	"crypto/ed25519"
	"crypto/rsa"
	"crypto/x509"
	"encoding/base64"
	"errors"
	"fmt"

	"github.com/lestrrat-go/jwx/v2/jwk"
)

// SerializeKey serializes a jwk.Key in the appropriate format so they can be wrapped.
// Symmetric keys are returned as raw bytes, while asymmetric keys are marshalled as ASN.1 DER (X.509, not PEM-encoded).
func SerializeKey(key jwk.Key) ([]byte, error) {
	var rawKey any
	err := key.Raw(&rawKey)
	if err != nil {
		return nil, fmt.Errorf("failed to extract raw key: %w", err)
	}

	switch r := rawKey.(type) {
	case []byte: // Symmetric keys
		return r, nil
	case *rsa.PrivateKey, *ecdsa.PrivateKey, ed25519.PrivateKey: // Private keys: marshal as PKCS#8
		return x509.MarshalPKCS8PrivateKey(r)
	case *rsa.PublicKey, *ecdsa.PublicKey, ed25519.PublicKey: // Public keys: marshal as PKIX
		return x509.MarshalPKIXPublicKey(r)
	default:
		return nil, errors.New("unsupported key type")
	}
}

// ParseKey takes a byte slice and returns a key (public, private, symmetric) after determining its type.
// It supports keys represented as JWKs, PEM-encoded (PKCS#8, PKCS#1 or PKIX) or as raw bytes (optionally base64-encoded).
// The parameter contentType is optional and it can contain a mime type.
func ParseKey(raw []byte, contentType string) (jwk.Key, error) {
	l := len(raw)
	if l == 0 {
		return nil, errors.New("key is empty")
	}

	// Determine the type of key if the type parameter is set
	switch contentType {
	case "application/json": // JWK
		return jwk.ParseKey(raw)
	case "application/x-pem-file", "application/pkcs8": // Generic PEM: PKCS#1, PKCS#8, PKIX
		return jwk.ParseKey(raw, jwk.WithPEM(true))
	}

	// Heuristically determine the type of key
	switch {
	case raw[0] == '{' && l != 16 && l != 24 && l != 32: // Assume it's a JWK unless the length is 16, 24, or 32 bytes
		return jwk.ParseKey(raw)
	case len(raw) > 10 && string(raw[0:5]) == ("-----"): // Assume it's something PEM-encoded
		return jwk.ParseKey(raw, jwk.WithPEM(true))
	default: // Assume a symmetric key
		return parseSymmetricKey(raw)
	}
}

func parseSymmetricKey(raw []byte) (jwk.Key, error) {
	// Try parsing as base64; first: remove any padding (and trailing newlines) if present
	trimmedRaw := bytes.TrimRight(raw, "\n=")

	// Try parsing as base64-standard
	dst := make([]byte, base64.RawStdEncoding.DecodedLen(len(raw)))
	n, err := base64.RawStdEncoding.Decode(dst, trimmedRaw)
	if err == nil {
		return jwk.FromRaw(dst[:n])
	}

	// Try parsing as base64-url
	n, err = base64.RawURLEncoding.Decode(dst, trimmedRaw)
	if err == nil {
		return jwk.FromRaw(dst[:n])
	}

	// Treat the byte slice as the raw, symmetric key
	return jwk.FromRaw(raw)
}
