package cron

import (
	"context"
	"fmt"
	"sync"

	"github.com/robfig/cron/v3"

	"git.tswf.io/infra/go-synapse-backupper/pkg/domain"
)

// cronScheduler wraps robfig/cron/v3 to implement domain.Scheduler.
type cronScheduler struct {
	cron       *cron.Cron
	waitGroup  sync.WaitGroup
	stopCancel context.CancelFunc
	stoppedCtx context.Context
}

// NewCronScheduler creates a new cron-based scheduler.
// The expr must be a valid cron expression. When WithSeconds is used
// (as in this implementation) the expression should contain six fields.
func NewCronScheduler(
	expr string,
	job func(),
) (
	domain.Scheduler,
	error,
) {
	scheduler := &cronScheduler{
		cron: cron.New(cron.WithSeconds()),
	}

	_, err := scheduler.cron.AddFunc(
		expr,
		func() {
			scheduler.waitGroup.Add(1)
			defer scheduler.waitGroup.Done()
			job()
		},
	)
	if err != nil {
		return nil, fmt.Errorf("invalid cron expression %q: %w", expr, err)
	}

	scheduler.stoppedCtx, scheduler.stopCancel = context.WithCancel(context.Background())

	return scheduler, nil
}

// Start begins executing the scheduled job.
func (s *cronScheduler) Start() {
	s.cron.Start()
}

// Stop halts the scheduler and returns a context that is cancelled
// once all in-flight jobs have completed.
func (s *cronScheduler) Stop() context.Context {
	s.cron.Stop()
	go func() {
		s.waitGroup.Wait()
		s.stopCancel()
	}()
	return s.stoppedCtx
}
