package singleton

import (
	"crypto/tls"
	"fmt"
	"sync"

	config2 "github.com/tidbcloud/core-portal/internal/central/config"
	infraapi "github.com/tidbcloud/core-portal/internal/central/infra-api"
	debugAuth "github.com/tidbcloud/core-portal/internal/central/pkg/auth/debug"
	"github.com/tidbcloud/pkgv2/auth/auth0"
	"github.com/tidbcloud/pkgv2/infra/aws"
	"github.com/tidbcloud/pkgv2/secret/encrypt"
	secretprovider "github.com/tidbcloud/pkgv2/secret/provider"
)

var (
	debugSessionManager     *debugAuth.SessionManager
	debugSessionManagerOnce sync.Once
	aesCrypt                *encrypt.AESCrypt
	aesCryptOnce            sync.Once
	encryptor               encrypt.Encryptor
	encryptorOnce           sync.Once
	tokenValidator          *auth0.RawTokenValidator
	tokenValidatorOnce      sync.Once
	tlsConfig               *tls.Config
	tlsConfigOnce           sync.Once
)

func GetDebugSessionManager() *debugAuth.SessionManager {
	debugSessionManagerOnce.Do(func() {
		debugSessionManager = debugAuth.NewSessionManager(&config2.Global().SessionConfig)
	})
	return debugSessionManager
}

func GetAesCrypt() *encrypt.AESCrypt {
	aesCryptOnce.Do(func() {
		serverConfig := config2.Global().ServerConfig
		inst, err := encrypt.NewAESCrypt([]byte(serverConfig.SaltSecret))
		if err != nil {
			panic(fmt.Sprintf("init aesCrypt: %s", err))
		}
		aesCrypt = inst
	})

	return aesCrypt
}

func GetEncryptor() encrypt.Encryptor {
	encryptorOnce.Do(func() {
		CMKArn := config2.Global().InfraConfig.CMKArn
		inst, err := secretprovider.NewSecretEncryptor(CMKArn)
		if err != nil {
			panic(fmt.Sprintf("init encryptor: %s", err))
		}
		encryptor = inst
	})
	return encryptor
}

func GetTokenValidator() *auth0.RawTokenValidator {
	tokenValidatorOnce.Do(func() {
		centralAuth0 := GetCentralAuth0()
		managementPortalAuth0 := GetManagementPortalAuth0()
		jwks, err := auth0.GetJWKS(centralAuth0.Domain)
		if err != nil {
			panic(fmt.Sprintf("get central auth0 jwks: %s", err))
		}
		managementPortalJWKs, err := auth0.GetJWKS(managementPortalAuth0.Domain)
		if err != nil {
			panic(fmt.Sprintf("get management portal auth0 jwks: %s", err))
		}

		tokenValidator = auth0.NewRawTokenValidator().
			RegisterAuth(auth0.AuthValidatorIDCentral, centralAuth0, jwks).
			RegisterAuth(auth0.AuthValidatorIDManagement, managementPortalAuth0, managementPortalJWKs)
	})

	return tokenValidator
}

func GetTlsConfig() *tls.Config {
	tlsConfigOnce.Do(func() {
		cfg, err := aws.ProxyTlsConfig(config2.Global().AwsConfig.PCAArn)
		if err != nil {
			panic(fmt.Sprintf("failed to get proxy TLS config: %s", err))
		}
		tlsConfig = cfg
	})

	return tlsConfig
}

func GetCentralAuth0() auth0.Conf {
	return auth0.Conf{
		Audience: config2.Global().Auth0Config.Auth0Audience,
		Secret:   []byte(config2.Global().Auth0Config.Auth0Secret),
		Domain:   config2.Global().Auth0Config.Auth0Domain,
	}
}

func GetManagementPortalAuth0() auth0.Conf {
	return auth0.Conf{
		Audience:                 config2.Global().Auth0Config.ManagementPortalAuth0Audience,
		Secret:                   []byte(config2.Global().Auth0Config.ManagementPortalAuth0Secret),
		Domain:                   config2.Global().Auth0Config.ManagementPortalAuth0Domain,
		ClientCredentialAudience: config2.Global().Auth0Config.ManagementPortalAuth0ClientAudience,
	}
}

// WarmupSingleton init singletons in launching stage to avoid cold boot
func WarmupSingleton() {
	GetDebugSessionManager()
	GetAesCrypt()
	GetEncryptor()
	GetTokenValidator()
	GetTlsConfig()
	GetCentralAuth0()
	GetManagementPortalAuth0()
	infraapi.InitInfraAPI()
}
