package connect

import (
	"crypto"
	"crypto/ecdsa"
	"crypto/rsa"
	"crypto/sha1"
	"crypto/sha256"
	"crypto/x509"
	"encoding/hex"
	"encoding/pem"
	"fmt"
	"math/big"
	"strings"
)

// ParseCert parses the x509 certificate from a PEM-encoded value.
func ParseCert(pemValue string) (*x509.Certificate, error) {
	// The _ result below is not an error but the remaining PEM bytes.
	block, _ := pem.Decode([]byte(pemValue))
	if block == nil {
		return nil, fmt.Errorf("no PEM-encoded data found")
	}

	if block.Type != "CERTIFICATE" {
		return nil, fmt.Errorf("first PEM-block should be CERTIFICATE type")
	}

	return x509.ParseCertificate(block.Bytes)
}

// ParseLeafCerts parses all of the x509 certificates from a PEM-encoded value
// under the assumption that the first cert is a leaf (non-CA) cert and the
// rest are intermediate CA certs.
//
// If no certificates are found this returns an error.
func ParseLeafCerts(pemValue string) (*x509.Certificate, *x509.CertPool, error) {
	certs, err := parseCerts(pemValue)
	if err != nil {
		return nil, nil, err
	}

	leaf := certs[0]
	if leaf.IsCA {
		return nil, nil, fmt.Errorf("first PEM-block should be a leaf cert")
	}

	intermediates := x509.NewCertPool()
	for _, cert := range certs[1:] {
		if !cert.IsCA {
			return nil, nil, fmt.Errorf("found an unexpected leaf cert after the first PEM-block")
		}
		intermediates.AddCert(cert)
	}

	return leaf, intermediates, nil
}

// ParseCerts parses the all x509 certificates from a PEM-encoded value.
// The first returned cert is a leaf cert and any other ones are intermediates.
//
// If no certificates are found this returns an error.
func parseCerts(pemValue string) ([]*x509.Certificate, error) {
	var out []*x509.Certificate

	rest := []byte(pemValue)
	for {
		// The _ result below is not an error but the remaining PEM bytes.
		block, remaining := pem.Decode(rest)
		if block == nil {
			break
		}
		rest = remaining

		if block.Type != "CERTIFICATE" {
			return nil, fmt.Errorf("PEM-block should be CERTIFICATE type")
		}

		cert, err := x509.ParseCertificate(block.Bytes)
		if err != nil {
			return nil, err
		}
		out = append(out, cert)
	}

	if len(out) == 0 {
		return nil, fmt.Errorf("no PEM-encoded data found")
	}

	return out, nil
}

// CalculateCertFingerprint parses the x509 certificate from a PEM-encoded value
// and calculates the SHA-1 fingerprint.
func CalculateCertFingerprint(pemValue string) (string, error) {
	// The _ result below is not an error but the remaining PEM bytes.
	block, _ := pem.Decode([]byte(pemValue))
	if block == nil {
		return "", fmt.Errorf("no PEM-encoded data found")
	}

	if block.Type != "CERTIFICATE" {
		return "", fmt.Errorf("first PEM-block should be CERTIFICATE type")
	}

	hash := sha1.Sum(block.Bytes)
	return HexString(hash[:]), nil
}

// ParseSigner parses a crypto.Signer from a PEM-encoded key. The private key
// is expected to be the first block in the PEM value.
func ParseSigner(pemValue string) (crypto.Signer, error) {
	// The _ result below is not an error but the remaining PEM bytes.
	block, _ := pem.Decode([]byte(pemValue))
	if block == nil {
		return nil, fmt.Errorf("no PEM-encoded data found")
	}

	switch block.Type {
	case "EC PRIVATE KEY":
		return x509.ParseECPrivateKey(block.Bytes)

	case "RSA PRIVATE KEY":
		return x509.ParsePKCS1PrivateKey(block.Bytes)

	case "PRIVATE KEY":
		signer, err := x509.ParsePKCS8PrivateKey(block.Bytes)
		if err != nil {
			return nil, err
		}
		pk, ok := signer.(crypto.Signer)
		if !ok {
			return nil, fmt.Errorf("private key is not a valid format")
		}

		return pk, nil

	default:
		return nil, fmt.Errorf("unknown PEM block type for signing key: %s", block.Type)
	}
}

// ParseCSR parses a CSR from a PEM-encoded value. The certificate request
// must be the the first block in the PEM value.
func ParseCSR(pemValue string) (*x509.CertificateRequest, error) {
	// The _ result below is not an error but the remaining PEM bytes.
	block, _ := pem.Decode([]byte(pemValue))
	if block == nil {
		return nil, fmt.Errorf("no PEM-encoded data found")
	}

	if block.Type != "CERTIFICATE REQUEST" {
		return nil, fmt.Errorf("first PEM-block should be CERTIFICATE REQUEST type")
	}

	return x509.ParseCertificateRequest(block.Bytes)
}

// KeyId returns a x509 KeyId from the given signing key. The key must be
// an *ecdsa.PublicKey currently, but may support more types in the future.
func KeyId(raw interface{}) ([]byte, error) {
	switch raw.(type) {
	case *ecdsa.PublicKey:
	case *rsa.PublicKey:
	default:
		return nil, fmt.Errorf("invalid key type: %T", raw)
	}

	// This is not standard; RFC allows any unique identifier as long as they
	// match in subject/authority chains but suggests specific hashing of DER
	// bytes of public key including DER tags.
	bs, err := x509.MarshalPKIXPublicKey(raw)
	if err != nil {
		return nil, err
	}

	kID := sha256.Sum256(bs)
	return kID[:], nil
}

// EncodeSerialNumber encodes the given serial number as a colon-hex encoded
// string.
func EncodeSerialNumber(serial *big.Int) string {
	return HexString(serial.Bytes())
}

// EncodeSigningKeyID encodes the given AuthorityKeyId or SubjectKeyId into a
// colon-hex encoded string suitable for using as a SigningKeyID value.
func EncodeSigningKeyID(keyID []byte) string { return HexString(keyID) }

// HexString returns a standard colon-separated hex value for the input
// byte slice. This should be used with cert serial numbers and so on.
func HexString(input []byte) string {
	return strings.Replace(fmt.Sprintf("% x", input), " ", ":", -1)
}

// IsHexString returns true if the input is the output of HexString(). Meant
// for use in tests.
func IsHexString(input []byte) bool {
	s := string(input)
	if strings.Count(s, ":") < 5 { // 5 is arbitrary
		return false
	}

	s = strings.ReplaceAll(s, ":", "")
	_, err := hex.DecodeString(s)
	return err == nil
}

// KeyInfoFromCert returns the key type and key bit length for the key used by
// the certificate.
func KeyInfoFromCert(cert *x509.Certificate) (keyType string, keyBits int, err error) {
	switch k := cert.PublicKey.(type) {
	case *ecdsa.PublicKey:
		return "ec", k.Curve.Params().BitSize, nil
	case *rsa.PublicKey:
		return "rsa", k.N.BitLen(), nil
	default:
		return "", 0, fmt.Errorf("unsupported key type")
	}
}
