package cron

import (
	"fmt"
	"log"
	"time"
)

// Cron handles jobs scheduling and execution
type Cron struct {
	jobs        []Job
	ErrChan     chan error
	DoneChan    chan struct{}
	SuccessChan chan string
}

// Job is a type that holds the details for each job.
type Job struct {
	Name     string
	Schedule string
	Action   JobFunc
}

type JobFunc func() error

func New() *Cron {
	return &Cron{
		jobs:        make([]Job, 0),
		ErrChan:     make(chan error),
		SuccessChan: make(chan string),
		DoneChan:    make(chan struct{}),
	}
}

func (c *Cron) AddJob(job Job) {
	c.jobs = append(c.jobs, job)
}

func (c *Cron) Start() {
	for _, j := range c.jobs {
		go c.scheduleJob(j)
	}
}

func (c *Cron) Stop() {
	close(c.DoneChan)
	close(c.SuccessChan)
	close(c.ErrChan)
}

// scheduleJob adds a task to the Cron schedule based on its schedule
func (c *Cron) scheduleJob(j Job) {
	for {
		select {
		case <-c.DoneChan:
			log.Printf("stopping job %s", j.Name)
			return
		default:
			now := time.Now()

			var next time.Time
			switch j.Schedule {
			case "minute":
				next = now.Add(10 * time.Second)
			case "daily":
				next = now.AddDate(0, 0, 1).Truncate(24 * time.Hour)
			case "weekly":
				next = now.AddDate(0, 0, 7).Truncate(24 * time.Hour)
			case "monthly":
				nextMonth := now.AddDate(0, 1, 0)
				next = time.Date(nextMonth.Year(), nextMonth.Month(), 1, 0, 0, 0, 0, nextMonth.Location())
			default:
				log.Printf("Unknown schedule %q for job %q", j.Schedule, j.Name)
				return
			}

			sleepDuration := time.Until(next)
			log.Printf("Job %q will run in %s", j.Name, sleepDuration.String())
			time.Sleep(sleepDuration)

			if err := j.Action(); err != nil {
				c.ErrChan <- fmt.Errorf("job %s failed: %w", j.Name, err)
			} else {
				c.SuccessChan <- fmt.Sprintf("job %s completed successfully", j.Name)
			}
		}
	}
}
