package main

import (
	"flag"
	"fmt"
	"io/fs"
	"os"
	"strings"

	"github.com/pingcap/errors"
	"github.com/pingcap/log"
	"github.com/pyroscope-io/client/pyroscope"
	"go.uber.org/zap"

	"github.com/pingcap-inc/clinic/pkg/config"
	_const "github.com/pingcap-inc/clinic/pkg/const"
	"github.com/pingcap-inc/clinic/pkg/db"
	"github.com/pingcap-inc/clinic/pkg/db/model"
	"github.com/pingcap-inc/clinic/pkg/event_bus"
	"github.com/pingcap-inc/clinic/pkg/event_listeners"
	"github.com/pingcap-inc/clinic/pkg/importer"
	"github.com/pingcap-inc/clinic/pkg/storage/aws"
	"github.com/pingcap-inc/clinic/pkg/storage/iface"
	"github.com/pingcap-inc/clinic/pkg/stream"
	manager2 "github.com/pingcap-inc/clinic/pkg/upload_file/manager"
)

var (
	cfgPath         string
	fileUUID        string
	orgID           uint
	clusterID       string
	mountPath       string
	taskTypes       string
	debug           bool
	pyroscopeServer string
)

func main() {
	flag.StringVar(&cfgPath, "config", "./", "impotered -c=/path/to/config.yaml")
	flag.StringVar(&fileUUID, "fid", "", "want rebuild which file")
	flag.UintVar(&orgID, "org", 0, "belong to which org")
	flag.StringVar(&clusterID, "cluster", "", "belong to which cluster")
	flag.StringVar(&mountPath, "dataPath", "", "belong to which cluster")
	flag.StringVar(&taskTypes, "types", "", "task types")
	flag.BoolVar(&debug, "debug", false, "enable debug")
	flag.StringVar(&pyroscopeServer, "pyroscope-server", "http://pyroscope-server.pyroscope.svc.cluster.loca:4040", "pyroscope server address")

	flag.Parse()

	if orgID <= 0 || fileUUID == "" || clusterID == "" || mountPath == "" {
		panic(errors.New("invalid parameter"))
	}

	tasks := decodeTaskTypes(taskTypes)
	if len(tasks) == 0 {
		log.Warn("empty tasks")
		return
	}

	if debug {
		profile, err := pyroscope.Start(pyroscope.Config{
			ApplicationName: "clinic.importer",
			ServerAddress:   pyroscopeServer,
			Logger:          pyroscope.StandardLogger,
			ProfileTypes: []pyroscope.ProfileType{
				pyroscope.ProfileCPU,
				pyroscope.ProfileInuseObjects,
				pyroscope.ProfileAllocObjects,
				pyroscope.ProfileInuseSpace,
				pyroscope.ProfileAllocSpace,
				pyroscope.ProfileGoroutines,
				pyroscope.ProfileMutexCount,
				pyroscope.ProfileMutexDuration,
				pyroscope.ProfileBlockCount,
				pyroscope.ProfileBlockDuration,
			},
		})
		if err != nil {
			log.Error("init pyroscope failed", zap.Error(err))
		}

		defer func() {
			_ = profile.Stop()
		}()
	}

	dataPath := fmt.Sprintf("%s/data", mountPath)
	err := os.RemoveAll(dataPath)
	if err != nil {
		panic(err)
	}

	if err := os.Mkdir(dataPath, fs.ModePerm); err != nil {
		panic(err)
	}

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

	// we need to init db since job can update its status directly
	err = db.Initialize(cfg.DBConfig, true)
	if err != nil {
		log.Error("init db failed", zap.Error(err))
		panic(err)
	}

	storage, err := newStorage(cfg.FileStorage)
	if err != nil {
		log.Error("init storage failed", zap.Error(err))
		panic(err)
	}

	file, err := model.QueryUploadFile(fileUUID)
	if err != nil {
		log.Error("QueryUploadFile failed", zap.Error(err))
		panic(err)
	}

	manager := manager2.NewUploadFileManager(storage)
	// register event listener before starting rebuild task but after init db
	event_bus.Register(event_listeners.NewRebuildEventListener())
	err = importer.NewImporter(&importer.ImporterOption{
		LogConfig:                cfg.SinkConfig.LogConfig,
		MetricConfig:             cfg.SinkConfig.MetricConfig,
		Storage:                  storage,
		Manager:                  manager,
		DataPath:                 dataPath,
		FileUUID:                 fileUUID,
		ClusterID:                clusterID,
		OrgID:                    orgID,
		ExpectedRebuildTaskTypes: tasks,
		BeginTS:                  file.BeginTime.UnixMilli(),
		EndTS:                    file.EndTime.UnixMilli(),
	}).Rebuild()
	if err != nil {
		t := make(map[_const.TaskType]struct{})
		for _, task := range tasks {
			t[task] = struct{}{}
		}
		importer.SendEvent(t, fileUUID, db.StatusFailed)
		panic(err)
	}

	log.Info("rebuild done")
}

func decodeTaskTypes(taskTypes string) []_const.TaskType {
	if len(taskTypes) == 0 {
		return nil
	}

	types := strings.Split(taskTypes, ",")

	results := make([]_const.TaskType, 0)
	stream.OfSlice(types).Map(func(str string) _const.TaskType {
		switch str {
		case "rebuildMetric":
			return _const.MetricTask
		case "rebuildConfiguration":
			return _const.RebuildConfTask
		case "rebuildLog":
			return _const.LogTask
		default:
			return _const.UnsupportTask
		}
	}).Filter(func(t _const.TaskType) bool {
		return t != _const.UnsupportTask
	}).CollectEmptySlice(&results)

	return results
}

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())
	}
}
