package certgen

import (
	"bytes"
	"crypto/ecdsa"
	"crypto/elliptic"
	"crypto/rand"
	"crypto/rsa"
	"crypto/x509"
	"crypto/x509/pkix"
	"encoding/json"
	"encoding/pem"
	"fmt"
	"math/big"
	"os"
	"time"

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

func ExtractPublicKey(priv interface{}) interface{} {
	switch k := priv.(type) {
	case *rsa.PrivateKey:
		return &k.PublicKey
	case *ecdsa.PrivateKey:
		return &k.PublicKey
	default:
		return nil
	}
}

func PublicKeyToJWKJson(pub interface{}) ([]byte, error) {
	jk, err := jwk.New(pub)

	if err != nil {
		return nil, err
	}

	return json.Marshal(jk)
}

func StringToJWK(in string) (jwk.Key, error) {
	set, err := jwk.ParseString(in)

	if err != nil {
		return nil, err
	}

	k, ok := set.Get(0)

	if !ok {
		return nil, fmt.Errorf("No key found")
	}

	return k, nil
}

func PublicKeyToJWK(pub interface{}) (jwk.Key, error) {
	return jwk.New(pub)
}

func pemBlockForKey(priv interface{}) *pem.Block {
	switch k := priv.(type) {
	case *rsa.PrivateKey:
		return &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(k)}
	case *ecdsa.PrivateKey:
		b, err := x509.MarshalECPrivateKey(k)
		if err != nil {
			fmt.Fprintf(os.Stderr, "Unable to marshal ECDSA private key: %v", err)
			os.Exit(2)
		}
		return &pem.Block{Type: "EC PRIVATE KEY", Bytes: b}
	default:
		return nil
	}
}

func GenCert(org, cn string) (priv *ecdsa.PrivateKey, cert *x509.Certificate, certPem string, keyPem string, err error) {
	priv, err = ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
	if err != nil {
		return nil, nil, "", "", fmt.Errorf("Failed to create key: %s", err)
	}

	template := x509.Certificate{
		SerialNumber: big.NewInt(1),
		Subject: pkix.Name{
			Organization: []string{org},
			CommonName:   cn,
		},
		NotBefore: time.Now(),
		NotAfter:  time.Now().Add(time.Hour * 24 * 180),

		KeyUsage:              x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
		ExtKeyUsage:           []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
		BasicConstraintsValid: true,
	}

	derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, ExtractPublicKey(priv), priv)

	if err != nil {
		return nil, nil, "", "", fmt.Errorf("Failed to create certificate: %s", err)
	}

	out := &bytes.Buffer{}
	pem.Encode(out, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes})
	certPem = out.String()
	out.Reset()

	pem.Encode(out, pemBlockForKey(priv))
	keyPem = out.String()

	cert, err = x509.ParseCertificate(derBytes)

	return priv, cert, certPem, keyPem, err
}
