package controller

import (
	"fmt"

	"github.com/pingcap/log"
	"github.com/spf13/viper"
	ov "github.com/tidbcloud/config-service/oneconfig/viper"
	"github.com/tidbcloud/serverless-service/idl/pbgen/proto/console"
	"github.com/tidbcloud/serverless-service/idl/pbgen/proto/rpc"
	"github.com/tidbcloud/serverless-service/idl/pbgen/proto/serverless"
	"github.com/tidbcloud/serverless-service/idl/pbgen/proto/v1beta"
	"github.com/tidbcloud/serverless-service/idl/pbgen/proto/v1beta1"
	"github.com/tidbcloud/serverless-service/internal/config"
	"github.com/tidbcloud/serverless-service/internal/domain/facade"
	"github.com/tidbcloud/serverless-service/internal/infra/manager"
	"github.com/tidbcloud/serverless-service/internal/infra/manager/canaryv2"
	"github.com/tidbcloud/serverless-service/internal/storage/migrate"
	"go.uber.org/zap"
	"google.golang.org/grpc"
	"gorm.io/gorm"
	"sigs.k8s.io/controller-runtime/pkg/manager/signals"
)

var sigCtx = signals.SetupSignalHandler()

func RunDBMigrate() {
	//nolint:gocritic
	if err := DiCall(func(db *gorm.DB) error {
		return migrate.RunDBMigrate(db)
	}); err != nil {
		panic(fmt.Sprintf("do DB migration failed: %s", err))
	}
	log.Info("migrate completed")
}

// RegisterGrpcServices register exposed grpc services
func RegisterGrpcServices(server *grpc.Server) {
	err := DiCall(func(
		billingRpc serverless.ServerlessBillingServiceServer,
		branchRpc rpc.BranchServiceServer,
		clusterRpc rpc.ClusterServiceServer,
		userRpc rpc.UserServiceServer,
		brRpc rpc.BackupRestoreServiceServer,
		importRpc console.ImportServiceServer,
		clusterOpsRpc rpc.ClusterCROperationServiceServer,
		k8sOpsRpc rpc.RegionalK8SOperationServiceServer,
		cdcOpsRpc rpc.ChangefeedOpsServiceServer,
	) {
		serverless.RegisterServerlessBillingServiceServer(server, billingRpc)
		rpc.RegisterBranchServiceServer(server, branchRpc)
		rpc.RegisterClusterServiceServer(server, clusterRpc)
		rpc.RegisterUserServiceServer(server, userRpc)
		rpc.RegisterBackupRestoreServiceServer(server, brRpc)
		console.RegisterImportServiceServer(server, importRpc)
		rpc.RegisterClusterCROperationServiceServer(server, clusterOpsRpc)
		rpc.RegisterRegionalK8SOperationServiceServer(server, k8sOpsRpc)
		rpc.RegisterChangefeedOpsServiceServer(server, cdcOpsRpc)
	})
	if err != nil {
		panic(fmt.Sprintf("grpc services init failed: %s", err))
	}
}

// RegisterLrpcServices register local rpc services for http-gateway
func RegisterLrpcServices(server *grpc.Server) {
	err := DiCall(func(
		billingConsole serverless.ServerlessBillingServiceServer,
		branchConsole console.BranchServiceServer,
		clusterConsole console.ClusterServiceServer,
		exportConsole console.ExportServiceServer,
		brConsole console.BackupRestoreServiceServer,
		importConsole console.ImportServiceServer,
		importV1beta v1beta.ImportServiceServer,
		branchV1beta v1beta.BranchServiceServer,
		userV1beta v1beta.UserServiceServer,
		branchV1beta1 v1beta1.BranchServiceServer,
		importV1beta1 v1beta1.ImportServiceServer,
		exportV1beta1 v1beta1.ExportServiceServer,
		auditLogV1beta1 v1beta1.AuditLogServiceServer,
		opsServer rpc.ClusterCROperationServiceServer,
		cdcV1beta1 v1beta1.ChangefeedServiceServer,
		clusterOpsServer rpc.ClusterCROperationServiceServer,
		k8sOpsServer rpc.RegionalK8SOperationServiceServer,
		cdcOpsServer rpc.ChangefeedOpsServiceServer,
		databaseAuditLogConsole console.DatabaseAuditLogServiceServer,
		databaseAuditLogV1beta1 v1beta1.DatabaseAuditLogServiceServer,
		dmConsole console.MigrationServiceServer,
		privateLinkConnectionV1beta1 v1beta1.PrivateLinkConnectionServiceServer,
		dmV1beta1 v1beta1.MigrationServiceServer,
		changefeedConsole console.ChangefeedServiceServer,
		privateLinkConnectionConsole console.PrivateLinkConnectionServiceServer,
	) {
		serverless.RegisterServerlessBillingServiceServer(server, billingConsole)
		console.RegisterBranchServiceServer(server, branchConsole)
		console.RegisterClusterServiceServer(server, clusterConsole)
		console.RegisterExportServiceServer(server, exportConsole)
		console.RegisterBackupRestoreServiceServer(server, brConsole)
		console.RegisterImportServiceServer(server, importConsole)
		console.RegisterDatabaseAuditLogServiceServer(server, databaseAuditLogConsole)
		console.RegisterMigrationServiceServer(server, dmConsole)
		console.RegisterChangefeedServiceServer(server, changefeedConsole)
		console.RegisterPrivateLinkConnectionServiceServer(server, privateLinkConnectionConsole)

		// register openapi service
		v1beta.RegisterImportServiceServer(server, importV1beta)
		v1beta.RegisterBranchServiceServer(server, branchV1beta)
		v1beta.RegisterUserServiceServer(server, userV1beta)
		v1beta1.RegisterBranchServiceServer(server, branchV1beta1)
		v1beta1.RegisterImportServiceServer(server, importV1beta1)
		v1beta1.RegisterExportServiceServer(server, exportV1beta1)
		v1beta1.RegisterAuditLogServiceServer(server, auditLogV1beta1)
		v1beta1.RegisterChangefeedServiceServer(server, cdcV1beta1)
		v1beta1.RegisterDatabaseAuditLogServiceServer(server, databaseAuditLogV1beta1)
		v1beta1.RegisterPrivateLinkConnectionServiceServer(server, privateLinkConnectionV1beta1)
		v1beta1.RegisterMigrationServiceServer(server, dmV1beta1)

		// register ops service
		rpc.RegisterClusterCROperationServiceServer(server, clusterOpsServer)
		rpc.RegisterRegionalK8SOperationServiceServer(server, k8sOpsServer)
		rpc.RegisterChangefeedOpsServiceServer(server, cdcOpsServer)
	})
	if err != nil {
		panic(fmt.Sprintf("lrpc services init failed: %s", err))
	}
}

func RunReconcilers() {
	_ = DiCall(func(
		localManager *manager.LocalElectionManager,
		canaryV2Reconciler *canaryv2.Reconciler,
	) {
		log.Info("branch reconciler is disabled; branchSyncWorker is polling branch status")

		if !config.Global().WorkerConfig.DisableCanaryController {
			if err := canaryV2Reconciler.SetupWithManager(localManager); err != nil {
				panic(fmt.Sprintf("failed to setup canary v2 reconciler: %s", err))
			}
		}

		go func() {
			log.Info("start local elect mgr")
			if err := localManager.Start(sigCtx); err != nil {
				panic(fmt.Sprintf("failed to start local elect mgr: %s", err))
			}
		}()
	})
}

func RunConfigWatcher() {
	_ = DiCall(func(
		featureGate facade.FeatureGate,
	) {
		cm, err := ov.NewConfigMapConfigManager("")
		if err != nil {
			panic(fmt.Sprintf("failed to create config manager: %s", err))
		}
		respCh := cm.Watch(config.RemoteConfigPath, nil)

		go func() {
			for resp := range respCh {
				if resp.Error != nil {
					log.Error("received error when watching config", zap.Error(resp.Error))
					continue
				}

				if err := viper.ReadRemoteConfig(); err != nil {
					log.Error("failed to read remote config", zap.Error(err))
					continue
				}

				// NOTE: only config entries that exist in oneconfig-serverless/config.yaml will be updated.
				// config entries that already exist in serverless-service/config.yaml will NOT be updated.
				if err := config.NormalizeConfig(true); err != nil {
					log.Error("failed to normalize config", zap.Error(err))
					continue
				}

				log.Info("renew serverless config succeed", zap.Any("config", config.Global()))

				if err := featureGate.Refresh(); err != nil {
					log.Error("failed to refresh feature gate", zap.Error(err))
					continue
				}
			}
		}()
	})
}
