package main

import (
	"context"
	"flag"
	"fmt"
	"path/filepath"
	"time"

	"github.com/pingcap/log"
	"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/globalapi"
	"github.com/tidbcloud/observability/pkg/globalapi/interceptor"
	"github.com/tidbcloud/observability/pkg/globalservice"
	"github.com/tidbcloud/observability/pkg/resourcemanager"
	"github.com/tidbcloud/observability/pkg/resourceworker/worker"
	"github.com/tidbcloud/tiinf/sdk/auditlog"
	"go.uber.org/zap"
	"k8s.io/client-go/kubernetes"
	_ "k8s.io/client-go/plugin/pkg/client/auth"
	"k8s.io/client-go/rest"
	"k8s.io/client-go/tools/clientcmd"
	"k8s.io/client-go/util/homedir"
	ctrl "sigs.k8s.io/controller-runtime"
	k8sZap "sigs.k8s.io/controller-runtime/pkg/log/zap"
	ctrlmetrics "sigs.k8s.io/controller-runtime/pkg/metrics"
)

// @title Observability GlobalServer API
// @version 1.0
// @description Observability GlobalServer API

// @contact.name zhangpeijin
// @contact.email zhangpeijin@pingcap.com

// @license.name Apache 2.0
// @license.url http://www.apache.org/licenses/LICENSE-2.0.html

// @host localhost:8080
// @BasePath /api/v1/
// @query.collection.format multi
func main() {
	ctx := utils.SetupSignalHandler()

	flag.IntVar(&port, "port", constants.DefaultObservabilityGlobalServerPort, "listening port")
	flag.IntVar(&metricsPort, "metrics-port", constants.DefaultObservabilityGlobalServerMetricsPort, "listening metrics port")
	flag.StringVar(&probeAddr, "health-probe-bind-address", fmt.Sprintf(":%d", constants.DefaultObservabilityGlobalServerHealthPort), "The address the probe endpoint binds to.")
	flag.StringVar(&vendor, "vendor", string(constants.AWS), "vendor")
	flag.StringVar(&region, "region", "us-west-2", "region")
	flag.StringVar(&env, "env", constants.EnvDev, "environment")
	// specify the kube config absolute path, must be configured for out-of-cluster mode to start.
	flag.StringVar(&kubeConfigPath, "kube-config-path", filepath.Join(homedir.HomeDir(), ".kube", "config"),
		"(optional) specify the kube config absolute path, must be configured for out-of-cluster mode to start")
	// log level. options value: trace, debug, info, warn, error, fatal, panic.
	flag.StringVar(&logLevel, "log-level", "info", "log level")
	flag.DurationVar(&requeueInterval, "requeue-interval", 5*time.Minute, "requeue interval of subscription")
	flag.DurationVar(&regionCacheSyncInterval, "region-cache-sync-interval", 5*time.Minute, "requeue interval of subscription")
	flag.BoolVar(&enableSubscriptionController, "enable-subscription-controller", false, "enable subscription controller")
	flag.BoolVar(&allowPartialSync, "allow-partial-sync", true, "allow partial sync")
	flag.BoolVar(&globalapi.EnableAuditLog, "enable-auditlog", true, "enable audit log")

	opts := k8sZap.Options{
		Development: true,
	}
	opts.BindFlags(flag.CommandLine)
	flag.Parse()
	// init log
	utils.InitLog(logLevel)
	metrics.SetupMetrics(ctrlmetrics.Registry)

	if globalapi.EnableAuditLog {
		if err := auditlog.InitAuditLogSDK(context.TODO(), 3*time.Second); err != nil {
			log.Fatal("failed to init auditlog sdk", zap.Error(err))
		}
	}

	kubeConfig, err := ctrl.GetConfig()
	if err != nil {
		// creates the out-of-cluster config
		kubeConfig, err = clientcmd.BuildConfigFromFlags("", kubeConfigPath)
		if err != nil {
			log.Panic("failed to init kubeconfig. please check if parameter `kube-config-path is correct",
				zap.Error(err),
				zap.String("Kube_Config_Path", kubeConfigPath))
		}
	}
	// creates manager resource
	cli := buildKubeClient(kubeConfig)
	if err := globalservice.NewManager(ctx, cli); err != nil {
		log.Panic("new manager resource failed", zap.Error(err))
	}

	serviceInfo := structs.ServiceInfo{
		ServerName: constants.ObservabilityGlobalServer,
		Port:       port,
		// disable metrics listener in prometheusBoot, we will handle it in controller-runtime.
		MetricsPort: 0,
		Vendor:      vendor,
		Region:      region,
		Env:         env,
		LogLevel:    logLevel,
		EnableHttps: false,
	}

	wOpts := &worker.Options{
		ZapOpts:                      &opts,
		MetricsAddr:                  fmt.Sprintf(":%d", metricsPort),
		HealthProbeAddr:              probeAddr,
		EnableSubscriptionController: enableSubscriptionController,
		RequeueInterval:              requeueInterval,
		RegionCacheSyncInterval:      regionCacheSyncInterval,
		AllowPartialSync:             allowPartialSync,
	}

	worker.InitGlobalManager(ctx, wOpts)
	//Start Manager must before GetManager
	go worker.StartWorker(ctx)
	// init auth gc work for auth token cache expired
	go interceptor.GCTokenCache(ctx)
	resourcemanager.InitReaderWriter(kubeConfig, worker.GetManager(), nil, false /*regional*/)
	globalapi.StartService(ctx, serviceInfo)
}

func buildKubeClient(kubeConfig *rest.Config) *kubernetes.Clientset {
	// creates kube client
	kubeClient, err := kubernetes.NewForConfig(kubeConfig)
	if err != nil {
		log.Panic("create kube client failed", zap.Error(err))
	}
	log.Info("build kube client successfully")
	return kubeClient
}
