package scheduler

import (
	"context"
	"log"
	"sync"
	"time"

	"fetcher/jobs"
)

// Runner executes registered jobs at their declared intervals.
type Runner struct {
	logger *log.Logger
}

// New creates a Runner that logs through the provided logger. Logger may be nil.
func New(logger *log.Logger) *Runner {
	return &Runner{logger: logger}
}

// Run starts all jobs and blocks until the context is cancelled.
func (r *Runner) Run(ctx context.Context, jobList []jobs.Job) {
	if len(jobList) == 0 {
		r.log("no jobs registered; scheduler idle until context cancelled")
		<-ctx.Done()
		return
	}

	var wg sync.WaitGroup
	wg.Add(len(jobList))

	for _, job := range jobList {
		job := job
		go func() {
			defer wg.Done()
			r.runJob(ctx, job)
		}()
	}

	<-ctx.Done()
	wg.Wait()
}

func (r *Runner) runJob(ctx context.Context, job jobs.Job) {
	interval := job.Interval()
	if interval <= 0 {
		r.logf("job %q provided non-positive interval %s; defaulting to 1s", job.Name(), interval)
		interval = time.Second
	}

	ticker := time.NewTicker(interval)
	defer ticker.Stop()

	r.execute(ctx, job)

	for {
		select {
		case <-ctx.Done():
			return
		case <-ticker.C:
			r.execute(ctx, job)
		}
	}
}

func (r *Runner) execute(ctx context.Context, job jobs.Job) {
	if ctx.Err() != nil {
		return
	}

	if err := job.Run(ctx); err != nil {
		r.logf("job %q failed: %v", job.Name(), err)
	}
}

func (r *Runner) log(message string) {
	if r.logger != nil {
		r.logger.Print(message)
	}
}

func (r *Runner) logf(format string, args ...interface{}) {
	if r.logger != nil {
		r.logger.Printf(format, args...)
	}
}
