package healthcheck

import (
	"strings"
	"time"

	"github.com/pingcap/log"
	"github.com/robfig/cron/v3"
	"go.uber.org/zap"
)

// cronParser is the global cron parser instance
// Supports standard 5-field cron: minute hour dom month dow
// Also supports descriptors like "@hourly", "@daily", etc.
var cronParser = cron.NewParser(cron.Minute | cron.Hour | cron.Dom | cron.Month | cron.Dow | cron.Descriptor)

// calculateNextRunTime calculates the next run time based on the cron expression
// Returns nil if the expression is invalid or empty
func calculateNextRunTime(cronExpr string, fromTime ...time.Time) *time.Time {
	if cronExpr == "" {
		return nil
	}

	schedule, err := cronParser.Parse(cronExpr)
	if err != nil {
		log.Error("failed to parse cron expression",
			zap.String("expression", cronExpr),
			zap.Error(err),
		)
		return nil
	}

	baseTime := time.Now()
	if len(fromTime) > 0 {
		baseTime = fromTime[0]
	}

	next := schedule.Next(baseTime)
	return &next
}

// ExtractCronLocation extracts the timezone from a cron expression, mirroring
// robfig/cron's own parsing: a "CRON_TZ=" or "TZ=" prefix selects the location,
// otherwise it falls back to time.Local (the cron library's default). The
// fallback must be time.Local rather than UTC so that the inspection-window
// clock times are interpreted in the same timezone the cron actually triggers.
// Inspection-window strategies are always created with a CRON_TZ= prefix, so the
// fallback only applies to legacy/hand-built expressions without one.
func ExtractCronLocation(scheduleExpr string) *time.Location {
	expr := strings.TrimSpace(scheduleExpr)
	var tz string
	switch {
	case strings.HasPrefix(expr, "CRON_TZ="):
		tz = expr[len("CRON_TZ="):]
	case strings.HasPrefix(expr, "TZ="):
		tz = expr[len("TZ="):]
	default:
		return time.Local
	}
	if i := strings.IndexAny(tz, " \t"); i >= 0 {
		tz = tz[:i]
	}
	loc, err := time.LoadLocation(tz)
	if err != nil {
		log.Warn("failed to load cron timezone, falling back to local",
			zap.String("tz", tz),
			zap.Error(err),
		)
		return time.Local
	}
	return loc
}

// validateCronExpression validates if the given string is a valid cron expression
func validateCronExpression(cronExpr string) error {
	if cronExpr == "" {
		return nil
	}
	_, err := cronParser.Parse(cronExpr)
	return err
}
