package app

import (
	"os"
	"strconv"
	"strings"

	"github.com/prometheus/client_golang/prometheus"
	pkgv2db "github.com/tidbcloud/pkgv2/db"
	"github.com/tidbcloud/tiinf/transport/tieventbus"

	cloudproviderapp "github.com/tidbcloud/tidb-management-service/serverless-global-service/app/cloudprovider"
	clusterapp "github.com/tidbcloud/tidb-management-service/serverless-global-service/app/cluster"
	dmapp "github.com/tidbcloud/tidb-management-service/serverless-global-service/app/dm"
	importapp "github.com/tidbcloud/tidb-management-service/serverless-global-service/app/import"
	pgbackupproxyapp "github.com/tidbcloud/tidb-management-service/serverless-global-service/app/pg_backup_proxy"
	plcapp "github.com/tidbcloud/tidb-management-service/serverless-global-service/app/privatelink"
	regionapp "github.com/tidbcloud/tidb-management-service/serverless-global-service/app/region"
	"github.com/tidbcloud/tidb-management-service/serverless-global-service/app/register"
	sqluserapp "github.com/tidbcloud/tidb-management-service/serverless-global-service/app/sqluser"
	"github.com/tidbcloud/tidb-management-service/serverless-global-service/domain/adapter"
	"github.com/tidbcloud/tidb-management-service/serverless-global-service/domain/service/cluster"
	dmdomain "github.com/tidbcloud/tidb-management-service/serverless-global-service/domain/service/dm"
	importdomain "github.com/tidbcloud/tidb-management-service/serverless-global-service/domain/service/import"
	plcdomain "github.com/tidbcloud/tidb-management-service/serverless-global-service/domain/service/privatelink"
	"github.com/tidbcloud/tidb-management-service/serverless-global-service/domain/service/region"
	"github.com/tidbcloud/tidb-management-service/serverless-global-service/domain/service/sqluser"
	"github.com/tidbcloud/tidb-management-service/serverless-global-service/infra/adapterimpl"
	"github.com/tidbcloud/tidb-management-service/serverless-global-service/infra/adapterimpl/cli"
	"github.com/tidbcloud/tidb-management-service/serverless-global-service/infra/metrics"
	"github.com/tidbcloud/tidb-management-service/serverless-global-service/infra/persistence/db"
	"github.com/tidbcloud/tidb-management-service/serverless-global-service/infra/persistence/repo"
	"github.com/tidbcloud/tidb-management-service/serverless-global-service/pkg/config"
	"github.com/tidbcloud/tidb-management-service/utilities/clusterevent"
	clusterproxyutil "github.com/tidbcloud/tidb-management-service/utilities/clusterproxy"
	"github.com/tidbcloud/tidb-management-service/utilities/featuregate"
	"github.com/tidbcloud/tidb-management-service/utilities/idgenerator"
	"github.com/tidbcloud/tidb-management-service/utilities/workflow"
)

const appName = "serverless-global-service"

func InitDependencies() {
	register.Provide(db.NewGormDB)
	register.Provide(db.NewDBProvider)
	register.Provide(newFeatureGate)

	register.Provide(newEventbus)
	register.Provide(newWALCommitter)
	register.Provide(newWALWatcher)
	register.Provide(clusterevent.NewPublisher)

	// Universal unique id
	register.Provide(newIDGenerator)
	register.Provide(newWorkflow)

	register.Provide(repo.NewCloudProviderRepo)
	register.Provide(repo.NewClusterRepoImpl)
	register.Provide(repo.NewRegionRepoImpl)

	register.Provide(adapterimpl.NewBRService)
	register.Provide(adapterimpl.NewServerless)
	register.Provide(adapterimpl.NewEncryptor)
	register.Provide(adapterimpl.NewBilling)
	register.Provide(adapterimpl.NewSys9SecretProvider)
	register.Provide(adapterimpl.NewSys9AuthManager)
	register.Provide(adapterimpl.NewSys9Lifecycle)

	register.Provide(cli.NewRegionalProxyClientImpl)
	register.Provide(adapterimpl.NewRegionalServiceProxy)
	register.Provide(adapterimpl.NewRegionServiceImpl)

	register.Provide(newClusterProxy)
	register.Provide(sqluser.NewSqlUserService)
	register.Provide(adapterimpl.NewAccountService)

	register.Provide(cluster.NewClusterService)
	register.Provide(region.NewRegionServiceImpl)

	register.Provide(importdomain.NewImportService)
	register.Provide(importapp.NewService)

	register.Provide(dmdomain.NewDMService)
	register.Provide(dmapp.NewService)

	register.Provide(clusterapp.NewService)
	register.Provide(regionapp.NewService)
	register.Provide(cloudproviderapp.NewService)
	register.Provide(sqluserapp.NewService)

	register.Provide(plcdomain.NewPrivateLinkConnectionService)
	register.Provide(plcapp.NewService)
	register.Provide(pgbackupproxyapp.NewService)

	register.Provide(metrics.NewClusterStatCollector)
}

func RegisterMetrics() {
	prometheus.MustRegister(metrics.WatchClusterPendingTaskFailedTotal)
}

func newEventbus() tieventbus.Eventbus {
	return clusterevent.NewEventbus(appName, config.Global().Eventbus.ProxyAddress)
}

func newWALCommitter(dbProvider pkgv2db.TransProvider) adapter.WALCommitter {
	return clusterevent.NewWALCommitter(dbProvider)
}

func newWALWatcher(dbProvider pkgv2db.TransProvider, handler clusterevent.Publisher) clusterevent.WALWatcher {
	return clusterevent.NewWALWatcher(dbProvider, handler, appName)
}

func newClusterProxy() clusterproxyutil.Provider {
	cfg := config.Global()
	return clusterproxyutil.NewClusterProxyProvider(
		cfg.AwsConfig.PCAArn,
		cfg.ProxyConfig.Auth0Domain,
		cfg.ProxyConfig.Auth0ClientID,
		cfg.ProxyConfig.Auth0ClientSecret,
		cfg.ProxyConfig.Auth0Audience,
	)
}

func newIDGenerator() idgenerator.IDGenerator {
	podName := os.Getenv(PodNameEnvKey)
	podIP := os.Getenv(idgenerator.DefaultPodIPEnvKey)
	nid := idgenerator.AutoNodeID
	if podName != "" {
		i := strings.LastIndex(podName, "-")
		if i != -1 {
			node := podName[i+1:]
			nodeNum, err := strconv.ParseUint(node, 10, 64)
			if err == nil {
				nid = idgenerator.NodeID(nodeNum)
			}
		}
	}

	return idgenerator.NewSonyflakeID(nid, podIP)
}

const (
	PodNameEnvKey = "POD_NAME"
)

func newWorkflow(provider pkgv2db.TransProvider, idGenerator idgenerator.IDGenerator) workflow.Workflow {
	podName := os.Getenv(PodNameEnvKey)

	defaultConfig := register.GetMysqlDefaultConfig()

	w, err := workflow.NewWorkflow(
		workflow.ID(podName),
		defaultConfig,
		provider,
		idGenerator,
		workflow.WithWorkerMode(workflow.ModeCompetitive),
	)
	if err != nil {
		panic(err)
	}

	return w
}

func newFeatureGate() featuregate.Service {
	return featuregate.NewFeatureGate(config.Global().ServerConfig.FeatureGates, nil)
}
