// SPDX-License-Identifier: 0BSD
// Copyright (c) 2024-2026 Sudo-Ivan / Quad4.io
package rate

import (
	"sync"
	"time"

	"git.quad4.io/Networks/Reticulum-Go/pkg/common"
)

const (
	DefaultAnnounceRateTarget  = 3600.0 // Default 1 hour between announces
	DefaultAnnounceRateGrace   = 3      // Default number of grace announces
	DefaultAnnounceRatePenalty = 7200.0 // Default 2 hour penalty
	DefaultBurstFreqNew        = 3.5    // Default announces/sec for new interfaces
	DefaultBurstFreq           = 12.0   // Default announces/sec for established interfaces
	DefaultBurstHold           = 60     // Default seconds to hold after burst
	DefaultBurstPenalty        = 300    // Default seconds penalty after burst
	DefaultMaxHeldAnnounces    = 256    // Default max announces in hold queue
	DefaultHeldReleaseInterval = 30     // Default seconds between releasing held announces

	// Allowance thresholds
	AllowanceMinThreshold = 1.0
	AllowanceDecrement    = 1.0

	// History check threshold
	HistoryGraceThreshold = 1
)

type Limiter struct {
	rate       float64
	capacity   float64
	lastUpdate time.Time
	allowance  float64
	mutex      sync.Mutex
}

func NewLimiter(rate float64, capacity float64) *Limiter {
	return &Limiter{
		rate:       rate,
		capacity:   capacity,
		lastUpdate: time.Now(),
		allowance:  capacity,
	}
}

func (l *Limiter) Allow() bool {
	l.mutex.Lock()
	defer l.mutex.Unlock()

	now := time.Now()
	elapsed := now.Sub(l.lastUpdate)
	l.lastUpdate = now

	l.allowance += elapsed.Seconds() * l.rate
	if l.allowance > l.capacity {
		l.allowance = l.capacity
	}

	if l.allowance < AllowanceMinThreshold {
		return false
	}

	l.allowance -= AllowanceDecrement
	return true
}

// AnnounceRateControl handles per-destination announce rate limiting
type AnnounceRateControl struct {
	rateTarget  float64
	rateGrace   int
	ratePenalty float64

	announceHistory map[string][]time.Time // Maps dest hash to announce times
	mutex           sync.RWMutex
}

func NewAnnounceRateControl(target float64, grace int, penalty float64) *AnnounceRateControl {
	return &AnnounceRateControl{
		rateTarget:      target,
		rateGrace:       grace,
		ratePenalty:     penalty,
		announceHistory: make(map[string][]time.Time),
	}
}

func (arc *AnnounceRateControl) AllowAnnounce(destHash string) bool {
	arc.mutex.Lock()
	defer arc.mutex.Unlock()

	history := arc.announceHistory[destHash]
	now := time.Now()

	// Cleanup old history entries
	cutoff := now.Add(-24 * time.Hour)
	newHistory := []time.Time{}
	for _, t := range history {
		if t.After(cutoff) {
			newHistory = append(newHistory, t)
		}
	}
	history = newHistory

	// Allow if within grace period
	if len(history) < arc.rateGrace {
		arc.announceHistory[destHash] = append(history, now)
		return true
	}

	// Check rate
	lastAnnounce := history[len(history)-1]
	waitTime := arc.rateTarget
	if len(history) > arc.rateGrace+HistoryGraceThreshold {
		waitTime += arc.ratePenalty
	}

	if now.Sub(lastAnnounce).Seconds() < waitTime {
		return false
	}

	arc.announceHistory[destHash] = append(history, now)
	return true
}

// IngressControl handles new destination announce rate limiting
type IngressControl struct {
	enabled             bool
	burstFreqNew        float64
	burstFreq           float64
	burstHold           time.Duration
	burstPenalty        time.Duration
	maxHeldAnnounces    int
	heldReleaseInterval time.Duration

	heldAnnounces map[string][]byte // Maps announce hash to announce data
	lastBurst     time.Time
	announceCount int
	mutex         sync.RWMutex
}

func NewIngressControl(enabled bool) *IngressControl {
	return &IngressControl{
		enabled:             enabled,
		burstFreqNew:        DefaultBurstFreqNew,
		burstFreq:           DefaultBurstFreq,
		burstHold:           time.Duration(DefaultBurstHold) * time.Second,
		burstPenalty:        time.Duration(DefaultBurstPenalty) * time.Second,
		maxHeldAnnounces:    DefaultMaxHeldAnnounces,
		heldReleaseInterval: time.Duration(DefaultHeldReleaseInterval) * time.Second,
		heldAnnounces:       make(map[string][]byte),
		lastBurst:           time.Now(),
	}
}

func (ic *IngressControl) ProcessAnnounce(announceHash string, announceData []byte, isNewDest bool) bool {
	if !ic.enabled {
		return true
	}

	ic.mutex.Lock()
	defer ic.mutex.Unlock()

	now := time.Now()
	elapsed := now.Sub(ic.lastBurst)

	// Reset counter if enough time has passed
	if elapsed > ic.burstHold+ic.burstPenalty {
		ic.announceCount = common.ZERO
		ic.lastBurst = now
	}

	// Check burst frequency
	maxFreq := ic.burstFreq
	if isNewDest {
		maxFreq = ic.burstFreqNew
	}

	ic.announceCount++

	// Avoid division by zero and handle very small elapsed times
	seconds := elapsed.Seconds()
	if seconds < 0.01 {
		seconds = 0.01
	}
	burstFreq := float64(ic.announceCount) / seconds

	// Hold announce if burst frequency exceeded
	if burstFreq > maxFreq {
		if len(ic.heldAnnounces) < ic.maxHeldAnnounces {
			ic.heldAnnounces[announceHash] = announceData
		}
		return false
	}

	return true
}

func (ic *IngressControl) ReleaseHeldAnnounce() (string, []byte, bool) {
	ic.mutex.Lock()
	defer ic.mutex.Unlock()

	// Return first held announce if any exist
	for hash, data := range ic.heldAnnounces {
		delete(ic.heldAnnounces, hash)
		return hash, data, true
	}

	return "", nil, false
}
