package wrapworker

import (
	"fmt"
	"github.com/pingcap/dbaas/cost-metering-service/billing_errors"
	"github.com/pingcap/dbaas/cost-metering-service/config"
	"github.com/pingcap/dbaas/cost-metering-service/log"
	"github.com/pingcap/dbaas/cost-metering-service/utils/header"
	"reflect"
	"runtime"
	"time"

	"github.com/go-co-op/gocron"
	"github.com/google/uuid"

	"github.com/pingcap/errors"
	"go.uber.org/zap"
)

// wrapWorker
func wrapWorker(jobFun interface{}, interval string, runInPersonalEnv bool) {

	// generate traceID
	uuid := uuid.New()
	header.SetTraceID(uuid.String())
	// get workerName with reflect
	f := reflect.ValueOf(jobFun)
	workerName := runtime.FuncForPC(f.Pointer()).Name()

	now := time.Now()
	func() {
		defer func() {
			if r := recover(); r != nil {
				err := errors.WithStack(fmt.Errorf("%v", r))
				log.Error("worker panic", zap.Error(err))
			}
		}()
		f.Call(make([]reflect.Value, 0))
	}()
	after := time.Now()
	log.Info("Worker Task Finished", zap.String("workerName", workerName), zap.Time("workerBegin", now), zap.Time("workerEnd", after))
	intervalValue, _ := time.ParseDuration(interval)
	if int(after.Unix()-now.Unix()) > int(intervalValue.Seconds()) {
		// log error
		log.Error("The task execution time exceeds the interval",
			zap.String("workerName", workerName),
			zap.Int("duration", int(after.Unix()-now.Unix())),
			zap.Int("interval", int(intervalValue.Seconds())),
		)
	}
}

func AddWorker(s *gocron.Scheduler, jobFun interface{}, interval string, runInPersonalEnv bool) error {
	// validation
	if s == nil || len(interval) == 0 {
		return billing_errors.NewSystemError(fmt.Errorf("input of worker task validation fail"))
	}
	typ := reflect.TypeOf(jobFun)
	if typ.Kind() != reflect.Func {
		return billing_errors.NewSystemError(fmt.Errorf("input of worker task validation fail: jobFun is not function"))
	}
	// if interval is not valid， there will be an error
	// job run in different routines
	_, err := s.Every(interval).Do(wrapWorker, jobFun, interval, runInPersonalEnv)
	if err != nil {
		return err
	}
	return nil
}

func NewWorker(jobFun interface{}, interval string, runInPersonalEnv bool) (*gocron.Scheduler, error) {
	s := gocron.NewScheduler(time.UTC)
	s.SetMaxConcurrentJobs(config.Get().GetInt("3"), gocron.WaitMode)
	err := AddWorker(s, jobFun, interval, runInPersonalEnv)
	if err != nil {
		return nil, err
	}
	return s, nil
}
