package service

import (
	"context"
	"sync"

	"github.com/tidbcloud/aisupport/packages/sdk-go/infra/logger"
)

const (
	TraceScopeName = "devops.cloud.pingcap.com/job"
)

type serviceNameContextKey struct{}

type Hook struct {
	BeforeRun func(ctx context.Context) error
	AfterRun  func(ctx context.Context, cause error)
}

// JobService is a base service to run jobs, it implements `RunnableService`
// and provide some utils.
type JobService struct {
	*BaseService

	hook Hook
	jobs []Job
}

func NewJobService(name string, opts ...BaseServiceOption) *JobService {
	return &JobService{
		BaseService: NewBaseService(name, opts...),
		jobs:        make([]Job, 0),
	}
}

func (s *JobService) AddJob(job ...Job) {
	s.jobs = append(s.jobs, job...)
}

func (s *JobService) SetHook(hook Hook) {
	s.hook = hook
}

// Run implements RunnableService.
func (s *JobService) Run(ctx context.Context) error {
	ctx = s.WrapContext(ctx) // wrap context to add service name in logger
	ctx = context.WithValue(ctx, serviceNameContextKey{}, s.Name())

	if s.hook.BeforeRun != nil {
		err := s.hook.BeforeRun(ctx)
		if err != nil {
			return err
		}
	}

	wg := sync.WaitGroup{}
	for _, j := range s.jobs {
		wg.Add(1)

		go func(j Job) {
			defer wg.Done()

			logger.FromContext(ctx).Info("a job of service is started", "job", j.Name())

			j.Run(ctx)

			logger.FromContext(ctx).Info("a job of service is stopped", "job", j.Name())
		}(j)
	}

	wg.Wait()
	cause := context.Cause(ctx)

	if s.hook.AfterRun != nil {
		s.hook.AfterRun(ctx, cause)
	}

	logger.FromContext(ctx).Info("service exit because context canceld", "cause", cause)
	return nil
}

func GetServiceNameFromContext(ctx context.Context) string {
	serviceName, ok := ctx.Value(serviceNameContextKey{}).(string)
	if ok && serviceName != "" {
		return serviceName
	}
	return "unknown"
}
