package backup

import (
	"time"

	"github.com/robfig/cron/v3"
)

const (
	extraOffsetSecs = 1800 // 0.5 hours
)

// RetentionSchedule implements cron.Schedule interface,
// it schedules backups at the next victoriametrics retention time plus an extra offset.
type RetentionSchedule struct {
	RetentionPeriod         time.Duration
	RetentionTimeZoneOffset time.Duration
}

var _ cron.Schedule = &RetentionSchedule{}

// Next returns the next victoriametrics retention time plus an extra offset,
// this ensures that the backup is scheduled after the victoriametrics indexdb rotation.
func (s *RetentionSchedule) Next(t time.Time) time.Time {
	retentionSecs := int64(s.RetentionPeriod.Seconds())
	offsetSecs := extraOffsetSecs + int64(s.RetentionTimeZoneOffset.Seconds())
	// Start at the earliest possible time (the upcoming second).
	t = t.Add(1*time.Second - time.Duration(t.Nanosecond())*time.Nanosecond)
	return time.Unix(nextRetentionDeadlineSeconds(t.Unix(), retentionSecs, offsetSecs), 0)
}

// nextRetentionDeadlineSeconds calculates the next retention deadline in seconds
// this function is copied from VictoriaMetrics,
// refer to https://github.com/VictoriaMetrics/VictoriaMetrics/blob/9b8caebc72408f5f19d3299d711ced1fcdd24f23/lib/storage/storage.go#L1201
func nextRetentionDeadlineSeconds(atSecs, retentionSecs, offsetSecs int64) int64 {
	// Round retentionSecs to days. This guarantees that per-day inverted index works as expected
	const secsPerDay = 24 * 3600
	retentionSecs = ((retentionSecs + secsPerDay - 1) / secsPerDay) * secsPerDay

	// Schedule the deadline to +4 hours from the next retention period start
	// because of historical reasons - see https://github.com/VictoriaMetrics/VictoriaMetrics/issues/248
	offsetSecs -= 4 * 3600

	// Make sure that offsetSecs doesn't exceed retentionSecs
	offsetSecs %= retentionSecs

	// align the retention deadline to multiples of retentionSecs
	// This makes the deadline independent of atSecs.
	deadline := ((atSecs + offsetSecs + retentionSecs - 1) / retentionSecs) * retentionSecs

	// Apply the provided offsetSecs
	deadline -= offsetSecs

	return deadline
}
