package main

import (
	"flag"
	"fmt"
	"github.com/gin-gonic/gin"
	_ "github.com/go-sql-driver/mysql"
	"github.com/pingcap/log"
	"github.com/pingcap/tep/pkg/apis/apiserver/v1alpha1"
	tepserverapis "github.com/pingcap/tep/pkg/apis/tepserver"
	"github.com/pingcap/tep/pkg/tepserver"
	"github.com/pingcap/tep/pkg/tepserver/filters"
	"github.com/pingcap/tep/pkg/utils"
	"github.com/pingcap/tep/pkg/utils/logutil"
	"github.com/pingcap/tep/pkg/version"
	_ "github.com/pingcap/tep/statik"
	"github.com/rakyll/statik/fs"
	"go.uber.org/zap"
	"net/http"
	"os"
	"strings"
)

var (
	dsn                        string
	host                       string
	port                       int
	apiPrefix                  string
	kubeAPIServerHost          string
	kubeAPIServerPort          int
	kubeAPIServerSkipSSLVerify bool
	apiServerPrefix            string
	caFilePath                 string
	certFilePath               string
	keyFilePath                string
	pdStorageClass             string
	tikvStorageClass           string
	monitorStorageClass        string
	backupStorageClass         string
	remoteStorageClass         string
	backupStorageSize          string
	endpoint                   string
	accessKey                  string
	secretKey                  string
	region                     string
	logLevel                   string
	printVersion               bool
	provider                   string
	acl                        string
)

var (
	DefaultSchedule        = "Tuesday,Saturday 0:30"
	DefaultMaxReservedTime = "168h"
)

func init() {
	flag.StringVar(&dsn, "dsn", "root:@tcp(127.0.0.1:3306)/tep?charset=utf8&parseTime=true&loc=UTC", "database connection string")
	flag.StringVar(&host, "host", "0.0.0.0", "listen host")
	flag.IntVar(&port, "port", 8000, "listen port")
	flag.StringVar(&apiPrefix, "api-prefix", "/pingcap.com/v1", "api prefix of the tep-server")
	flag.StringVar(&apiServerPrefix, "apiserver-prefix", "/apis/tep.pingcap.com/v1alpha1", "api prefix of tep-apiserver")
	flag.StringVar(&kubeAPIServerHost, "kube-apiserver-host", "kubernetes.default.svc", "kube apiserver service")
	flag.IntVar(&kubeAPIServerPort, "kube-apiserver-port", 443, "the port of kube apiserver service")
	flag.BoolVar(&kubeAPIServerSkipSSLVerify, "kube-apiserver-skip-verify-ssl", false, "is true, TLS accepts any certificate presented by the server and any host name in that certificate")
	flag.StringVar(&caFilePath, "ca-file-path", "/var/run/secrets/kubernetes.io/serviceaccount/ca.crt", "CA cert to trust")
	flag.StringVar(&certFilePath, "cert-file-path", "/var/lib/tls/cert", "client cert used to connect to kube apiserver")
	flag.StringVar(&keyFilePath, "key-file-path", "/var/lib/tls/key", "private key of the client cert used to connect to kube apiserver")
	flag.StringVar(&pdStorageClass, "pd-storage-class", "local-storage", "the storage class for pd's persistent volumes")
	flag.StringVar(&tikvStorageClass, "tikv-storage-class", "local-storage", "the storage class for tikv's persistent volumes")
	flag.StringVar(&monitorStorageClass, "monitor-storage-class", "local-storage", "the storage class for monitor's persistent volumes")
	flag.StringVar(&backupStorageClass, "backup-storage-class", "local-storage", "the storage class for backup's persistent volumes")
	flag.StringVar(&remoteStorageClass, "remote-storage-class", "STANDARD", "the storage class for remote storage system (such as S3 and GCS)")
	flag.StringVar(&endpoint, "endpoint", "", "the endpoint of s3 storage")
	flag.StringVar(&accessKey, "access-key", "", "the accessKey of s3 storage")
	flag.StringVar(&secretKey, "secret-key", "", "the secretKey of s3 storage")
	flag.StringVar(&region, "region", "", "the region of s3 storage")
	flag.StringVar(&provider, "provider", "", "the provider of s3 storage")
	flag.StringVar(&acl, "acl", "", "the acl of s3 storage")
	flag.StringVar(&backupStorageSize, "backup-storage-size", "100Gi", "the storage size for backup persistent volumes")
	flag.StringVar(&logLevel, "log-level", "info", "logging level")
	flag.BoolVar(&printVersion, "V", false, "show version and quit")
	flag.BoolVar(&printVersion, "version", false, "show version and quit")
	flag.Parse()
}

func main() {
	logCfg := logutil.NewLogConfig(logLevel, logutil.DefaultLogFormat, logutil.EmptyFileLogConfig, false)
	logutil.InitZapLogger(logCfg)
	logutil.InitLogger(logCfg)

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

	log.Info(fmt.Sprintf("Logger initialized with level: %s", log.GetLevel()))

	if secretKey == "" {
		log.Fatal("secret-key is a required field")
	}
	if accessKey == "" {
		log.Fatal("access-key is a required field")
	}

	svr, err := tepserver.NewServer(&tepserver.ServerConfig{
		APIPrefix:                  apiPrefix,
		KubeAPIServerHost:          kubeAPIServerHost,
		KubeAPIServerPort:          kubeAPIServerPort,
		KubeAPIServerSkipSSLVerify: kubeAPIServerSkipSSLVerify,
		APIServerPrefix:            apiServerPrefix,
		CAFilePath:                 caFilePath,
		CertFilePath:               certFilePath,
		KeyFilePath:                keyFilePath,
		DSN:                        dsn,
		PDStorageClass:             pdStorageClass,
		BackupStorageClass:         backupStorageClass,
		RemoteStorageClass:         remoteStorageClass,
		TiKVStorageClass:           tikvStorageClass,
		MonitorStorageClass:        monitorStorageClass,
		BackupStorageSize:          backupStorageSize,
		Endpoint:                   endpoint,
		AccessKey:                  accessKey,
		SecretKey:                  secretKey,
		Region:                     region,
		Provider:                   provider,
		ACL:                        acl,
	})

	if err != nil {
		log.Fatal("failed to create tep-server", zap.Error(err))
	}

	backup := &v1alpha1.MetadataScheduledBackup{
		Schedule:        DefaultSchedule,
		MaxReservedTime: DefaultMaxReservedTime,
	}
	if _, initStatus := svr.CreateMetadataScheduledBackup(backup); !initStatus.OK() {
		// only leave a message means cronjob create error
		log.Error("failed to create scheduled metadata backup", zap.String("msg", initStatus.Message()))
	}

	engine := tepserverapis.NewService(svr)

	statikFS, err := fs.New()
	if err != nil {
		log.Fatal("failed to embed static files into your binary", zap.Error(err))
	}
	staticHandler := http.FileServer(statikFS)

	engine.NoRoute(func(c *gin.Context) {
		// reverse proxy requests with API prefix to apiserver
		if strings.HasPrefix(c.Request.URL.Path, apiPrefix) {
			authErr := filters.Auth(c, svr.SessionManager, fmt.Sprintf("%s%s", svr.APIPrefix, tepserverapis.LoginPath))
			if authErr == nil {
				svr.APIServerReverseProxy(c)
			} else {
				utils.SendMsg(c, nil, utils.NewAPIStatus(http.StatusUnauthorized, authErr.Error()))
			}
			return
		}

		// handle paths for static resources
		if !strings.HasPrefix(c.Request.URL.Path, "/static/") {
			c.Request.URL.Path = "/"
		}
		staticHandler.ServeHTTP(c.Writer, c.Request)
	})

	addr := fmt.Sprintf("%s:%d", host, port)
	log.Fatal("failed to bind", zap.String("address", engine.Run(addr).Error()))
}
