package manager

import (
	"fmt"
	"time"

	"github.com/pingcap/errors"
	cloudv1 "github.com/tidbcloud/infra-api/api/v1"
	"github.com/tidbcloud/serverless-service/internal/config"
	corev1 "k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/runtime"
	utilruntime "k8s.io/apimachinery/pkg/util/runtime"
	"k8s.io/client-go/tools/clientcmd"
	ctrl "sigs.k8s.io/controller-runtime"
	"sigs.k8s.io/controller-runtime/pkg/cache"
	"sigs.k8s.io/controller-runtime/pkg/manager"
	metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server"
)

var (
	leaseDuration = 30 * time.Second
	renewDeadline = 20 * time.Second
)

const (
	leaderElectionID        = "election-serverless-service"
	leaderElectionNamespace = "default"
)

// ElectionInfraManager use leader election to ensure only one instance is observing changes at the same time.
type ElectionInfraManager struct {
	manager.Manager
}

// NOTE: this manager do not share the same informer cache with infraFacade.
// You better not to use infraFacade to fetch the latest resource.

func NewElectInfraManager() *ElectionInfraManager {
	mgr, err := newElectManager(config.Global().InfraAPIConfig)
	if err != nil {
		panic(fmt.Sprintf("initialize the elect infra manager failed: %s", err.Error()))
	}

	return &ElectionInfraManager{
		mgr,
	}
}

func newElectManager(cfg config.InfraAPIConfig) (manager.Manager, error) {
	scheme := runtime.NewScheme()
	utilruntime.Must(cloudv1.AddToScheme(scheme))
	utilruntime.Must(corev1.AddToScheme(scheme))

	if cfg.KubeConfigPath == "" {
		return nil, errors.New("infra API server kubeconfig path is empty")
	}
	restCfg, err := clientcmd.BuildConfigFromFlags("", cfg.KubeConfigPath)
	if err != nil {
		return nil, errors.Annotate(err, "retrieve infra API server kubeconfig failed")
	}
	// set timeout for infra api client, ref to https://github.com/kubernetes/client-go/blob/master/rest/config.go
	restCfg.Timeout = InfraAPIClientDefaultTimeout

	leaderElection := !config.IsLocalStage()

	mgr, err := ctrl.NewManager(
		restCfg, ctrl.Options{
			Scheme:                  scheme,
			Metrics:                 metricsserver.Options{BindAddress: "0"}, // disable
			LeaderElection:          leaderElection,
			LeaderElectionID:        leaderElectionID,
			LeaderElectionNamespace: leaderElectionNamespace,
			LeaseDuration:           &leaseDuration,
			RenewDeadline:           &renewDeadline,
			Cache: cache.Options{
				SyncPeriod: NoSyncPeriod,
			},
		},
	)
	if err != nil {
		return nil, errors.Annotate(err, "create controller-runtime manager failed")
	}

	return mgr, nil
}
