package main

import (
	"flag"
	"os"
	"os/signal"
	"runtime/debug"
	"syscall"
	"time"

	"github.com/go-co-op/gocron"
	_ "github.com/go-sql-driver/mysql"
	_ "go.uber.org/automaxprocs"

	. "github.com/pingcap/test-infra/services/tcms/cmd/jobs/scheduled"
	"github.com/pingcap/test-infra/services/tcms/handler"
	"github.com/pingcap/test-infra/services/tcms/utils"
	. "github.com/pingcap/test-infra/services/tcms/utils/log"
	"github.com/pingcap/test-infra/services/tcms/utils/sql"
)

var (
	version   = "latest"
	buildTime = "none"

	config = flag.String("config", utils.TestConfig, "specify the config path")
	secret = flag.String("secret", utils.TestSecretConfig, "specify the secret config directory")
)

type CronJob struct {
	Job
	Cron     string
	Interval time.Duration
}

type Job interface {
	Run()
}

func NewJobs(cfg *utils.Config) []CronJob {
	// Use the same services as TCMS handler used.
	handlerSvc := handler.NewHandlerService(cfg)
	db := sql.MustConnect(cfg.DSN)

	return []CronJob{
		{Job: NewTrigger(handlerSvc.Trigger), Interval: time.Second},
		{Job: NewSvcTaskController(handlerSvc.SvcTaskRun), Interval: 30 * time.Second},
		{Job: NewMeilisync(db, handlerSvc.Search, handlerSvc.Case, handlerSvc.Plan, handlerSvc.PlanExec), Interval: 5 * time.Second},
		{Job: NewTestbedInfoSyncer(cfg.JWTKey, handlerSvc.PlanExec, handlerSvc.Testbed), Interval: 90 * time.Second},
		{Job: NewResourcePoolSyncer(cfg.JWTKey, handlerSvc.RMSRPC, handlerSvc.RMS, handlerSvc.ResourcePool), Interval: time.Minute},

		{Job: NewDailyRunThrottle(db), Cron: "@hourly"},
		{Job: NewDailySummary(handlerSvc.Summary), Cron: "@hourly"},
	}
}

func registerJob(s *gocron.Scheduler, cj CronJob) {
	var h *gocron.Job
	cron, interval := cj.Cron, cj.Interval
	fn := func() {
		defer func() {
			if r := recover(); r != nil {
				Log.Errorf(nil, "unexpected panic: %+v, stack: %s", r, string(debug.Stack()))
			}
		}()
		cj.Run()
	}
	if cron != "" {
		h, _ = s.Cron(cron).Do(fn)
	} else {
		h, _ = s.Every(interval).Do(fn)
	}
	// SingletonMode prevents a new job from starting if the prior job has not yet
	// completed it's run.
	h.SingletonMode()
}

func waitShutdown(s *gocron.Scheduler) {
	shutdown := make(chan os.Signal, 1)
	signal.Notify(shutdown, os.Interrupt, syscall.SIGINT, syscall.SIGTERM)
	<-shutdown
	s.Stop()
}

func main() {
	Log.Infof("version: %s, build time: %s", version, buildTime)
	flag.Parse()

	jobs := NewJobs(utils.NewConfig(*config, *secret))
	s := gocron.NewScheduler(time.UTC)
	for _, j := range jobs {
		registerJob(s, j)
	}
	s.StartAsync()

	waitShutdown(s)
}
