package pubsub

import (
	"crypto/tls"
	"crypto/x509"
	"encoding/pem"
	"errors"
	"fmt"
)

// TLSProperties is a struct that contains the TLS properties.
type TLSProperties struct {
	CACert     string
	ClientCert string
	ClientKey  string
}

const (
	// CACert is the metadata key name for the CA certificate.
	CACert = "caCert"
	// ClientCert is the metadata key name for the client certificate.
	ClientCert = "clientCert"
	// ClientKey is the metadata key name for the client key.
	ClientKey = "clientKey"
)

// TLS takes a metadata object and returns the TLSProperties configured.
func TLS(metadata map[string]string) (TLSProperties, error) {
	cfg := TLSProperties{}

	if val, ok := metadata[CACert]; ok && val != "" {
		if !isValidPEM(val) {
			return TLSProperties{}, errors.New("invalid caCert")
		}
		cfg.CACert = val
	}
	if val, ok := metadata[ClientCert]; ok && val != "" {
		if !isValidPEM(val) {
			return TLSProperties{}, errors.New("invalid clientCert")
		}
		cfg.ClientCert = val
	}
	if val, ok := metadata[ClientKey]; ok && val != "" {
		if !isValidPEM(val) {
			return TLSProperties{}, errors.New("invalid clientKey")
		}
		cfg.ClientKey = val
	}

	return cfg, nil
}

// ConvertTLSPropertiesToTLSConfig converts the TLSProperties to a tls.Config.
func ConvertTLSPropertiesToTLSConfig(properties TLSProperties) (*tls.Config, error) {
	tlsConfig := new(tls.Config)

	if properties.ClientCert != "" && properties.ClientKey != "" {
		cert, err := tls.X509KeyPair([]byte(properties.ClientCert), []byte(properties.ClientKey))
		if err != nil {
			return tlsConfig, fmt.Errorf("unable to load client certificate and key pair. Err: %v", err)
		}
		tlsConfig.Certificates = []tls.Certificate{cert}
	}

	if properties.CACert != "" {
		tlsConfig.RootCAs = x509.NewCertPool()
		if ok := tlsConfig.RootCAs.AppendCertsFromPEM([]byte(properties.CACert)); !ok {
			return tlsConfig, fmt.Errorf("unable to load CA certificate")
		}
	}

	return tlsConfig, nil
}

// isValidPEM validates the provided input has PEM formatted block.
func isValidPEM(val string) bool {
	block, _ := pem.Decode([]byte(val))

	return block != nil
}
