// Copyright 2018 PingCAP, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// See the License for the specific language governing permissions and
// limitations under the License.

package main

import (
	"context"
	"flag"
	"fmt"
	"net/http"
	_ "net/http/pprof"
	"os"
	"time"

	"github.com/emicklei/go-restful"
	"github.com/emicklei/go-restful-swagger12"
	"github.com/golang/glog"
	"github.com/pingcap/tidb-enterprise/installer/tep-operator/api"
	"github.com/pingcap/tidb-enterprise/installer/tep-operator/pkg/client/clientset/versioned"
	informers "github.com/pingcap/tidb-enterprise/installer/tep-operator/pkg/client/informers/externalversions"
	"github.com/pingcap/tidb-enterprise/installer/tep-operator/pkg/controller/tepcluster"
	"github.com/pingcap/tidb-enterprise/installer/tep-operator/pkg/utils"
	"github.com/pingcap/tidb-enterprise/installer/tep-operator/provisioner/ansible"
	"github.com/pingcap/tidb-enterprise/installer/tep-operator/version"

	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/util/uuid"
	"k8s.io/apimachinery/pkg/util/wait"
	"k8s.io/client-go/kubernetes"
	"k8s.io/client-go/tools/leaderelection"
	"k8s.io/client-go/tools/leaderelection/resourcelock"
	"k8s.io/client-go/tools/record"
)

var (
	printVersion           bool
	kubecfg                string
	defaultSwaggerFilePath = "/usr/local/apidocs"
	leaseDuration          = 15 * time.Second
	renewDuration          = 5 * time.Second
	retryPeriod            = 3 * time.Second
	resyncDuration         = 30 * time.Second
	waitDuration           = 5 * time.Second
)

func init() {
	flag.BoolVar(&printVersion, "V", false, "Show version and quit")
	flag.BoolVar(&printVersion, "version", false, "Show version and quit")
	flag.StringVar(&kubecfg, "kubecfg", "", "Path to kubeconfig file, omit this if run in cluster")
	flag.StringVar(&ansible.WorkDir, "workDir", "", "The work path of ansible playbook")

	flag.Parse()
}

func main() {
	if printVersion {
		version.PrintVersionInfo()
		os.Exit(0)
	}
	version.LogVersionInfo()

	defer glog.Flush()

	id, err := os.Hostname()
	if err != nil {
		glog.Fatalf("failed to get hostname: %v", err)
	}
	// add a uniquifier so that two processes on the same host don't accidentally both become active
	id = fmt.Sprintf("%s_%s", id, uuid.NewUUID())

	ns := os.Getenv("NAMESPACE")
	if ns == "" {
		glog.Fatal("NAMESPACE environment variable not set")
	}

	swaggerFilePath := os.Getenv("SWAGGER_FILE_PATH")
	if swaggerFilePath == "" {
		swaggerFilePath = defaultSwaggerFilePath
	}
	cfg, err := utils.GetClusterConfig(kubecfg)
	if err != nil {
		glog.Fatalf("failed to get cluster config: %v", err)
	}
	cli, err := versioned.NewForConfig(cfg)
	if err != nil {
		glog.Fatalf("failed to create Clientset: %v", err)
	}
	kubeCli, err := kubernetes.NewForConfig(cfg)
	if err != nil {
		glog.Fatalf("failed to get kubernetes Clientset: %v", err)
	}

	var informerFactory informers.SharedInformerFactory
	informerFactory = informers.NewSharedInformerFactory(cli, resyncDuration)

	rl := resourcelock.EndpointsLock{
		EndpointsMeta: metav1.ObjectMeta{
			Namespace: ns,
			Name:      "tep-controller-manager",
		},
		Client: kubeCli.CoreV1(),
		LockConfig: resourcelock.ResourceLockConfig{
			Identity:      id,
			EventRecorder: &record.FakeRecorder{},
		},
	}

	tcController := tepcluster.NewController(kubeCli, cli, informerFactory)
	stop := make(chan struct{})
	defer close(stop)
	go informerFactory.Start(stop)

	run := func(ctx context.Context) {
		tcController.Run(ctx.Done())
	}

	// leader election for multiple tep-controller-manager
	go wait.Forever(func() {
		leaderelection.RunOrDie(context.TODO(), leaderelection.LeaderElectionConfig{
			Lock:          &rl,
			LeaseDuration: leaseDuration,
			RenewDeadline: renewDuration,
			RetryPeriod:   retryPeriod,
			Callbacks: leaderelection.LeaderCallbacks{
				OnStartedLeading: run,
				OnStoppedLeading: func() {
					glog.Fatalf("leaderelection lost")
				},
			},
		})
	}, waitDuration)

	mgrAPI := api.NewManagerAPI()
	ws := mgrAPI.NewService()
	restful.Add(ws)

	config := swagger.Config{
		WebServices:     restful.DefaultContainer.RegisteredWebServices(),
		WebServicesUrl:  "http://localhost:8099",
		ApiPath:         "/apidocs.json",
		SwaggerPath:     "/apidocs/",
		SwaggerFilePath: swaggerFilePath,
	}
	swagger.RegisterSwaggerService(config, restful.DefaultContainer)
	glog.Infof("tep operator listening on 0.0.0.0:8099")
	glog.Fatal(http.ListenAndServe(":8099", nil))
}
