package main

import (
	"flag"
	"fmt"
	"github.com/pingcap/log"
	apis "github.com/pingcap/tep/pkg/apis/apiserver"
	"github.com/pingcap/tep/pkg/apiserver"
	"github.com/pingcap/tep/pkg/metrics/provider"
	"github.com/pingcap/tep/pkg/migrate"
	"github.com/pingcap/tep/pkg/operator"
	store "github.com/pingcap/tep/pkg/registry/apiserver"
	"github.com/pingcap/tep/pkg/utils/es"
	"github.com/robfig/cron"
	"github.com/spf13/cobra"
	"github.com/spf13/pflag"
	"io"
	utilerrors "k8s.io/apimachinery/pkg/util/errors"
	genericapiserver "k8s.io/apiserver/pkg/server"
	genericoptions "k8s.io/apiserver/pkg/server/options"
	utilfeature "k8s.io/apiserver/pkg/util/feature"
	"k8s.io/client-go/rest"
	"k8s.io/client-go/tools/clientcmd"
	"k8s.io/component-base/logs"
	"k8s.io/klog"
	kubeserver "k8s.io/kubernetes/pkg/kubeapiserver/server"
	"net"
	"os"
	"time"
)

func main() {
	logs.InitLogs()
	defer logs.FlushLogs()

	stopCh := genericapiserver.SetupSignalHandler()
	options := NewWardleServerOptions(os.Stdout, os.Stderr)
	cmd := NewCommandStartWardleServer(options, stopCh)
	cmd.Flags().AddGoFlagSet(flag.CommandLine)
	if err := cmd.Execute(); err != nil {
		klog.Fatal(err)
	}
}

type WardleOptions struct {
	SecureServing   *genericoptions.SecureServingOptionsWithLoopback
	Authentication  *genericoptions.DelegatingAuthenticationOptions
	Authorization   *genericoptions.DelegatingAuthorizationOptions
	Features        *genericoptions.FeatureOptions
	InSecureServing *genericoptions.DeprecatedInsecureServingOptionsWithLoopback
}

// NewWardleServerOptions constructs a new set of default options for wardle-server.
func NewWardleOptions() *WardleOptions {
	o := &WardleOptions{
		SecureServing:  genericoptions.NewSecureServingOptions().WithLoopback(),
		Authentication: genericoptions.NewDelegatingAuthenticationOptions(),
		Authorization:  genericoptions.NewDelegatingAuthorizationOptions(),
		Features:       genericoptions.NewFeatureOptions(),
		InSecureServing: (&genericoptions.DeprecatedInsecureServingOptions{
			BindAddress: net.ParseIP("0.0.0.0"),
			BindPort:    8080,
			BindNetwork: "tcp",
		}).WithLoopback(),
	}

	return o
}

func (o *WardleOptions) AddFlags(fs *pflag.FlagSet) {
	o.SecureServing.AddFlags(fs)
	o.Authentication.AddFlags(fs)
	o.Authorization.AddFlags(fs)
	o.Features.AddFlags(fs)
	o.InSecureServing.AddFlags(fs)
}

func (o *WardleOptions) Validate() []error {
	errors := []error{}
	errors = append(errors, o.SecureServing.Validate()...)
	errors = append(errors, o.Authentication.Validate()...)
	errors = append(errors, o.Authorization.Validate()...)
	errors = append(errors, o.Features.Validate()...)
	errors = append(errors, o.InSecureServing.Validate()...)

	return errors
}

func (o *WardleOptions) ApplyTo(config *genericapiserver.RecommendedConfig, insecureServingInfo **genericapiserver.DeprecatedInsecureServingInfo) error {
	if err := o.SecureServing.ApplyTo(&config.Config.SecureServing, &config.Config.LoopbackClientConfig); err != nil {
		return err
	}
	//if err := o.Authentication.ApplyTo(&config.Config.Authentication, config.SecureServing, config.OpenAPIConfig); err != nil {
	//	return err
	//}
	//if err := o.Authorization.ApplyTo(&config.Config.Authorization); err != nil {
	//	return err
	//}
	if err := o.InSecureServing.ApplyTo(insecureServingInfo, &config.Config.LoopbackClientConfig); err != nil {
		return err
	}
	return o.Features.ApplyTo(&config.Config)
}

// WardleServerOptions contains state for master/api server
type WardleServerOptions struct {
	WardleOptions *WardleOptions

	StdOut io.Writer
	StdErr io.Writer

	DSN                     string
	BackupDB                bool
	BackupDBPath            string
	Kubeconfig              string
	logLevel                string
	ESURL                   string
	TidbLogESIndexPrefix    string
	SystemLogESIndexPrefix  string
	SlowLogESIndexPrefix    string
	AuditLogESIndexPrefix   string
	APIServerCIDR           string
	K8sPromServiceName      string
	K8SPromNamespace        string
	AlertmanagerNamespace   string
	AlertmanagerServiceName string
	AlertmanagerSecretName  string
	TepServiceName          string
	TepNamespace            string
	TepAPIServerPort        int
	TepAPIVersion           string
	MetadataBackupImage     string
	AlertRetention          time.Duration
	KubeClientTimeout       time.Duration
}

// NewWardleServerOptions returns a new WardleServerOptions
func NewWardleServerOptions(out, errOut io.Writer) *WardleServerOptions {
	o := &WardleServerOptions{
		WardleOptions: NewWardleOptions(),

		StdOut: out,
		StdErr: errOut,
	}
	return o
}

// NewCommandStartWardleServer provides a CLI handler for 'start master' command
// with a default WardleServerOptions.
func NewCommandStartWardleServer(defaults *WardleServerOptions, stopCh <-chan struct{}) *cobra.Command {
	o := *defaults
	cmd := &cobra.Command{
		Short: "Launch a TEP API server",
		Long:  "Launch a TEP API server",
		RunE: func(c *cobra.Command, args []string) error {
			if err := o.Validate(args); err != nil {
				return err
			}
			return o.RunWardleServer(stopCh)
		},
	}

	flags := cmd.Flags()

	flag.StringVar(&o.DSN, "dsn", "root:@tcp(tep-meta-db:3306)/tep?charset=utf8&parseTime=True&loc=Local", "database connection string")
	flag.BoolVar(&o.BackupDB, "backup-db", false, "backup database before migration")
	flag.StringVar(&o.BackupDBPath, "backup-db-path", "/tmp", "the database backup file directory")
	flag.StringVar(&o.Kubeconfig, "kubeconfig", "", "kubeconfig file")
	flag.StringVar(&operator.RegistryPrefix, "registry-prefix", "", "docker registry prefix")
	flag.StringVar(&operator.TiDBOperatorVersion, "tidb-operator-version", "v1.0.3", "TiDB Operator version")
	flag.StringVar(&o.ESURL, "elasticsearch-url", "http://127.0.0.1:9200", "elasticsearch url")
	flag.StringVar(&o.TidbLogESIndexPrefix, "elasticsearch-tidb-index-prefix", "kubernetes_cluster", "elasticsearch tidb log index prefix")
	flag.StringVar(&o.SystemLogESIndexPrefix, "elasticsearch-syslog-index-prefix", "syslog", "elasticsearch system log index prefix")
	flag.StringVar(&o.SlowLogESIndexPrefix, "elasticsearch-slowlog-index-prefix", "slowlog", "elasticsearch slow log index prefix")
	flag.StringVar(&o.AuditLogESIndexPrefix, "elasticsearch-auditlog-index-prefix", "auditlog", "elasticsearch audit log index prefix")
	flag.BoolVar(&store.EnablePortForward, "enable-port-forwarder", false, "forward local port to the k8s service or pod port, with this flag on,  we can deploy TEP outside of the k8s cluster")
	flag.StringVar(&o.APIServerCIDR, "api-server-cidr", "", "the default whitelist")
	flag.StringVar(&o.K8sPromServiceName, "kubernetes-prometheus-service-name", "k8s-prometheus", "kubernetes prometheus service name")
	flag.StringVar(&o.K8SPromNamespace, "kubernetes-prometheus-namespace", "monitoring", "kubernetes prometheus namespace")
	flag.StringVar(&o.AlertmanagerNamespace, "alertmanager-namespace", "tep", "alertmanager namespace")
	flag.StringVar(&o.AlertmanagerServiceName, "alertmanager-service-name", "alertmanager-main", "alertmanager service name")
	flag.StringVar(&o.AlertmanagerSecretName, "alertmanager-secret-name", "alertmanager-main", "alertmanager secret name")
	flag.StringVar(&o.TepServiceName, "tep-service-name", "tep-apiserver", "tep service name")
	flag.StringVar(&o.TepNamespace, "tep-namespace", "tep", "tep namespace")
	flag.IntVar(&o.TepAPIServerPort, "tep-apserver-port", 443, "tep apiserver port")
	flag.StringVar(&o.TepAPIVersion, "tep-api-version", "v1alpha1", "tep apiserver version")
	flag.StringVar(&o.MetadataBackupImage, "metadata-backup-image", "hub.pingcap.net/pingcap/tep-metadata-backup-manager:latest", "backup image for metadata backup and restore job")
	flag.DurationVar(&o.AlertRetention, "alert-retention", time.Duration(1)*time.Hour*480, "How long to keep alerts for")
	flag.DurationVar(&o.KubeClientTimeout, "kube-client-timeout", 5*time.Second, "kube client timeout")

	o.WardleOptions.AddFlags(flags)
	utilfeature.DefaultMutableFeatureGate.AddFlag(flags)

	return cmd
}

// Validate validates WardleServerOptions
func (o WardleServerOptions) Validate(args []string) error {
	errors := []error{}
	errors = append(errors, o.WardleOptions.Validate()...)
	return utilerrors.NewAggregate(errors)
}

// Config returns config for the api server given WardleServerOptions
func (o *WardleServerOptions) Config() (*apiserver.Config, *genericapiserver.DeprecatedInsecureServingInfo, error) {
	// TODO have a "real" external address
	if err := o.WardleOptions.SecureServing.MaybeDefaultWithSelfSignedCerts("localhost", nil, []net.IP{net.ParseIP("127.0.0.1")}); err != nil {
		return nil, nil, fmt.Errorf("error creating self-signed certificates: %v", err)
	}

	var insecureServingInfo *genericapiserver.DeprecatedInsecureServingInfo
	serverConfig := genericapiserver.NewRecommendedConfig(apiserver.Codecs)

	//TODO: umcomment it after we resolved the open API generation issue
	//serverConfig.OpenAPIConfig = genericapiserver.DefaultOpenAPIConfig(sampleopenapi.GetOpenAPIDefinitions, openapi.NewDefinitionNamer(apiserver.Scheme))
	//serverConfig.OpenAPIConfig.Info.Title = "Wardle"
	//serverConfig.OpenAPIConfig.Info.Version = "0.1"

	if err := o.WardleOptions.ApplyTo(serverConfig, &insecureServingInfo); err != nil {
		return nil, nil, err
	}

	config := &apiserver.Config{
		GenericConfig: serverConfig,
		ExtraConfig:   apiserver.ExtraConfig{},
	}
	return config, insecureServingInfo, nil
}

// RunWardleServer starts a new WardleServer given WardleServerOptions
func (o WardleServerOptions) RunWardleServer(stopCh <-chan struct{}) error {
	log.Info("start to migrate database")
	var err error
	o.DSN, err = migrate.UpgradeDB(o.DSN, o.BackupDB, o.BackupDBPath)
	if err != nil {
		return err
	}
	log.Info("migrate database done")

	config, insecureServingInfo, err := o.Config()
	if err != nil {
		return err
	}
	var cfg *rest.Config
	if o.Kubeconfig == "" {
		cfg, err = rest.InClusterConfig()
	} else {
		cfg, err = clientcmd.BuildConfigFromFlags("", o.Kubeconfig)
	}
	if err != nil {
		return err
	}

	webhookPath := fmt.Sprintf("https://%s.%s.svc:%d/apis/%s/%s/webhook", o.TepServiceName, o.TepNamespace, o.TepAPIServerPort, apis.GroupName, o.TepAPIVersion)

	store, err := store.NewStore(o.DSN, cfg, es.ESConf{
		URL:                  o.ESURL,
		TiDBLogIndexPrefix:   o.TidbLogESIndexPrefix,
		SystemLogIndexPrefix: o.SystemLogESIndexPrefix,
		SlowLogIndexPrefix:   o.SlowLogESIndexPrefix,
		AuditLogIndexPrefix:  o.AuditLogESIndexPrefix,
	}, o.APIServerCIDR, provider.GetPromDNSName(o.K8SPromNamespace, o.K8sPromServiceName), o.AlertmanagerNamespace, o.AlertmanagerServiceName, o.AlertmanagerSecretName, webhookPath, o.MetadataBackupImage, o.AlertRetention, o.K8SPromNamespace, o.KubeClientTimeout)

	if err != nil {
		return err
	}

	// create a scheduled task to delete expired backups
	c := cron.New()
	c.AddFunc("0 0 * * *", store.Controller().DeleteBackupViaReservedTime)
	c.Start()

	server, err := config.Complete().New(store)
	if err != nil {
		return err
	}

	if insecureServingInfo != nil {
		insecureHandlerChain := kubeserver.BuildInsecureHandlerChain(server.GenericAPIServer.UnprotectedHandler(), &config.GenericConfig.Config)
		if err := insecureServingInfo.Serve(insecureHandlerChain, config.GenericConfig.RequestTimeout, stopCh); err != nil {
			return err
		}
	}

	return server.GenericAPIServer.PrepareRun().Run(stopCh)
}
