package main

import (
	"flag"
	"strings"
	"sync"

	"github.com/pingcap/log"
	"go.uber.org/zap"
	"go.uber.org/zap/zapcore"
	"k8s.io/client-go/rest"
	_ "k8s.io/client-go/tools/clientcmd"
	_ "k8s.io/client-go/util/homedir"
	"k8s.io/klog/v2"
	ctrl "sigs.k8s.io/controller-runtime"
	sigszap "sigs.k8s.io/controller-runtime/pkg/log/zap"

	"github.com/tidbcloud/infra-provider/regional-server/pkg/common/utils"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/component"
	rsconfig "github.com/tidbcloud/infra-provider/regional-server/pkg/config"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/scheme"
)

var (
	clientMode              bool
	logLevel                string
	port                    int
	grpcPort                int
	rootConfigPaths         repeatedStringFlag
	addonTmplConfigPath     string
	infraTmplConfigPath     string
	dedicatedTmplConfigPath string
	dpTmplConfigPath        string
	probeAddr               string
	metricsAddr             string
	enableLeaderElection    bool
	enabledComponents       string
	enabledControllers      string
)

type repeatedStringFlag []string

func (f *repeatedStringFlag) String() string {
	return strings.Join(*f, ",")
}

func (f *repeatedStringFlag) Set(value string) error {
	*f = append(*f, value)
	return nil
}

func init_logger() {
	utils.SetDefaultSlog(logLevel)
	// init logger, use pingcap/log now, this logger will be used in apiserver
	logger, props, err := log.InitLogger(&log.Config{Level: logLevel})
	if err != nil {
		panic(err)
	}
	log.ReplaceGlobals(logger, props)

	// init controller-runtime logger
	level, err := zapcore.ParseLevel(logLevel)
	if err != nil {
		level = zapcore.InfoLevel
	}
	opts := sigszap.Options{
		Development: false,
		Level:       level,
		TimeEncoder: zapcore.RFC3339TimeEncoder,
	}
	zlog := sigszap.New(sigszap.UseFlagOptions(&opts))
	ctrl.SetLogger(zlog)
	// This helps to standardize the log layout of client-go.
	klog.SetLogger(zlog)
}

// +kubebuilder:rbac:groups="",resources="*",verbs="*"
// +kubebuilder:rbac:groups=dedicated.regionalserver.tidbcloud.com,resources="*",verbs="*"
// +kubebuilder:rbac:groups=iam.aws.upbound.io,resources="*",verbs="*"
// +kubebuilder:rbac:groups=aws.upbound.io,resources="*",verbs="*"
// +kubebuilder:rbac:groups=managedidentity.azure.upbound.io,resources=userassignedidentities,verbs=get;list;watch;create;update;patch;delete

func main() {
	flag.IntVar(&port, "port", 2333, "api service listening port")
	flag.IntVar(&grpcPort, "grpc-port", 2335, "grpc service listening port")
	flag.BoolVar(&clientMode, "client-mode", true, "client mode (true: in-cluster, false: out-of-cluster)")
	flag.StringVar(&metricsAddr, "metrics-bind-address", ":8082", "The address the metric endpoint binds to.")
	flag.StringVar(&probeAddr, "health-probe-bind-address", ":8081", "The address the probe endpoint binds to.")
	flag.StringVar(&logLevel, "log-level", "info", "log level. default: info")
	flag.Var(&rootConfigPaths, "root-config", "The path to the root config. May be specified multiple times; later files override earlier files. Nested maps are merged recursively; scalars and lists are replaced entirely by the later file.")
	flag.BoolVar(&enableLeaderElection, "leader-elect", false, "Enable leader election for controller manager.")
	flag.StringVar(&addonTmplConfigPath, "addon-template-config", "", "The path to the k8s addons templates.")
	flag.StringVar(&infraTmplConfigPath, "infra-template-config", "", "The path to the k8s infra components templates.")
	flag.StringVar(&dedicatedTmplConfigPath, "dedicated-template-config", "", "The path to the dedicated infra components templates.")
	flag.StringVar(&dpTmplConfigPath, "dp-template-config", "", "The path to the data platform infra components templates.")
	flag.StringVar(&enabledComponents, "enabled-components", "controller,server", "enabled sub-component, split by comma.")
	flag.StringVar(&enabledControllers, "enabled-controllers", "", "enabled controllers, split by comma. if empty, all controllers are enabled.")
	flag.Parse()

	ctx := utils.SetupSignalHandler()
	init_logger()
	//nolint
	defer log.Sync()
	log.Info("Version", zap.Any("version", utils.GetVersionInfo()))

	// build kube config
	var kubeConfig *rest.Config
	var err error
	if clientMode {
		// creates the in-cluster config
		kubeConfig, err = rest.InClusterConfig()
	} else {
		// creates the out-of-cluster config
		kubeConfig, err = ctrl.GetConfig()
		// kubeConfig, err = clientcmd.BuildConfigFromFlags("", kubeConfigPath)
	}
	if err != nil {
		log.Error("init kubeconfig failed", zap.Error(err))
	}

	// avoid client-side throttling
	kubeConfig.Burst = 50
	kubeConfig.QPS = 20

	config, err := rsconfig.LoadRootConfigs(rootConfigPaths)
	if err != nil {
		log.Fatal("unable to load root config", zap.Error(err))
	}

	tmplConfig, err := rsconfig.LoadTemplatesConfig(addonTmplConfigPath, infraTmplConfigPath, dedicatedTmplConfigPath, dpTmplConfigPath)
	if err != nil {
		log.Fatal("unable to load template config", zap.Error(err))
	}

	log.Info("effective root config", zap.Strings("rootConfigFiles", rootConfigPaths), zap.Any("config", config))

	enabledSet := make(map[string]bool)
	componentNames := strings.Split(enabledComponents, ",")
	for _, name := range componentNames {
		trimmedName := strings.TrimSpace(name)
		if trimmedName != "" {
			enabledSet[trimmedName] = true
		}
	}

	enabledControllersMap := make(map[string]bool)
	if enabledControllers != "" {
		controllerNames := strings.Split(enabledControllers, ",")
		for _, name := range controllerNames {
			trimmedName := strings.TrimSpace(name)
			if trimmedName != "" {
				enabledControllersMap[trimmedName] = true
			}
		}
	}

	allModules := []component.Component{
		component.NewServer(&component.ServerConfig{
			KubeConfig: kubeConfig,
			Config:     config,
			TmplConfig: tmplConfig,
			Scheme:     scheme.Scheme,
			HttpPort:   port,
			GrpcPort:   grpcPort,
		}),
		component.NewController(&component.ControllerOptions{
			KubeConfig:             kubeConfig,
			Config:                 config,
			TmplConfig:             tmplConfig,
			Scheme:                 scheme.Scheme,
			HealthProbeBindAddress: probeAddr,
			MetricsAddress:         metricsAddr,
			LeaderElection:         enableLeaderElection,
			EnabledControllers:     enabledControllersMap,
		}),
	}

	// start all enabled components
	var wg sync.WaitGroup
	for _, co := range allModules {
		if _, enabled := enabledSet[co.Name()]; enabled {
			log.Info("component is enabled, starting", zap.String("component", co.Name()))
			wg.Add(1)
			go func() {
				defer wg.Done()
				if err = co.Start(ctx); err != nil {
					log.Fatal("start component failed",
						zap.String("component", co.Name()), zap.Error(err))
				}
			}()
		}
	}
	wg.Wait()
	log.Info("dataplane manager stopped")
}
