package main

import (
	"context"
	"flag"

	vmv1beta1 "github.com/VictoriaMetrics/operator/api/operator/v1beta1"
	helmv2 "github.com/fluxcd/helm-controller/api/v2"
	"github.com/go-logr/zapr"
	"github.com/pingcap/log"
	o11yv1 "github.com/tidbcloud/observability/api/regionalserver/resourceworker/v1"
	"github.com/tidbcloud/observability/pkg/common/configs"
	"github.com/tidbcloud/observability/pkg/common/constants"
	"github.com/tidbcloud/observability/pkg/common/metrics"
	"github.com/tidbcloud/observability/pkg/common/structs"
	"github.com/tidbcloud/observability/pkg/common/utils"
	"github.com/tidbcloud/observability/pkg/opsapi"
	"github.com/tidbcloud/observability/pkg/resourcemanager"
	"github.com/tidbcloud/observability/pkg/resourceworker/controllers/clusterupgradeoperation"
	"github.com/tidbcloud/observability/pkg/resourceworker/controllers/vmoperation"
	"go.uber.org/zap"
	"k8s.io/apimachinery/pkg/runtime"
	utilruntime "k8s.io/apimachinery/pkg/util/runtime"
	clientgoscheme "k8s.io/client-go/kubernetes/scheme"
	"k8s.io/client-go/rest"
	ctrl "sigs.k8s.io/controller-runtime"
	controller_metrics "sigs.k8s.io/controller-runtime/pkg/metrics"
	metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server"
)

var (
	Scheme               = runtime.NewScheme()
	clientMode           = flag.Bool("clientMode", true, "client mode (true: in-cluster, false: out-of-cluster)")
	enableLeaderElection = flag.Bool("leaderElect", false, "Enable leader election for controller manager.")
	port                 = flag.Int("port", 8180, "The port to listen on for HTTP requests")
	metricsAddr          = flag.String("metricsBindAddress", ":8181", "The address the metric endpoint binds to.")
	logLevel             = flag.String("logLevel", "info", "log level. default: info")
	accountInfoPath      = flag.String("accountInfo", "./config/accountinfo.yaml", "account info config path. default: ./config/accountinfo.yaml")
	vendor               = flag.String("vendor", string(constants.AWS), "vendor")
	selfMonitorEndpoint  = flag.String("selfMonitorEndpoint", "http://prometheus-operated.self-monitoring:9090", "self monitoring prometheus endpoint")
)

func init() {
	utilruntime.Must(clientgoscheme.AddToScheme(Scheme))
	utilruntime.Must(o11yv1.AddToScheme(Scheme))
	utilruntime.Must(vmv1beta1.AddToScheme(Scheme))
	utilruntime.Must(helmv2.AddToScheme(Scheme))
}

func main() {
	ctx := utils.SetupSignalHandler()
	flag.Parse()

	// init log
	utils.InitLog(*logLevel)
	// controller-runtime uses its own logr; wire it to our zap logger so reconciler logs show up.
	ctrl.SetLogger(zapr.NewLogger(log.L()))
	log.Info("ops server running in vendor", zap.String("Vendor", *vendor))

	// init 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()
	}
	if err != nil {
		log.Error("init kubeconfig failed", zap.Error(err))
	}

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

	// load configs
	accountInfo := configs.LoadAccountInfo(*accountInfoPath)
	logicalRegion := accountInfo.Region
	if accountInfo.LogicalRegionName != "" {
		logicalRegion = accountInfo.LogicalRegionName
	}
	serviceInfo := structs.ServiceInfo{
		ServerName:          constants.ObservabilityOpsServer,
		Port:                *port,
		Vendor:              *vendor,
		Region:              accountInfo.Region,
		LogicalRegionName:   logicalRegion,
		Env:                 accountInfo.Env,
		LogLevel:            *logLevel,
		SelfMonitorEndpoint: *selfMonitorEndpoint,
		AccountInfo:         accountInfo,
	}
	configs.Init(serviceInfo)

	mgr, err := ctrl.NewManager(kubeConfig, ctrl.Options{
		Scheme:                 Scheme,
		HealthProbeBindAddress: "0", // disable health probe in manager, will be enabled by opsapi
		Metrics:                metricsserver.Options{BindAddress: *metricsAddr},
		LeaderElection:         *enableLeaderElection,
		LeaderElectionID:       "4xa2j5zh.observability.pingcap.com",
	})
	if err != nil {
		log.Fatal("unable to start manager", zap.Error(err))
	}

	go startControllers(ctx, mgr, serviceInfo)
	resourcemanager.InitOpsReaderWriter(kubeConfig, mgr)

	opsapi.StartService(ctx, serviceInfo)
}

func startControllers(ctx context.Context, mgr ctrl.Manager, serviceInfo structs.ServiceInfo) {
	if reconciler, err := vmoperation.NewVMOperationReconciler(mgr.GetClient(), serviceInfo, mgr.GetEventRecorderFor("vmoperation")); err != nil {
		log.Fatal("unable to create vmoperation reconciler", zap.Error(err))
	} else {
		if err = reconciler.SetupWithManager(mgr); err != nil {
			log.Fatal("unable to setup vmoperation reconciler", zap.Error(err))
		}
	}

	if reconciler, err := clusterupgradeoperation.NewClusterUpgradeOperationReconciler(
		mgr.GetClient(),
		mgr.GetScheme(),
		serviceInfo,
		mgr.GetEventRecorderFor("clusterupgradeoperation"),
	); err != nil {
		log.Fatal("unable to create clusterupgradeoperation reconciler", zap.Error(err))
	} else {
		if err = reconciler.SetupWithManager(mgr); err != nil {
			log.Fatal("unable to setup clusterupgradeoperation reconciler", zap.Error(err))
		}
	}

	metrics.SetupMetrics(controller_metrics.Registry)

	if err := mgr.Start(ctx); err != nil {
		log.Fatal("unable to start manager", zap.Error(err))
	}
}
