package server

import (
	"context"
	"time"

	"github.com/tidbcloud/account/idl/pbgen/proto/account"
	appContainer "github.com/tidbcloud/dataflow-service/app/container"
	"github.com/tidbcloud/dataflow-service/app/event_bus"
	"github.com/tidbcloud/dataflow-service/internal/config"
	internalContainer "github.com/tidbcloud/dataflow-service/internal/container"
	centralDB "github.com/tidbcloud/dataflow-service/internal/db"
	"github.com/tidbcloud/dataflow-service/internal/event-bus/consumer"
	"github.com/tidbcloud/dataflow-service/pkg/grpc/grpcli"
	"github.com/tidbcloud/dataflow-service/pkg/log"
	"github.com/tidbcloud/pkgv2/bizerr"
	"github.com/tidbcloud/tiinf/monitor/titracing"
	"github.com/tidbcloud/tiinf/sdk/auditlog"
	"github.com/tidbcloud/tiinf/sdk/auth"
	"github.com/uptrace/opentelemetry-go-extra/otelgorm"
	"go.uber.org/zap"
	"gorm.io/gorm"
)

const (
	dataflow = "dataflow"
)

type Server struct {
	option ServerOption
}

type ServerOption struct {
	EnableEventBusConsumer bool
}

func NewServer(
	ctx context.Context, serverOption ServerOption,
) (*Server, error) {
	s := &Server{
		option: serverOption,
	}
	if err := s.setupLogger(); err != nil {
		return nil, err
	}
	if err := s.setupJaeger(ctx); err != nil {
		return nil, err
	}
	if err := s.setupAuditLogSDK(ctx); err != nil {
		return nil, err
	}
	setupInfraAPIConnection()
	setupAuthConnection(ctx)
	db, err := s.setupMySQLGorm(ctx)
	if err != nil {
		return nil, err
	}
	if err := s.setupOldArchModel(ctx, db); err != nil {
		return nil, err
	}
	if err := s.setupNewArchModel(ctx, db); err != nil {
		return nil, err
	}
	if serverOption.EnableEventBusConsumer {
		consumer.RegisterConsumer()
		event_bus.RegisterHandlers()
	}
	return s, nil
}

func (s *Server) setupLogger() error {
	return log.InitLogger(&log.InitLoggerOption{
		PrintLogLevel: config.Global().ServerConfig.LogLevel,
	})
}

func (s *Server) setupJaeger(ctx context.Context) error {
	if _, err := titracing.InitJaeger(dataflow); err != nil {
		return bizerr.WrapError(err, "initialize jaeger failed")
	}
	return nil
}

func (s *Server) setupAuditLogSDK(ctx context.Context) error {
	if config.Global().DebugConfig.Enable {
		return nil
	}
	err := auditlog.InitAuditLogSDK(context.TODO(), time.Second)
	if err != nil {
		return bizerr.WrapError(err, "failed to init console audit log sdk")
	}
	return nil
}

func (s *Server) setupOldArchModel(ctx context.Context, db *gorm.DB) error {
	return internalContainer.SetUpContainers(ctx, db)
}

func (s *Server) setupNewArchModel(ctx context.Context, db *gorm.DB) error {
	return appContainer.SetUpContainers(ctx, db)
}

func (s *Server) setupMySQLGorm(ctx context.Context) (*gorm.DB, error) {
	// Initialize global database.
	mysqlConfig := config.Global().MysqlConfig
	log.GetLogger(ctx).Info("Initialize global database connection",
		zap.String("user", mysqlConfig.User), zap.String("db", mysqlConfig.Database),
		zap.String("host", mysqlConfig.Host), zap.Uint16("port", mysqlConfig.Port))
	// always not do migrate
	db, err := centralDB.InitializeWithoutMigrate(config.Global().MysqlConfig)
	if err != nil {
		return nil, bizerr.WrapError(err, "initialize global database connection failed")
	}
	if err := db.Use(otelgorm.NewPlugin()); err != nil {
		return nil, bizerr.WrapError(err, "add otel gorm plugin failed")
	}
	return db, nil
}

func setupInfraAPIConnection() {
	grpcli.MustNewTiDBClusterInfraClient()
}

func setupAuthConnection(ctx context.Context) {
	addr := config.Global().GrpcConfig.AccountProviderAddr
	conn := grpcli.MustBuildGrpcClientConnection(ctx, addr)
	auth.InitAccountClientByClient(account.NewAccountAPIServiceClient(conn), 0)
}
