package main

import (
	"context"
	"crypto/rsa"
	"crypto/x509"
	"encoding/pem"
	"flag"
	"fmt"
	"os"
	"time"

	"github.com/gin-contrib/pprof"
	"github.com/gin-gonic/gin"
	gocache "github.com/patrickmn/go-cache"
	"github.com/pingcap/errors"
	"github.com/pingcap/log"
	"go.uber.org/zap"

	"github.com/pingcap-inc/clinic/pkg/account_sdk"
	"github.com/pingcap-inc/clinic/pkg/api"
	"github.com/pingcap-inc/clinic/pkg/config"
	"github.com/pingcap-inc/clinic/pkg/datasource"
	"github.com/pingcap-inc/clinic/pkg/db"
	"github.com/pingcap-inc/clinic/pkg/es"
	"github.com/pingcap-inc/clinic/pkg/internal_api"
	"github.com/pingcap-inc/clinic/pkg/o11y"
	"github.com/pingcap-inc/clinic/pkg/proxy"
	"github.com/pingcap-inc/clinic/pkg/rbac"
	"github.com/pingcap-inc/clinic/pkg/resource_limit"
	"github.com/pingcap-inc/clinic/pkg/storage/aws"
	"github.com/pingcap-inc/clinic/pkg/storage/iface"
	"github.com/pingcap-inc/clinic/pkg/task_manager"
	"github.com/pingcap-inc/clinic/pkg/upload_file/decrypt"
	uploadfilemanager "github.com/pingcap-inc/clinic/pkg/upload_file/manager"
)

var cfgPath string

func main() {
	flag.StringVar(&cfgPath, "c", "./", "internal_server -c=/path/to/config.yaml")
	flag.Parse()

	// no need to load dashboard config
	cfg, err := config.InitConfig(cfgPath)
	if err != nil {
		panic(err)
	}

	err = db.Initialize(cfg.DBConfig, true)
	if err != nil {
		log.Error("init db failed", zap.Error(err))
		panic(err)
	}

	var accountCli *account_sdk.Client
	log.Info("tidb-cloud-account", zap.String("endpoint", cfg.DBaasConfig.AccountSvcAddr))
	if cfg.DBaasConfig.AccountSvcAddr != "" {
		cli, err := account_sdk.New(cfg.DBaasConfig.AccountSvcAddr, account_sdk.TimeoutOption(2*time.Second))
		if err != nil {
			log.Fatal("init account client failed", zap.Error(err))
		}

		accountCli = cli
	}

	err = datasource.Initialize(cfg.DBaasConfig, accountCli)
	if err != nil {
		log.Error("init dbaascore failed", zap.Error(err))
		panic(err)
	}
	engine := gin.New()
	engine.Use(
		gin.LoggerWithConfig(gin.LoggerConfig{
			SkipPaths: []string{
				"/metrics",
				"/api/ping",
			},
		}),
		gin.Recovery(),
	)

	pprof.Register(engine)

	auth, err := rbac.InitAuth(cfg.DBConfig, cfg.ServerConfig.RbacModelPath)
	if err != nil {
		log.Fatal("init rbac failed", zap.Error(err))
	}

	priv, err := loadPrivateKey(cfg.FileStorage.RSAPrivateKey)
	if err != nil {
		log.Error("load private key failed", zap.Error(err))
		panic(err)
	}

	storage, err := newStorage(cfg.FileStorage)
	if err != nil {
		log.Error("init storage failed", zap.Error(err))
		panic(err)
	}
	taskManagerCli := task_manager.NewTaskManagerClient(cfg.TaskManagerConfig)
	decryptor := decrypt.NewDecrypt(priv, storage, nil, nil)
	esClient := es.NewESClient(cfg.SinkConfig.LogConfig)
	o11yProxyHandler := proxy.NewO11yProxyHandler(auth, cfg.CentralO11yConfig.Env)
	metricProxyHandler := proxy.NewTiDBClusterMetricHandler(auth, &cfg.SinkConfig.MetricConfig.SelectAddr, cfg.CentralO11yConfig.Env)
	if cfg.EnableO11yCanary {
		if err := cfg.CentralO11yConfig.Validate(); err != nil {
			log.Fatal("central o11y config invalid", zap.Error(err))
		}
		o11ySharedOpts := []o11y.Option{
			o11y.TimeoutOption(3 * time.Second),
			o11y.MaxIdleConnOption(10),
		}
		o11yCli, err := o11y.New(
			cfg.CentralO11yConfig.GSEndpoint,
			cfg.CentralO11yConfig.DSEndpointTPL,
			cfg.CentralO11yConfig.ClientId,
			cfg.CentralO11yConfig.ClientSecret,
			cfg.CentralO11yConfig.Env,
			append(o11ySharedOpts, o11y.ByocDSEndpointOption(cfg.CentralO11yConfig.ByocDSEndpoint))...,
		)
		if err != nil {
			log.Fatal("init central o11y client failed", zap.Error(err))
		}
		metricProxyHandler.SetO11y(o11yCli)
		o11yProxyHandler.SetO11y(o11yCli)
		o11yProxyHandler.SetClusterInfoCache(gocache.New(gocache.NoExpiration, gocache.NoExpiration))
	}

	api.InitHealthCheckApi(engine)
	if err := internal_api.NewAPIService(&internal_api.APIServiceParams{
		Gin:              engine,
		Ctx:              context.Background(),
		Cfg:              cfg,
		Rbac:             auth,
		MetricHandler:    metricProxyHandler,
		O11yProxyHandler: o11yProxyHandler,
		QuotaLimiter:     &resource_limit.NopQuotaLimiter{},
		TaskManagerCli:   taskManagerCli,
		Decrypt:          decryptor,
		EsClient:         esClient,
		Manager:          uploadfilemanager.NewUploadFileManager(storage),
	}).Start(); err != nil {
		log.Fatal("can not start server", zap.Error(err))
	}

	log.Panic("start server failed", zap.Error(engine.Run(cfg.ServerConfig.Addr)))
}

func newStorage(cfg config.FileStorage) (iface.IStorage, error) {
	switch cfg.StorageType() {
	case config.AWSStorageType:
		return aws.NewAWSStorage(cfg)
	default:
		return nil, errors.Errorf("unsupport storage type %s", cfg.StorageType())
	}
}

func loadPrivateKey(kpath string) (*rsa.PrivateKey, error) {
	data, err := os.ReadFile(kpath)
	if err != nil {
		return nil, err
	}
	block, _ := pem.Decode(data)
	if block == nil {
		return nil, fmt.Errorf("decode pem failed")
	}
	priv, err := x509.ParsePKCS1PrivateKey(block.Bytes)
	if err != nil {
		return nil, fmt.Errorf("failed to parse certificate: %s", err.Error())
	}
	return priv, nil
}
