package valueobjects import ( "fmt" "time" "github.com/knowfoolery/backend/shared/errors" "github.com/knowfoolery/backend/shared/types" ) type ResetFrequency string const ( ResetFrequencyNever ResetFrequency = "never" ResetFrequencyDaily ResetFrequency = "daily" ResetFrequencyWeekly ResetFrequency = "weekly" ResetFrequencyMonthly ResetFrequency = "monthly" ResetFrequencyCustom ResetFrequency = "custom" ) func (rf ResetFrequency) IsValid() bool { switch rf { case ResetFrequencyNever, ResetFrequencyDaily, ResetFrequencyWeekly, ResetFrequencyMonthly, ResetFrequencyCustom: return true default: return false } } type ResetAction string const ( ResetActionClear ResetAction = "clear" ResetActionArchive ResetAction = "archive" ResetActionMerge ResetAction = "merge" ) func (ra ResetAction) IsValid() bool { switch ra { case ResetActionClear, ResetActionArchive, ResetActionMerge: return true default: return false } } type ResetRules struct { frequency ResetFrequency action ResetAction customInterval time.Duration timezone string preserveTopN int32 resetTime time.Time nextResetAt time.Time autoReset bool notifyBeforeReset time.Duration metadata map[string]interface{} } func NewResetRules(frequency ResetFrequency, action ResetAction) (*ResetRules, error) { if !frequency.IsValid() { return nil, errors.ErrInvalidResetFrequency } if !action.IsValid() { return nil, errors.ErrInvalidResetAction } rr := &ResetRules{ frequency: frequency, action: action, timezone: "UTC", preserveTopN: 0, autoReset: false, notifyBeforeReset: time.Hour * 24, metadata: make(map[string]interface{}), } if frequency != ResetFrequencyNever { rr.autoReset = true rr.calculateNextReset() } return rr, nil } func (rr *ResetRules) Frequency() ResetFrequency { return rr.frequency } func (rr *ResetRules) Action() ResetAction { return rr.action } func (rr *ResetRules) CustomInterval() time.Duration { return rr.customInterval } func (rr *ResetRules) Timezone() string { return rr.timezone } func (rr *ResetRules) PreserveTopN() int32 { return rr.preserveTopN } func (rr *ResetRules) ResetTime() time.Time { return rr.resetTime } func (rr *ResetRules) NextResetAt() time.Time { return rr.nextResetAt } func (rr *ResetRules) AutoReset() bool { return rr.autoReset } func (rr *ResetRules) NotifyBeforeReset() time.Duration { return rr.notifyBeforeReset } func (rr *ResetRules) Metadata() map[string]interface{} { result := make(map[string]interface{}) for k, v := range rr.metadata { result[k] = v } return result } func (rr *ResetRules) SetCustomInterval(interval time.Duration) error { if rr.frequency != ResetFrequencyCustom { return errors.ErrInvalidResetFrequency } if interval <= 0 { return errors.ErrInvalidResetInterval } rr.customInterval = interval rr.calculateNextReset() return nil } func (rr *ResetRules) SetTimezone(timezone string) error { if timezone == "" { return errors.ErrInvalidTimezone } _, err := time.LoadLocation(timezone) if err != nil { return errors.ErrInvalidTimezone } rr.timezone = timezone rr.calculateNextReset() return nil } func (rr *ResetRules) SetPreserveTopN(n int32) error { if n < 0 { return errors.ErrInvalidPreserveCount } rr.preserveTopN = n return nil } func (rr *ResetRules) SetResetTime(resetTime time.Time) error { if resetTime.IsZero() { return errors.ErrInvalidResetTime } rr.resetTime = resetTime rr.calculateNextReset() return nil } func (rr *ResetRules) SetAutoReset(autoReset bool) { rr.autoReset = autoReset if autoReset { rr.calculateNextReset() } } func (rr *ResetRules) SetNotifyBeforeReset(duration time.Duration) error { if duration < 0 { return errors.ErrInvalidNotificationTime } rr.notifyBeforeReset = duration return nil } func (rr *ResetRules) SetMetadata(key string, value interface{}) error { if key == "" { return errors.ErrInvalidMetadata } rr.metadata[key] = value return nil } func (rr *ResetRules) RemoveMetadata(key string) { delete(rr.metadata, key) } func (rr *ResetRules) IsResetDue() bool { if !rr.autoReset || rr.frequency == ResetFrequencyNever { return false } return time.Now().After(rr.nextResetAt) } func (rr *ResetRules) IsNotificationDue() bool { if !rr.autoReset || rr.frequency == ResetFrequencyNever { return false } notificationTime := rr.nextResetAt.Add(-rr.notifyBeforeReset) now := time.Now() return now.After(notificationTime) && now.Before(rr.nextResetAt) } func (rr *ResetRules) TimeUntilReset() time.Duration { if !rr.autoReset || rr.frequency == ResetFrequencyNever { return 0 } return time.Until(rr.nextResetAt) } func (rr *ResetRules) TimeUntilNotification() time.Duration { if !rr.autoReset || rr.frequency == ResetFrequencyNever { return 0 } notificationTime := rr.nextResetAt.Add(-rr.notifyBeforeReset) return time.Until(notificationTime) } func (rr *ResetRules) calculateNextReset() { if !rr.autoReset || rr.frequency == ResetFrequencyNever { return } now := time.Now() loc, _ := time.LoadLocation(rr.timezone) var next time.Time switch rr.frequency { case ResetFrequencyDaily: if rr.resetTime.IsZero() { next = time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, loc).AddDate(0, 0, 1) } else { resetHour, resetMin, resetSec := rr.resetTime.Clock() next = time.Date(now.Year(), now.Month(), now.Day(), resetHour, resetMin, resetSec, 0, loc) if next.Before(now) { next = next.AddDate(0, 0, 1) } } case ResetFrequencyWeekly: daysUntilSunday := (7 - int(now.Weekday())) % 7 if daysUntilSunday == 0 { daysUntilSunday = 7 } next = now.AddDate(0, 0, daysUntilSunday) next = time.Date(next.Year(), next.Month(), next.Day(), 0, 0, 0, 0, loc) case ResetFrequencyMonthly: next = time.Date(now.Year(), now.Month()+1, 1, 0, 0, 0, 0, loc) case ResetFrequencyCustom: if rr.customInterval > 0 { next = now.Add(rr.customInterval) } } rr.nextResetAt = next } func (rr *ResetRules) UpdateFrequency(frequency ResetFrequency) error { if !frequency.IsValid() { return errors.ErrInvalidResetFrequency } rr.frequency = frequency if frequency == ResetFrequencyNever { rr.autoReset = false rr.nextResetAt = time.Time{} } else { rr.autoReset = true rr.calculateNextReset() } return nil } func (rr *ResetRules) UpdateAction(action ResetAction) error { if !action.IsValid() { return errors.ErrInvalidResetAction } rr.action = action return nil } func (rr *ResetRules) String() string { return fmt.Sprintf("ResetRules{Frequency: %s, Action: %s, AutoReset: %t, NextReset: %v}", rr.frequency, rr.action, rr.autoReset, rr.nextResetAt) } type ResetRulesSnapshot struct { Frequency ResetFrequency `json:"frequency"` Action ResetAction `json:"action"` CustomInterval time.Duration `json:"custom_interval"` Timezone string `json:"timezone"` PreserveTopN int32 `json:"preserve_top_n"` ResetTime time.Time `json:"reset_time"` NextResetAt time.Time `json:"next_reset_at"` AutoReset bool `json:"auto_reset"` NotifyBeforeReset time.Duration `json:"notify_before_reset"` Metadata map[string]interface{} `json:"metadata"` } func (rr *ResetRules) ToSnapshot() ResetRulesSnapshot { return ResetRulesSnapshot{ Frequency: rr.frequency, Action: rr.action, CustomInterval: rr.customInterval, Timezone: rr.timezone, PreserveTopN: rr.preserveTopN, ResetTime: rr.resetTime, NextResetAt: rr.nextResetAt, AutoReset: rr.autoReset, NotifyBeforeReset: rr.notifyBeforeReset, Metadata: rr.Metadata(), } } func (rr *ResetRules) FromSnapshot(snapshot ResetRulesSnapshot) error { if !snapshot.Frequency.IsValid() { return errors.ErrInvalidResetFrequency } if !snapshot.Action.IsValid() { return errors.ErrInvalidResetAction } rr.frequency = snapshot.Frequency rr.action = snapshot.Action rr.customInterval = snapshot.CustomInterval rr.timezone = snapshot.Timezone rr.preserveTopN = snapshot.PreserveTopN rr.resetTime = snapshot.ResetTime rr.nextResetAt = snapshot.NextResetAt rr.autoReset = snapshot.AutoReset rr.notifyBeforeReset = snapshot.NotifyBeforeReset rr.metadata = make(map[string]interface{}) for k, v := range snapshot.Metadata { rr.metadata[k] = v } return nil }