package aws

import (
	"context"
	"crypto/tls"
	"crypto/x509"
	"fmt"
	"time"

	awsconfig "github.com/aws/aws-sdk-go-v2/config"
	"github.com/aws/aws-sdk-go-v2/service/acmpca"
	"github.com/eko/gocache/lib/v4/cache"
	gocacheStore "github.com/eko/gocache/store/go_cache/v4"
	gocache "github.com/patrickmn/go-cache"
	"github.com/tidbcloud/pkgv2/log"
	"go.uber.org/zap"
	"k8s.io/utils/pointer"
)

const (
	// caCertCacheDuration is the duration for cache CA Certificates which used to communicate with cluster proxy.
	// because the expiration time of the CA Certificates is very long(20 years), so we can cache it for a long time.
	caCertCacheDuration = 30 * 24 * time.Hour
)

var (
	cacheManager *cache.Cache[any]
)

func init() {
	gocacheClient := gocache.New(caCertCacheDuration, 10*time.Minute)
	gocacheStore := gocacheStore.NewGoCache(gocacheClient)
	cacheManager = cache.New[any](gocacheStore)
}

// Deprecated: use ProxyTlsConfigV2 to instead
func ProxyTlsConfig(pcaArn string) (*tls.Config, error) {
	ctx := context.TODO()
	return ProxyTlsConfigV2(ctx, pcaArn)
}

func ProxyTlsConfigV2(ctx context.Context, pcaArn string) (*tls.Config, error) {
	caCertBytes, err := getCaCert(ctx, pcaArn)
	if err != nil {
		return nil, fmt.Errorf("get CA Cert failed: %s", err)
	}

	caCertPool := x509.NewCertPool()
	caCertPool.AppendCertsFromPEM(caCertBytes)
	return &tls.Config{
		InsecureSkipVerify: true,
		VerifyConnection: func(cs tls.ConnectionState) error {
			opts := x509.VerifyOptions{
				// Skip the verification of domain name as the GCP load-balancer provisions IP addresses instead of domain names.
				DNSName:       "",
				Intermediates: x509.NewCertPool(),
				Roots:         caCertPool,
			}
			for _, cert := range cs.PeerCertificates[1:] {
				opts.Intermediates.AddCert(cert)
			}
			_, err := cs.PeerCertificates[0].Verify(opts)
			return err
		},
	}, nil
}

func getCaCert(ctx context.Context, pcaArn string) ([]byte, error) {
	logger := log.WithContextS(ctx).With(zap.String("pcaArn", pcaArn))

	value, err := cacheManager.Get(ctx, pcaArn)
	if err == nil {
		logger.Infof("hit cache")
		return value.([]byte), nil
	}

	// slower branch
	logger.Infof("get CA Cert from cache failed, try slower branch: %s", err)
	awsConfig, err := awsconfig.LoadDefaultConfig(context.TODO())
	if err != nil {
		panic("AWS configuration error, " + err.Error())
	}
	svc := acmpca.NewFromConfig(awsConfig)
	output, err := svc.GetCertificateAuthorityCertificate(context.TODO(), &acmpca.GetCertificateAuthorityCertificateInput{
		CertificateAuthorityArn: pointer.String(pcaArn)})
	if err != nil {
		return nil, err
	}

	// cache it
	_ = cacheManager.Set(ctx, pcaArn, []byte(*output.Certificate))
	return []byte(*output.Certificate), nil
}
