package cron

import (
	"context"
	"fmt"
	"log"
	"strings"
	"sync"

	"github.com/robfig/cron/v3"
	"github.com/tidbcloud/tiinf/monitor/tilog"
	"go.uber.org/zap"
)

type CronJobManager struct {
	cron *cron.Cron
	jobs map[string]cron.EntryID
	mu   sync.RWMutex
	ctx  context.Context
}

type CronJob interface {
	Name() string
	CronExpression() string
	Execute(ctx context.Context) error
}

// NewCronJobManager
func NewCronJobManager() *CronJobManager {
	c := cron.New(cron.WithSeconds(), cron.WithLogger(cron.VerbosePrintfLogger(log.New(log.Writer(), "CRON: ", log.LstdFlags))))

	return &CronJobManager{
		cron: c,
		jobs: make(map[string]cron.EntryID),
		ctx:  context.Background(),
	}
}

// AddJob
func (m *CronJobManager) AddJob(job CronJob) error {
	m.mu.Lock()
	defer m.mu.Unlock()

	if _, exists := m.jobs[job.Name()]; exists {
		return fmt.Errorf("job with ID '%s' already exists", job.Name())
	}

	// add the job to the cron scheduler
	// check expression validity
	expression := job.CronExpression()
	if len(strings.Split(expression, " ")) == 5 {
		// old cron expression format minute, hour, day of month, month, day of week
		// but robfig/cron/v3 expects 6 fields (second, minute, hour, day of month, month, day of week)
		expression = "0 " + expression // prepend 0 for seconds
	}

	entryId, err := m.cron.AddFunc(expression, func() {
		if err := job.Execute(m.ctx); err != nil {
			tilog.Error(m.ctx, "Failed to execute job", zap.String("id", job.Name()), zap.Error(err))
		}
	})
	if err != nil {
		tilog.Error(m.ctx, "Failed to add job to cron", zap.String("id", job.Name()), zap.Error(err))
		return fmt.Errorf("invalid cron expression: %w", err)
	}

	m.jobs[job.Name()] = entryId
	tilog.Info(m.ctx, "job added", zap.String("job", job.Name()), zap.String("cron", job.CronExpression()))
	return nil
}

// Start
func (m *CronJobManager) Start() {
	tilog.Info(m.ctx, "Starting cron scheduler")
	m.cron.Start()
}

// Stop
func (m *CronJobManager) Stop() {
	tilog.Info(m.ctx, "Stopping cron scheduler")
	m.cron.Stop()
}
