// Copyright (c) 2022 Gitpod GmbH. All rights reserved.
// Licensed under the GNU Affero General Public License (AGPL).
// See License.AGPL.txt in the project root for license information.

package scheduler

import (
	"github.com/gitpod-io/gitpod/common-go/log"
	"github.com/robfig/cron"
	"sync"
	"time"
)

func New(jobs ...JobSpec) *Scheduler {
	return &Scheduler{
		specs:       jobs,
		runningJobs: sync.WaitGroup{},
		cron:        cron.NewWithLocation(time.UTC),
	}
}

type Scheduler struct {
	specs       []JobSpec
	runningJobs sync.WaitGroup

	cron *cron.Cron
}

type JobSpec struct {
	Job      Job
	ID       string
	Schedule cron.Schedule
}

func (c *Scheduler) Start() {
	log.Infof("Starting usage scheduler. Setting up %d jobs.", len(c.specs))

	for _, job := range c.specs {
		// need to re-assign job to avoid pointing to a different job spec once the `cron.FuncJob` executes.
		j := job
		c.cron.Schedule(job.Schedule, cron.FuncJob(func() {
			c.runningJobs.Add(1)
			defer c.runningJobs.Done()

			now := time.Now().UTC()
			logger := log.WithField("job_id", j.ID)

			logger.Infof("Starting scheduled job %s", j.ID)
			reportJobStarted(j.ID)

			err := j.Job.Run()
			defer func() {
				reportJobCompleted(j.ID, time.Since(now), err)
			}()
			if err != nil {
				logger.WithError(err).Errorf("Scheduled job %s failed.", job.ID)
				return
			}
			logger.Infof("Scheduled job %s completed succesfully.", job.ID)
		}))
	}

	c.cron.Start()
}

// Stop terminates the Scheduler and awaits for all running jobs to complete.
func (c *Scheduler) Stop() {
	log.Info("Stopping scheduler.")
	c.cron.Stop()

	log.Info("Awaiting existing jobs to complete.")
	// Wait for existing jobs to finish
	c.runningJobs.Wait()

	log.Infof("All running jobs completed.")
}
