package worker

import (
	"context"
	"fmt"
	"runtime"
	"strings"
	"time"

	"github.com/go-co-op/gocron"
	"github.com/tidbcloud/tiinf/monitor/tilog"
	"go.opentelemetry.io/otel"
	"go.uber.org/zap"

	"github.com/tidbcloud/tidb-management-service/tidb-mgmt-service/domain/service/worker/shadowpool"
)

type Runner struct {
	scheduler *gocron.Scheduler
}

func NewRunner(shadowPoolWorker shadowpool.Worker) *Runner {
	scheduler := gocron.NewScheduler(time.UTC)
	scheduler.SingletonModeAll()

	if _, err := scheduler.
		Every(time.Minute * 1).
		Do(FunWrapper("Fulfil_Shadow_Pool", shadowPoolWorker.Fulfil)); err != nil {
		panic(err)
	}

	if _, err := scheduler.
		Every(time.Minute * 10).
		Do(FunWrapper("Free_Clusters_in_Shadow_Pool", shadowPoolWorker.Free)); err != nil {
		panic(err)
	}

	if _, err := scheduler.
		Every(time.Minute * 1).
		Do(FunWrapper("Inventory_Clusters_in_Shadow_Pool", shadowPoolWorker.Inventory)); err != nil {
		panic(err)
	}

	return &Runner{
		scheduler: scheduler,
	}
}

func (w *Runner) StartWorker() {
	tilog.Info(context.Background(), "start worker runner...")
	w.scheduler.StartAsync()
	tilog.Info(context.Background(), "worker runner started")
}

func (w *Runner) StopWorker() {
	tilog.Info(context.Background(), "stop worker runner...")
	w.scheduler.Stop()
	tilog.Info(context.Background(), "worker runner stopped")
}

var Tracer = otel.Tracer("tidb-mgmt-service-worker")

type Fun func() error

type Handler func(ctx context.Context) error

func FunWrapper(workerName string, f Handler) Fun {
	handler := WithRecover(workerName, f)
	return func() error {
		ctx := context.Background()
		ctx, span := Tracer.Start(ctx, genWorkerSpanName(workerName))
		defer span.End()
		return handler(ctx)
	}
}

func WithRecover(workerName string, f Handler) Handler {
	return func(ctx context.Context) error {
		defer func() {
			if rErr := recover(); rErr != nil {
				var stacktrace strings.Builder
				for i := 2; ; i++ {
					_, f, l, got := runtime.Caller(i)
					if !got {
						break
					}
					_, _ = fmt.Fprintf(&stacktrace, "%s:%d\n", f, l)
				}
				tilog.Error(ctx, "get panic when running action",
					zap.String("worker", workerName),
					zap.String("stack trace", stacktrace.String()),
					zap.Any("error msg", rErr))
			}
		}()
		return f(ctx)
	}
}

func genWorkerSpanName(workerName string) string {
	return fmt.Sprintf("tidb-mgmt-service-worker.%s", workerName)
}
