package models

import (
	"time"

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

	"github.com/juju/errors"
	"github.com/mailgun/mailgun-go/v4"
	"github.com/patrickmn/go-cache"
	"github.com/pingcap/dbaas/central/config"
	cdcdao "github.com/pingcap/dbaas/central/db/dbutil/cdc"
	clusterdao "github.com/pingcap/dbaas/central/db/dbutil/cluster"
	replicationdao "github.com/pingcap/dbaas/central/db/dbutil/replication"
	"github.com/pingcap/dbaas/central/dbinit"
	infraapi "github.com/pingcap/dbaas/central/infra-api"
	adminmodel "github.com/pingcap/dbaas/central/model/admin"
	"github.com/pingcap/dbaas/central/model/admin/grafana"
	"github.com/pingcap/dbaas/central/model/billing"
	"github.com/pingcap/dbaas/central/model/cdc"
	"github.com/pingcap/dbaas/central/model/session"
	"github.com/pingcap/dbaas/central/model/tenant"
	"github.com/pingcap/dbaas/central/model/testtoken"
	"github.com/pingcap/dbaas/central/test"
	"github.com/pingcap/log"
	"github.com/sendgrid/sendgrid-go"
	mailv2 "github.com/tidbcloud/pkgv2/mail/v2"
	panicIf "github.com/tidbcloud/pkgv2/panicif"
	secretprovider "github.com/tidbcloud/pkgv2/secret/provider"
	"github.com/tidbcloud/pkgv2/zendesk"
	"go.uber.org/zap"
	"gopkg.in/auth0.v4/management"
)

var (
	adminModel     *adminmodel.Model
	testTokenModel *testtoken.Model
)

func GetAdminModel() *adminmodel.Model {
	return adminModel
}

func GetTestTokenModel() *testtoken.Model {
	return testTokenModel
}

func InitMySQL(isUnitTest bool) {
	if isUnitTest {
		log.Info("init db in unittest mode")
		test.Setup()
	} else {
		mysqlConfig := config.Global().MysqlConfig
		log.Info("Initialize MySQL connection", zap.String("user", mysqlConfig.User))
		_, err := dbinit.Initialize(mysqlConfig, false)
		panicIf.Error(err)
	}
}

func InitAdminModel(infraAPI *infraapi.API) error {
	var err error

	serverConfig := config.Global().ServerConfig
	CMKArn := config.Global().InfraConfig.CMKArn

	enc, err := secretprovider.NewSecretEncryptor(CMKArn)
	if err != nil {
		return errors.Trace(err)
	}
	aesCrypt, err := encrypt.NewAESCrypt([]byte(serverConfig.SaltSecret))
	if err != nil {
		return errors.Trace(err)
	}
	session := session.NewSession(aesCrypt)
	mg := mailgun.NewMailgun(config.Domain, serverConfig.MailGunApiKey)
	sgCli := sendgrid.NewSendClient(serverConfig.SendgridApiKey)
	mailFacade := mailv2.NewMailProvider(mg, sgCli, config.SendgridTemplateName2id)

	zendeskConfig := config.Global().ZendeskConfig
	var zendeskClient *zendesk.Client
	if zendeskConfig.EnableZendesk {
		zendeskClient, err = zendesk.NewClient(zendeskConfig.ZendeskDomain, zendeskConfig.ZendeskEmail, zendeskConfig.ZendeskAPIToken)
		if err != nil {
			return errors.Annotate(err, "initialize the zendesk client failed")
		}
	}

	auth0Config := config.Global().Auth0Config
	m, err := management.New(auth0Config.Auth0Domain, auth0Config.Auth0ManagementClientID, auth0Config.Auth0ManagementClientSecret)
	if err != nil {
		return errors.Trace(err)
	}

	salesforceConfig := config.Global().SalesforceConfig
	var salesforceClient *salesforce.Client
	if salesforceConfig.EnableSalesforce {
		accessTokenCache := cache.New(20*time.Minute, 10*time.Minute)
		salesforceCfg := &salesforce.Config{
			SalesforceDomain:       config.Global().SalesforceConfig.SalesforceDomain,
			SalesforceClientID:     config.Global().SalesforceConfig.SalesforceClientID,
			SalesforceClientSecret: config.Global().SalesforceConfig.SalesforceClientSecret,
			SalesforceUsername:     config.Global().SalesforceConfig.SalesforceUsername,
			SalesforcePassword:     config.Global().SalesforceConfig.SalesforcePassword,
		}
		salesforceClient, err = salesforce.NewClient(accessTokenCache, salesforceCfg)
		if err != nil {
			return err
		}
	}
	grafanaConfig := config.Global().GrafanaConfig
	grafanaClient := grafana.NewClient(grafanaConfig.Server, grafanaConfig.APIKey, grafana.DefaultHTTPClient)
	billingModel := billing.NewModel(log.L(), mailFacade, salesforceClient)
	tenantModel := tenant.NewModel(log.L(), session, mailFacade, m.User, zendeskClient, salesforceClient, billingModel, nil)
	cdcdao := &cdcdao.CDCDao{}
	clusterdao := &clusterdao.ClusterDao{}
	replicationdao := &replicationdao.ReplicationDao{}
	cdcModel := cdc.NewModel(log.L(), enc, infraAPI, cdcdao, clusterdao, replicationdao, billingModel)
	admin := adminmodel.NewModel(log.L(), mailFacade, grafanaClient, salesforceClient, infraAPI, tenantModel, billingModel, cdcModel)
	adminModel = &admin
	t := testtoken.NewModel(log.L(), auth0Config.Auth0Domain, auth0Config.Auth0ManagementClientID, auth0Config.Auth0ManagementClientSecret, aesCrypt, nil, nil)
	testTokenModel = &t
	return nil
}

func InitFakeAdminModel(log *zap.Logger, mailFacade mailv2.Facade,
	grafanaClient adminmodel.GrafanaCollector, salesforceClient tenant.SalesforceHandler, api *infraapi.API,
	session *session.Session, authHandler tenant.Auth0UserManagementHandler, zendeskHandler tenant.ZendeskHandler,
) error {
	billingModel := billing.NewModel(log, mailFacade, salesforceClient)
	tenantModel := tenant.NewModel(log, session, mailFacade, authHandler, zendeskHandler, salesforceClient, billingModel, nil)

	m := adminmodel.NewModel(log, mailFacade, grafanaClient, salesforceClient, api, tenantModel, billingModel, cdc.Model{})

	adminModel = &m
	return nil
}
