package container

import (
	"context"

	plog "github.com/pingcap/log"
	"github.com/tidbcloud/dataflow-service/app/models/config_manager"
	config_manager_impl "github.com/tidbcloud/dataflow-service/app/models/config_manager/impl"
	importadminbp "github.com/tidbcloud/dataflow-service/idl/pbgen/proto/adminapi/v1beta1"
	"github.com/tidbcloud/dataflow-service/idl/pbgen/proto/dataflow"
	dmpb "github.com/tidbcloud/dataflow-service/idl/pbgen/proto/dm"
	"github.com/tidbcloud/dataflow-service/internal/config"
	eventpublisher "github.com/tidbcloud/dataflow-service/internal/event-bus/publisher"
	backupmodel "github.com/tidbcloud/dataflow-service/internal/model/backup"
	clustermodel "github.com/tidbcloud/dataflow-service/internal/model/cluster"
	connprofilemodel "github.com/tidbcloud/dataflow-service/internal/model/connprofile"
	dmmodel "github.com/tidbcloud/dataflow-service/internal/model/dm"
	importmodel "github.com/tidbcloud/dataflow-service/internal/model/import"
	statisticmodel "github.com/tidbcloud/dataflow-service/internal/model/statistic"
	"github.com/tidbcloud/dataflow-service/internal/openapi/service/backup"
	importoapi "github.com/tidbcloud/dataflow-service/internal/openapi/service/import"
	importadmin "github.com/tidbcloud/dataflow-service/internal/openapi/service/importadmin"
	"github.com/tidbcloud/dataflow-service/internal/service"
	backupservice "github.com/tidbcloud/dataflow-service/internal/service/backup"
	"github.com/tidbcloud/dataflow-service/internal/service/connprofile"
	"github.com/tidbcloud/dataflow-service/internal/service/dm"
	featureservice "github.com/tidbcloud/dataflow-service/internal/service/feature"
	importservice "github.com/tidbcloud/dataflow-service/internal/service/import"
	"github.com/tidbcloud/dataflow-service/pkg/event"
	"github.com/tidbcloud/dataflow-service/pkg/grpc/grpcli"
	"github.com/tidbcloud/dataflow-service/pkg/log"
	"github.com/tidbcloud/idl/pbgen/proto/openapi/dp"
	"github.com/tidbcloud/pkgv2/secret/encrypt"
	secretprovider "github.com/tidbcloud/pkgv2/secret/provider"
	"gorm.io/gorm"
)

var globalContainer = &GlobalContainer{}

func Global() *GlobalContainer {
	return globalContainer
}

type GlobalContainer struct {
	Encryptor encrypt.Encryptor

	BackupsModel     backupmodel.Model
	ImportsModel     importmodel.Model
	ClusterModel     clustermodel.Model
	DmModel          dmmodel.Model
	StatisticModel   statisticmodel.Model
	ConnProfileModel connprofilemodel.Model

	DPFeatureConsoleAPI   dataflow.DPFeatureServiceServer
	BackupConsoleAPI      dataflow.BackupServiceServer
	DMConsoleAPI          dmpb.ServiceServer
	ImportConsoleAPI      dataflow.ImportServiceServer
	ConnProfileConsoleAPI dataflow.ConnProfileServiceServer

	ImportAdminAPI importadminbp.ImportServiceServer
	BackupOpenAPI  dp.BackupServer
	ImportOpenAPI  dp.ImportServer

	ImportInnerAPI dataflow.ImportInnerServiceServer

	ConfigCenterGrpcClient  grpcli.ConfigCenterGrpcClient
	ConfigCenterManager     config_manager.ConfigCenterManager
	MgmtSvcGrpcClient       grpcli.MgmtSvcGrpcClient
	ServerlessSvcGrpcClient grpcli.ServerlessSvcGrpcClient
}

func SetUpContainers(ctx context.Context, db *gorm.DB) error {
	ctx = log.WithLogID(ctx)
	logger := log.GetLogger(ctx)
	container := &GlobalContainer{}

	initGrpcClient(ctx, container)

	encryptor, err := secretprovider.NewSecretEncryptor(config.Global().InfraConfig.CMKArn)
	if err != nil {
		return err
	}
	container.Encryptor = encryptor
	if err := initModels(container); err != nil {
		return err
	}

	initConsoleAPI(container)
	initOpenAPI(container)
	initInnerAPI(container)

	globalContainer = container
	logger.Info("container setup success")
	return nil
}

func initGrpcClient(ctx context.Context, container *GlobalContainer) {

	container.ConfigCenterGrpcClient = grpcli.NewConfigCenterGrpcClient(
		ctx,
		config.Global().GrpcConfig.ConfigCenterAddr)
	container.MgmtSvcGrpcClient = grpcli.NewMgmtSvcGrpcClient(
		ctx, config.Global().GrpcConfig.TiDBMgmtSvcAddr)
	container.ServerlessSvcGrpcClient = grpcli.NewServerlessSvcGrpcClient(ctx,
		config.Global().GrpcConfig.ServerlessSvcAddr)
}

func initInnerAPI(container *GlobalContainer) {
	container.ImportInnerAPI = importservice.NewInternalService(container.ImportsModel, container.ClusterModel)
}

func initOpenAPI(container *GlobalContainer) {
	container.BackupOpenAPI = backup.NewService(container.BackupConsoleAPI)
	container.ImportAdminAPI = importadmin.NewService(container.ImportConsoleAPI) // NOTE: OpenAPI must be initialized after ImportConsoleAPI
	container.ImportOpenAPI = importoapi.NewService(container.ImportsModel, container.ClusterModel,
		container.MgmtSvcGrpcClient, container.ServerlessSvcGrpcClient)
}

func initConsoleAPI(container *GlobalContainer) {
	container.DPFeatureConsoleAPI = featureservice.NewService(service.New(), container.BackupsModel, container.MgmtSvcGrpcClient)
	container.BackupConsoleAPI = backupservice.NewService(service.New(), container.BackupsModel, container.MgmtSvcGrpcClient)
	container.DMConsoleAPI = dm.NewService(
		service.New(), container.DmModel,
		connprofile.NewService(service.New(), container.ConnProfileModel),
	)
	container.ImportConsoleAPI = importservice.NewService(service.New(), container.ImportsModel, container.ClusterModel)
	container.ConnProfileConsoleAPI = connprofile.NewService(service.New(), container.ConnProfileModel)
}

func initModels(container *GlobalContainer) error {
	container.ConfigCenterManager = config_manager_impl.NewConfigCenterManager(container.ConfigCenterGrpcClient)
	container.StatisticModel = statisticmodel.NewModel(plog.L())
	container.ImportsModel = importmodel.NewModel(plog.L(), container.Encryptor,
		eventpublisher.NewEventBusPublisherImpl(event.ImportAppName), container.ConfigCenterManager)
	container.BackupsModel = backupmodel.NewModel(plog.L())
	container.DmModel = dmmodel.NewModel(plog.L(), container.Encryptor,
		&container.ImportsModel, container.ConfigCenterManager)
	container.ConnProfileModel = connprofilemodel.NewModel(plog.L())
	container.ClusterModel = clustermodel.NewModel(plog.L())
	return nil
}
