package controller

import (
	"fmt"

	"github.com/prometheus/client_golang/prometheus"
	"github.com/tidbcloud/serverless-service/internal/config"
	"github.com/tidbcloud/serverless-service/internal/domain/facade"
	"github.com/tidbcloud/serverless-service/internal/domain/service"
	"github.com/tidbcloud/serverless-service/internal/grpc/grpcli"
	aws2 "github.com/tidbcloud/serverless-service/internal/infra/aws"
	"github.com/tidbcloud/serverless-service/internal/infra/eventbus"
	"github.com/tidbcloud/serverless-service/internal/infra/eventbus/subscriber"
	"github.com/tidbcloud/serverless-service/internal/infra/facade_imp"
	"github.com/tidbcloud/serverless-service/internal/infra/manager"
	"github.com/tidbcloud/serverless-service/internal/infra/manager/branch"
	"github.com/tidbcloud/serverless-service/internal/infra/manager/canaryv2"
	"github.com/tidbcloud/serverless-service/internal/infra/repo_imp"
	stashing "github.com/tidbcloud/serverless-service/internal/infra/stashing_area"
	"github.com/tidbcloud/serverless-service/internal/metric"
	"github.com/tidbcloud/serverless-service/internal/metric/dm_controller"
	"github.com/tidbcloud/serverless-service/internal/metric/import_collector"
	"github.com/tidbcloud/serverless-service/internal/service/auditlog_svc"
	"github.com/tidbcloud/serverless-service/internal/service/billing_svc"
	"github.com/tidbcloud/serverless-service/internal/service/br_svc"
	"github.com/tidbcloud/serverless-service/internal/service/branch_svc"
	"github.com/tidbcloud/serverless-service/internal/service/cdc_svc"
	"github.com/tidbcloud/serverless-service/internal/service/cluster_svc"
	"github.com/tidbcloud/serverless-service/internal/service/dm_svc"
	"github.com/tidbcloud/serverless-service/internal/service/dm_svc/console"
	dm_v1beta1 "github.com/tidbcloud/serverless-service/internal/service/dm_svc/v1beta1"
	"github.com/tidbcloud/serverless-service/internal/service/event_svc"
	"github.com/tidbcloud/serverless-service/internal/service/export_svc"
	"github.com/tidbcloud/serverless-service/internal/service/import_svc"
	import_console "github.com/tidbcloud/serverless-service/internal/service/import_svc/console"
	import_v1beta "github.com/tidbcloud/serverless-service/internal/service/import_svc/v1beta"
	import_v1beta1 "github.com/tidbcloud/serverless-service/internal/service/import_svc/v1beta1"
	"github.com/tidbcloud/serverless-service/internal/service/ops_svc"
	"github.com/tidbcloud/serverless-service/internal/service/privatelink_svc"
	"github.com/tidbcloud/serverless-service/internal/storage"
	appworkflow "github.com/tidbcloud/serverless-service/internal/workflow"
	appworkflowdag "github.com/tidbcloud/serverless-service/internal/workflow/dag"
	appworkflowimpl "github.com/tidbcloud/serverless-service/internal/workflow/impl"
	"go.uber.org/dig"
)

var globalDI *dig.Container

func init() {
	globalDI = dig.New()
}

//nolint:unparam
func mustProvide(constructor any, options ...dig.ProvideOption) {
	if err := globalDI.Provide(constructor, options...); err != nil {
		panic(err)
	}
}

func DiCall(fn any, options ...dig.InvokeOption) error {
	return globalDI.Invoke(fn, options...)
}

func InitDependencies() {
	mustProvide(grpcli.NewConfigCenterClient)
	mustProvide(billing_svc.NewBillingService)

	mustProvide(cluster_svc.NewClusterRpcService)
	mustProvide(cluster_svc.NewClusterConsoleService)

	mustProvide(service.NewUserRoleService)
	mustProvide(service.NewBranchService)
	mustProvide(service.NewBillingService)
	mustProvide(service.NewExportService)
	mustProvide(branch_svc.NewBranchConsoleService)
	mustProvide(branch_svc.NewBranchRpcService)
	mustProvide(branch_svc.NewBranchUserRpcService)
	mustProvide(branch_svc.NewBranchV1betaService)
	mustProvide(branch_svc.NewBranchV1betaUserService)
	mustProvide(branch_svc.NewBranchV1beta1Service)
	mustProvide(export_svc.NewExportConsoleService)
	mustProvide(export_svc.NewExportV1beta1Service)
	mustProvide(repo_imp.NewBranchRepoImp)
	mustProvide(repo_imp.NewLockRepoImpl)
	mustProvide(repo_imp.NewExportRepoImp)
	mustProvide(aws2.NewS3ClientProvider)
	mustProvide(aws2.NewPresignerProvider)
	mustProvide(stashing.NewStashingAreaProvider)
	mustProvide(repo_imp.NewUploadRepoImp)

	mustProvide(facade_imp.NewAccountFacade)
	mustProvide(facade_imp.NewBillingFacade)
	mustProvide(facade_imp.NewInfraFacade)
	mustProvide(facade_imp.NewO11yFacade)
	mustProvide(facade_imp.NewTimeFacadeImp)
	mustProvide(facade_imp.NewFeatureGateImp)
	mustProvide(facade_imp.NewClusterProxyProviderFacade)
	mustProvide(facade_imp.NewEncryptorFacade)
	mustProvide(facade_imp.NewMgmtSvcFacade)
	mustProvide(eventbus.NewEventBusService)

	mustProvide(repo_imp.NewClusterRepo)
	mustProvide(repo_imp.NewClusterTidbPoolRepo)
	mustProvide(repo_imp.NewDowntimeRepo)
	mustProvide(repo_imp.NewCanaryRepo)

	mustProvide(repo_imp.NewImportRepo)
	mustProvide(service.NewImportService)
	mustProvide(import_svc.NewImportSyncerService)
	mustProvide(import_svc.NewImportHelperService)
	mustProvide(import_v1beta.NewImportService)
	mustProvide(import_v1beta1.NewImportService)
	mustProvide(import_console.NewImportService)

	mustProvide(storage.NewGormDB)
	mustProvide(storage.NewDBProvier)
	mustProvide(manager.NewInfraManager)

	// br
	mustProvide(repo_imp.NewBackupScheduleRepoImp)
	mustProvide(repo_imp.NewBackupScheduleHistoryRepoImp)
	mustProvide(repo_imp.NewBackupJobRepoImp)
	mustProvide(repo_imp.NewUserBackupRepoImp)
	mustProvide(repo_imp.NewBackupTombstoneRepoImp)
	mustProvide(repo_imp.NewPhysicalBackupSlotRepoImp)
	mustProvide(repo_imp.NewRestoreJobRepoImp)
	mustProvide(repo_imp.NewBackupScheduleCheckJobRepoImp)
	mustProvide(service.NewBackupRestoreService)
	mustProvide(br_svc.NewBackupRestoreConsoleService)
	mustProvide(br_svc.NewBackupRestoreRpcService)

	// cdc
	mustProvide(cdc_svc.NewCDCV1beta1Service)
	mustProvide(appworkflowimpl.NewWorkflow)
	// NewRuntime triggers fastflow executor on construction, but the
	// executor itself is gated by config.IsWorkflowMode() inside the
	// Workflow.StartExecutor() implementation, so providing it
	// unconditionally is safe — http/grpc/worker/br-worker pods just
	// don't request *Runtime via DiCall and therefore never instantiate
	// the executor. Only the workflow pod (via NewWorkflowRunner) asks
	// for it, so the executor only runs in the workflow pod.
	mustProvide(appworkflow.NewRuntime)
	mustProvide(repo_imp.NewTiCDCChangefeedResourceRepoImp)
	mustProvide(repo_imp.NewTiCDCClusterRepoImp)
	mustProvide(repo_imp.NewKernelTiCDCChangefeedRepoImp)
	mustProvide(service.NewCDCNewArchWorkflowHook)
	mustProvide(appworkflowdag.NewRegistrar)
	mustProvide(service.NewCDCService)
	mustProvide(repo_imp.NewChangefeedRepoImp)
	mustProvide(repo_imp.NewCDCClusterRepoImp)
	mustProvide(cdc_svc.NewChangefeedConsoleService)
	// auditlog
	mustProvide(auditlog_svc.NewDatabaseAuditLogConsoleService)
	mustProvide(auditlog_svc.NewDatabaseAuditLogV1beta1Service)
	mustProvide(auditlog_svc.NewAuditLogV1beta1Service)
	mustProvide(service.NewAuditLogService)
	mustProvide(repo_imp.NewAuditLogFilterRuleRepoImp)
	mustProvide(repo_imp.NewAuditLogConfigRepoImp)

	mustProvide(repo_imp.NewDMTaskRepo)
	mustProvide(service.NewDMService)
	mustProvide(console.NewDMService)
	// private link connection
	mustProvide(service.NewPrivateLinkConnectionService)
	mustProvide(privatelink_svc.NewPrivateLinkConnectionV1beta1Service)
	mustProvide(repo_imp.NewPrivateLinkConnectionRepoImp)
	mustProvide(repo_imp.NewPrivateLinkConnectionLinkRepoImp)
	mustProvide(repo_imp.NewPLCAttachDomainRepoImp)
	mustProvide(repo_imp.NewPLCDomainRecordRepoImp)
	mustProvide(privatelink_svc.NewPrivateLinkConnectionConsoleService)

	mustProvide(dm_v1beta1.NewDMService)

	if config.IsWorkerMode() {
		mustProvide(billing_svc.NewMeteringService)
		mustProvide(billing_svc.NewInstanceCapacityMeterService)
		mustProvide(repo_imp.NewRegionMeteringRepo)
		mustProvide(repo_imp.NewInstanceCapacityCheckpointRepo)
		mustProvide(NewExportMeteringWorker)
		mustProvide(NewInstanceCapacityMeterWorker)
		mustProvide(NewTriggerIndexAdvisorWorker)

		mustProvide(cluster_svc.NewSyncTidbPoolService)
		mustProvide(NewClusterTidbPoolSyncer)
		// mustProvide(cluster_svc.NewCleanupTidbPoolService)
		// mustProvide(NewClusterTidbPoolCleaner)
		mustProvide(NewImportSyncer)
		mustProvide(export_svc.NewExportSyncerService)
		mustProvide(NewExportSyncer)

		mustProvide(event_svc.NewSpendLimitService)
		mustProvide(subscriber.NewSpendLimitEventHandler)
		mustProvide(event_svc.NewClusterEventService)
		mustProvide(subscriber.NewClusterEventHandler)

		mustProvide(event_svc.NewBranchSyncEventService)
		mustProvide(subscriber.NewBranchClusterEventHandler)

		mustProvide(manager.NewElectInfraManager)
		mustProvide(manager.NewLocalElectionManager)
		mustProvide(branch.NewBranchReconciler)
		mustProvide(NewBranchSyncWorker)
		mustProvide(canaryv2.NewReconciler)

		mustProvide(NewBranchMetricCollector)
		mustProvide(service.NewBranchSizeLimiterService)
		mustProvide(NewBranchStorageLimiter)

		// backup
		mustProvide(subscriber.NewBackupClusterEventHandler)
		mustProvide(event_svc.NewBackupEventService)
		mustProvide(event_svc.NewVirtualizedBackupEventService)

		registerCollector()

		mustProvide(cluster_svc.NewDowntimeService)
		mustProvide(NewDowntimeWorker)

		mustProvide(cluster_svc.NewServicePlanEventService)
		mustProvide(NewServicePlanEventJob)

		mustProvide(dm_svc.NewDMTaskWorker)
		mustProvide(NewDMWorker)
		mustProvide(dm_svc.NewDMPrecheckWorker)
		mustProvide(dm_svc.NewDMEndpointWorker)
		mustProvide(NewDMPrecheckWorker)
		mustProvide(NewDMEndpointWorker)
	}

	if config.IsBRWorkerMode() {
		mustProvide(service.NewBackupWorker)
		mustProvide(NewBackupCleanWorker)
		mustProvide(NewRestoreSyncWorker)
		mustProvide(NewRestoreWorker)
		mustProvide(NewBackupScheduleCheckWorker)
		mustProvide(service.NewVirtualizedBackupSlotWorker)
		mustProvide(NewVirtualizedBackupSlotWorker)
		mustProvide(service.NewVirtualizedBackupCleanupWorker)
		mustProvide(NewVirtualizedBackupCleanupWorker)

		// cdc
		mustProvide(service.NewCDCWorker)
		mustProvide(NewCDCWorker)

		// private link connection
		mustProvide(service.NewPrivateLinkConnectionWorker)
		mustProvide(NewPrivateLinkConnectionWorker)
	}

	// ops
	mustProvide(ops_svc.NewClusterRpcService)
	mustProvide(ops_svc.NewK8sRpcService)
	mustProvide(ops_svc.NewCdcRpcService)

	checkInjection()
}

func registerCollector() {
	mustProvide(metric.NewBranchCreateTimeCollector)
	mustProvide(metric.NewExportMetricCollector)
	mustProvide(metric.NewBackupRestoreMetricCollector)
	mustProvide(metric.NewChangefeedMetricCollector)
	mustProvide(import_collector.NewStatusCollector)
	mustProvide(import_collector.NewImportCollector)
	mustProvide(dm_controller.NewMigrationCollector)
	err := DiCall(func(collector *metric.BranchCreateTimeCollector) {
		prometheus.MustRegister(collector)
	})
	if err != nil {
		panic(fmt.Sprintf("register branch create time collector failed: %s", err))
	}

	err = DiCall(func(collector *import_collector.ImportCollector) {
		prometheus.MustRegister(collector)
	})
	if err != nil {
		panic(fmt.Sprintf("register import metric collector failed: %s", err))
	}

	err = DiCall(func(collector *import_collector.StatusCollector) {
		prometheus.MustRegister(collector)
	})
	if err != nil {
		panic(fmt.Sprintf("register import status metric collector failed: %s", err))
	}

	err = DiCall(func(collector *metric.ExportMetricCollector) {
		prometheus.MustRegister(collector)
	})
	if err != nil {
		panic(fmt.Sprintf("register export metric collector failed: %s", err))
	}
	err = DiCall(func(collector *metric.BackupRestoreMetricCollector) {
		prometheus.MustRegister(collector)
	})
	if err != nil {
		panic(fmt.Sprintf("register br metric collector failed: %s", err))
	}
	err = DiCall(func(collector *dm_controller.MigrationCollector) {
		prometheus.MustRegister(collector)
	})
	if err != nil {
		panic(fmt.Sprintf("register migration metric collector failed: %s", err))
	}
	err = DiCall(func(collector *metric.ChangefeedMetricCollector) {
		prometheus.MustRegister(collector)
	})
	if err != nil {
		panic(fmt.Sprintf("register changefeed metric collector failed: %s", err))
	}
}

func checkInjection() {
	// check facade
	_ = DiCall(func(_ facade.InfraFacade) {})

	// check worker
	_ = DiCall(NewDowntimeWorker)

	// check ops
	_ = DiCall(ops_svc.NewClusterRpcService)
	_ = DiCall(ops_svc.NewK8sRpcService)
	_ = DiCall(ops_svc.NewCdcRpcService)
}
