package account

import (
	"sync"
	"time"
)

// SendLimiter throttles confirm-code sends per recipient address: it enforces a
// minimum cooldown between two sends and a cap over a rolling hour. It guards against
// email bombing and protects the relay's own quota. State is in-memory (per process,
// reset on restart) and keyed by the normalised recipient address, which is adequate
// for the single-instance backend. Safe for concurrent use.
type SendLimiter struct {
	mu       sync.Mutex
	cooldown time.Duration
	perHour  int
	now      func() time.Time
	sends    map[string][]time.Time
}

// NewSendLimiter returns a SendLimiter allowing at most one send per cooldown and at
// most perHour sends over any rolling hour, to the same recipient.
func NewSendLimiter(cooldown time.Duration, perHour int) *SendLimiter {
	return &SendLimiter{
		cooldown: cooldown,
		perHour:  perHour,
		now:      func() time.Time { return time.Now() },
		sends:    make(map[string][]time.Time),
	}
}

// Allow reports whether a send to key is permitted now, recording the send when it is.
// It is denied when the last send was within the cooldown or the rolling-hour cap is
// already reached.
func (l *SendLimiter) Allow(key string) bool {
	l.mu.Lock()
	defer l.mu.Unlock()
	now := l.now()
	cutoff := now.Add(-time.Hour)
	kept := l.sends[key][:0]
	for _, t := range l.sends[key] {
		if t.After(cutoff) {
			kept = append(kept, t)
		}
	}
	if n := len(kept); n > 0 && now.Sub(kept[n-1]) < l.cooldown {
		l.set(key, kept)
		return false
	}
	if len(kept) >= l.perHour {
		l.set(key, kept)
		return false
	}
	l.set(key, append(kept, now))
	return true
}

// set stores the retained send times for key, dropping the entry entirely once empty
// so the map stays bounded to recipients active within the last hour.
func (l *SendLimiter) set(key string, times []time.Time) {
	if len(times) == 0 {
		delete(l.sends, key)
		return
	}
	l.sends[key] = times
}
