package tls

import (
	"crypto/rand"
	"crypto/rsa"
	"crypto/x509"
	"crypto/x509/pkix"
	"fmt"
	"math/big"
	"net"
	"strings"
	"time"

	"github.com/kyverno/kyverno/pkg/config"
)

// generateCA creates the self-signed CA cert and private key
// it will be used to sign the webhook server certificate
func generateCA(key *rsa.PrivateKey, certValidityDuration time.Duration) (*rsa.PrivateKey, *x509.Certificate, error) {
	now := time.Now()
	begin, end := now.Add(-1*time.Hour), now.Add(certValidityDuration)
	if key == nil {
		newKey, err := rsa.GenerateKey(rand.Reader, 2048)
		if err != nil {
			return nil, nil, err
		}
		key = newKey
	}
	templ := &x509.Certificate{
		SerialNumber: big.NewInt(0),
		Subject: pkix.Name{
			CommonName: "*.kyverno.svc",
		},
		NotBefore:             begin,
		NotAfter:              end,
		KeyUsage:              x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment | x509.KeyUsageCertSign,
		BasicConstraintsValid: true,
		IsCA:                  true,
	}
	der, err := x509.CreateCertificate(rand.Reader, templ, templ, key.Public(), key)
	if err != nil {
		return nil, nil, err
	}
	cert, err := x509.ParseCertificate(der)
	if err != nil {
		return nil, nil, err
	}
	return key, cert, nil
}

// generateTLS takes the results of GenerateCACert and uses it to create the
// PEM-encoded public certificate and private key, respectively
func generateTLS(server string, caCert *x509.Certificate, caKey *rsa.PrivateKey, certValidityDuration time.Duration) (*rsa.PrivateKey, *x509.Certificate, error) {
	now := time.Now()
	begin, end := now.Add(-1*time.Hour), now.Add(certValidityDuration)
	dnsNames := []string{
		config.KyvernoServiceName(),
		fmt.Sprintf("%s.%s", config.KyvernoServiceName(), config.KyvernoNamespace()),
		inClusterServiceName(),
	}
	var ips []net.IP
	if server != "" {
		serverHost := ""
		if strings.Contains(server, ":") {
			host, _, err := net.SplitHostPort(server)
			if err != nil {
				logger.Error(err, "failed to split server host/port", "server", server)
			}
			serverHost = host
		}
		if serverHost != "" {
			ip := net.ParseIP(serverHost)
			if ip == nil || ip.IsUnspecified() {
				dnsNames = append(dnsNames, serverHost)
			} else {
				ips = append(ips, ip)
			}
		}
	}
	templ := &x509.Certificate{
		SerialNumber: big.NewInt(1),
		Subject: pkix.Name{
			CommonName: config.KyvernoServiceName(),
		},
		DNSNames:              dnsNames,
		IPAddresses:           ips,
		NotBefore:             begin,
		NotAfter:              end,
		KeyUsage:              x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment,
		ExtKeyUsage:           []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
		BasicConstraintsValid: true,
	}
	key, err := rsa.GenerateKey(rand.Reader, 2048)
	if err != nil {
		return nil, nil, err
	}
	der, err := x509.CreateCertificate(rand.Reader, templ, caCert, key.Public(), caKey)
	if err != nil {
		logger.Error(err, "create certificate failed")
		return nil, nil, err
	}
	cert, err := x509.ParseCertificate(der)
	if err != nil {
		logger.Error(err, "parse certificate failed")
		return nil, nil, err
	}
	return key, cert, nil
}
