package infra

import (
	"context"
	"fmt"
	"sync"

	"github.com/tidbcloud/pkgv2/secret/encrypt"
	"github.com/tidbcloud/pkgv2/secret/provider"

	"github.com/tidbcloud/account/commons/config"
	"github.com/tidbcloud/core-svc/idl/pbgen/proto/core"
	"github.com/tidbcloud/pkgv2/clusterproxy"
	"github.com/tidbcloud/pkgv2/clusterproxy/tidbcloud"
	"github.com/tidbcloud/tiinf/errs"
	"github.com/tidbcloud/tiinf/monitor/tilog"
	"go.uber.org/zap"
)

const (
	cloudAminUser = "cloud_admin"
)

func ExecQuerySql(ctx context.Context, cluster *core.Cluster, operator *clusterproxy.ClusterOperator, querySql string) (string, error) {
	tilog.Info(ctx, "executeQuerySql request", zap.Any("clusterID", cluster.Id), zap.Any("operatorUsername", operator.Username), zap.Any("sql", querySql))
	proxy, err := GetClusterProxy(ctx, cluster)
	if err != nil {
		tilog.Warn(ctx, "executeQuerySql GetClusterProxy error", zap.Any("err", err))
		return "", err
	}
	bytes, err := proxy.QuerySQLV2(ctx, operator, querySql)
	if err != nil {
		tilog.Warn(ctx, "executeQuerySql QuerySQLV2 error", zap.Any("err", err))
		return "", err
	}
	result := string(bytes)
	tilog.Info(ctx, "executeQuerySql response", zap.Any("rsp", result))

	return result, nil
}

func GetClusterProxy(ctx context.Context, cluster *core.Cluster) (clusterproxy.ClusterProxy, error) {
	if cluster == nil {
		return nil, errs.NewBadRequest("cluster is nil", nil)
	}
	if cluster.InternalEndpoint == "" {
		return nil, errs.NewInternalUnknown("cluster internal endpoint is empty", nil)
	}
	proxyConfig := config.Global().ClusterProxyConfig
	pConfig := tidbcloud.ProxyConfig{
		Auth0Domain:       proxyConfig.Auth0Domain,
		Auth0ClientID:     proxyConfig.Auth0ClientID,
		Auth0ClientSecret: proxyConfig.Auth0ClientSecret,
		Auth0Audience:     proxyConfig.Auth0Audience,
	}

	input := tidbcloud.ClusterProxyInput{
		PcaARN:    getPcaArn(),
		Cfg:       pConfig,
		ClusterID: cluster.Id,
		BaseURL:   cluster.InternalEndpoint,
		Version:   cluster.Version,
		Timeout:   5,
	}
	//tilog.Info(ctx, "GetClusterProxy config", zap.Any("config", proxyConfig), zap.Any("internalEndpoint", cluster.InternalEndpoint), zap.Any("version", cluster.Version))

	p, err := tidbcloud.NewClusterProxyV2(ctx, input)
	if err != nil {
		return nil, err
	}
	return p, nil
}

func getPcaArn() string {
	if config.IsProdEnv() {
		return "arn:aws:acm-pca:us-west-2:380838443567:certificate-authority/ed5106d3-0890-4af9-bd60-b6df4f6299ca"
	}
	return "arn:aws:acm-pca:us-west-2:385595570414:certificate-authority/4668d5cc-1042-4371-9ff4-72ab9493c545"
}

func GetClusterCloudAdmin(ctx context.Context, cluster *core.Cluster) (*clusterproxy.ClusterOperator, error) {
	isDedicated := !cluster.IsDevTier
	var username string
	if isDedicated {
		username = cloudAminUser
	} else {
		username = fmt.Sprintf("%s.%s", *cluster.ServerlessToken, cloudAminUser)
	}

	req := &core.GetClusterConfidentialInfoReq{
		ClusterId: cluster.Id,
	}
	rsp, err := GetHandler().GetClusterConfidentialInfo(ctx, req)
	tilog.Info(ctx, "GetClusterCloudAdmin infra GetClusterConfidentialInfo result", zap.Any("rsp", rsp), zap.Any("req", req), zap.Any("err", err))
	if err != nil {
		tilog.Error(ctx, "GetClusterCloudAdmin infra GetClusterConfidentialInfo error", zap.Any("err", err))
		return nil, err
	}
	credential, err := decryptCredential(ctx, rsp.Info.EncryptedCloudAdminPasswd)
	if err != nil {
		tilog.Error(ctx, "GetClusterCloudAdmin getBase64Credential error", zap.Any("err", err))
		return nil, err
	}

	cmOperator := &clusterproxy.ClusterOperator{
		Username:   username,
		AuthMethod: clusterproxy.AuthMethodPassword,
		Credential: credential,
	}

	return cmOperator, nil
}

func decryptCredential(ctx context.Context, encryptCredential string) (string, error) {
	enc := getSecretEncryptor(ctx)
	if enc == nil {
		return "", errs.NewInternalUnknown("secret encryptor init fail", nil)
	}
	credential, err := encrypt.DecryptBase64(enc, encryptCredential)
	if err != nil {
		return "", errs.NewInternalUnknown("DecryptBase64 encryptCredential err", err)
	}
	return string(credential), nil

}

var (
	encInstance encrypt.Encryptor
	encInitOnce sync.Once
)

func getSecretEncryptor(ctx context.Context) encrypt.Encryptor {
	encInitOnce.Do(func() {
		cmkArn := getCmkArn()
		enc, err := provider.NewSecretEncryptor(cmkArn)
		if err != nil {
			tilog.Info(ctx, "NewSecretEncryptor fail", zap.Error(err))
			return
		}
		encInstance = enc
	})
	return encInstance
}

func getCmkArn() string {
	var cmkArn string
	if config.IsProdEnv() {
		cmkArn = "arn:aws:kms:us-west-2:380838443567:key/6a3f87f3-d032-414b-870f-458979546c97"
	} else {
		cmkArn = "arn:aws:kms:us-west-2:385595570414:key/f15101b5-6aed-48bf-9b9a-fc36406efe92"
	}
	return cmkArn
}
