package firewall

import (
	"strings"
	"sync"
	"sync/atomic"
	"time"

	"reverse-proxy-ddos/config"

	"github.com/valyala/fasthttp"
)

type rpsTracker struct {
	requestCount  int64
	lastCheckTime time.Time
}

type requestTracker struct {
	requestCount      map[string]*int64
	flaggedUACount    map[string]*int64
	resetTimers       map[string]*time.Timer
	uaResetTimers     map[string]*time.Timer
	previousUserAgent map[string]uaChangeInfo
	whitelist         map[string]time.Time
	challenges        map[string]time.Time
	blacklist         map[string]time.Time
	blacklistDuration time.Duration
	challengeFailures map[string]int
	whitelistDuration time.Duration
	rpsLimit          int64
	zonerpstracker    *rpsTracker
	requestCounts     []int64
	uamActive         bool
	uamExpiration     time.Time
	uamThreshold      float64
	permUAM           int64
	flaggedUAs        [][]interface{}
	staticWhitelist   map[string]bool
	mu                sync.RWMutex
}

type Firewall struct {
	backendTrackers map[string]*requestTracker
	cfg             *config.Config
}

// -*-*-*-*-*-*-*-*-*-*
// | Helper Functions |
// -*-*-*-*-*-*-*-*-*-*

func (f *Firewall) AddToWhitelist(ctx *fasthttp.RequestCtx) {
	tracker := f.backendTrackers[string(ctx.Request.Host())]
	ip := string(ctx.RemoteAddr().String())
	if backend, exists := f.cfg.BackendMap[string(ctx.Request.Host())]; exists && backend.Cloudflare {
		ip = string(ctx.Request.Header.Peek("X-Forwarded-For"))
	}

	tracker.mu.Lock()
	defer tracker.mu.Unlock()

	tracker.whitelist[ip] = time.Now().Add(tracker.whitelistDuration)
}

func IsChallengeRequest(ctx *fasthttp.RequestCtx) bool {
	path := string(ctx.Path())
	return strings.HasPrefix(path, "/rproxy/") && strings.HasSuffix(path, ".js")
}

func (f *Firewall) CleanupExpiredWhitelistEntries() {
	for _, tracker := range f.backendTrackers {
		tracker.mu.Lock()
		for ip, expiration := range tracker.whitelist {
			if time.Now().After(expiration) {
				delete(tracker.whitelist, ip)
			}
		}
		tracker.mu.Unlock()
	}
}

func (f *Firewall) CleanupExpiredBlacklistEntries() {
	for _, tracker := range f.backendTrackers {
		tracker.mu.Lock()

		for ip, expiration := range tracker.blacklist {
			if time.Now().After(expiration) {
				delete(tracker.blacklist, ip)
			}
		}

		tracker.mu.Unlock()
	}
}

func NewHandler(cfg *config.Config) *Firewall {
	backendTrackers := make(map[string]*requestTracker)
	for host := range cfg.BackendMap {
		staticWhitelist := make(map[string]bool)
		for _, ip := range cfg.BackendMap[host].StaticWhitelist {
			staticWhitelist[ip] = true
		}
		backendTrackers[host] = &requestTracker{
			requestCount:      make(map[string]*int64),
			flaggedUACount:    make(map[string]*int64),
			resetTimers:       make(map[string]*time.Timer),
			uaResetTimers:     make(map[string]*time.Timer),
			previousUserAgent: make(map[string]uaChangeInfo),
			whitelist:         make(map[string]time.Time),
			challenges:        make(map[string]time.Time),
			blacklist:         make(map[string]time.Time),
			challengeFailures: make(map[string]int),
			whitelistDuration: time.Duration(cfg.BackendMap[host].WhitelistDuration) * time.Second,
			blacklistDuration: time.Duration(cfg.BackendMap[host].BlacklistDuration) * time.Second,
			rpsLimit:          int64(cfg.BackendMap[host].ForceIntrusive),
			uamThreshold:      float64(cfg.BackendMap[host].UAMThreshold),
			permUAM:           int64(cfg.BackendMap[host].PermUAM),
			flaggedUAs:        cfg.BackendMap[host].FlaggedUAs,
			staticWhitelist:   staticWhitelist,
		}
	}
	return &Firewall{
		backendTrackers: backendTrackers,
		cfg:             cfg,
	}
}

func (f *Firewall) FilterTraffic(ctx *fasthttp.RequestCtx) int {
	backend, exists := f.cfg.BackendMap[string(ctx.Request.Host())]
	if !exists {
		// RC502 = Unknown host, not configured in config.json
		return 502
	}

	ip := string(ctx.RemoteAddr().String())
	if backend.Cloudflare {
		ip = string(ctx.Request.Header.Peek("X-Forwarded-For"))
	}
	tracker := f.backendTrackers[string(ctx.Request.Host())]

	tracker.mu.Lock()
	defer tracker.mu.Unlock()

	if expiration, blacklisted := tracker.blacklist[ip]; blacklisted {
		if time.Now().Before(expiration) {
			return 0
		} else {
			delete(tracker.blacklist, ip)
		}
	}

	if tracker.staticWhitelist[ip] {
		return 1
	}

	if _, exists := tracker.requestCount[ip]; exists {
		if atomic.LoadInt64(tracker.requestCount[ip]) > int64(backend.Ratelimit) && backend.Ratelimit != 0 {
			// RC429 = Ratelimit, block request
			return 429
		}
	}

	// IP is whitelisted, allow the request
	if _, whitelisted := tracker.whitelist[ip]; whitelisted {
		return 1
	}

	if !IsChallengeRequest(ctx) {
		if tracker.permUAM == 0 {
			var rpsTr *rpsTracker
			if tracker.zonerpstracker == nil {
				rpsTr = &rpsTracker{
					requestCount:  0,
					lastCheckTime: time.Now(),
				}
				tracker.zonerpstracker = rpsTr
			} else {
				rpsTr = tracker.zonerpstracker
				elapsed := time.Since(rpsTr.lastCheckTime).Seconds()
				if elapsed >= 1 {
					tracker.requestCounts = append(tracker.requestCounts, rpsTr.requestCount)
					if len(tracker.requestCounts) > 2 {
						tracker.requestCounts = tracker.requestCounts[1:]
					}

					rpsTr.requestCount = 0
					rpsTr.lastCheckTime = time.Now()
				}
			}
			rpsTr.requestCount++
		}

		if _, exists := tracker.requestCount[ip]; !exists {
			var count int64
			tracker.requestCount[ip] = &count
		}
		count := atomic.AddInt64(tracker.requestCount[ip], 1)
		if count > int64(backend.Ratelimit) && backend.Ratelimit != 0 {
			// RC429 = Ratelimit, block request
			return 429
		}
		if _, exists := tracker.resetTimers[ip]; !exists {
			tracker.resetTimers[ip] = time.AfterFunc(time.Duration(backend.RateInter)*time.Second, func() {
				f.resetRequestCount(tracker, ip)
			})
		}

		if tracker.permUAM == 0 {
			rpsTr := tracker.zonerpstracker
			if rpsTr.requestCount > tracker.rpsLimit {
				if tracker.challengeFailures[ip] >= backend.ChallengeFailure {
					tracker.blacklist[ip] = time.Now().Add(tracker.blacklistDuration)
					return 0
				}
				tracker.challengeFailures[ip]++
				return 1002
			}

			if len(tracker.requestCounts) > 0 {
				sum := int64(0)
				for _, count := range tracker.requestCounts {
					sum += count
				}
				sum += tracker.zonerpstracker.requestCount
				averageRPS := sum / 2
				if !tracker.uamActive {
					if averageRPS > tracker.rpsLimit {
						tracker.uamActive = true
						tracker.uamExpiration = time.Now().Add(60 * time.Second)
					}
				} else if time.Since(tracker.uamExpiration) >= time.Duration(5)*time.Second {
					if time.Now().After(tracker.uamExpiration) {
						tracker.uamActive = false
					} else {
						if averageRPS > tracker.rpsLimit*2 {
							tracker.uamExpiration = time.Now().Add(60 * time.Second)
						}
					}
				}
			}
		}
	}

	if tracker.uamActive {
		if tracker.challengeFailures[ip] >= backend.ChallengeFailure {
			tracker.blacklist[ip] = time.Now().Add(tracker.blacklistDuration)
			return 0
		}
		tracker.challengeFailures[ip]++
		return 1002
	}

	if tracker.permUAM == 1 {
		if tracker.challengeFailures[ip] >= backend.ChallengeFailure {
			tracker.blacklist[ip] = time.Now().Add(tracker.blacklistDuration)
			return 0
		}
		tracker.challengeFailures[ip]++
		return 1002
	}

	// -*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-
	// |  Mitigation Stage 1 | Flagged UA  |
	// -*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-

	if res := f.flaggedUAStage(tracker, ctx); res != 1 {
		if tracker.challengeFailures[ip] >= backend.ChallengeFailure {
			tracker.blacklist[ip] = time.Now().Add(tracker.blacklistDuration)
			return 0
		}
		tracker.challengeFailures[ip]++
		return res
	}

	// -*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-
	// |  Mitigation Stage 2 | UA Changes  |
	// -*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-

	if res := f.uaChangeStage(tracker, ip, string(ctx.Request.Header.Peek("User-Agent")), backend.UaChangeLimit); res != 1 {
		if tracker.challengeFailures[ip] >= backend.ChallengeFailure {
			tracker.blacklist[ip] = time.Now().Add(tracker.blacklistDuration)
			return 0
		}
		tracker.challengeFailures[ip]++
		return res
	}

	// Has passed all checks but it is not in the whitelist, do the embedded JS challenge.
	if tracker.challengeFailures[ip] >= backend.ChallengeFailure {
		tracker.blacklist[ip] = time.Now().Add(tracker.blacklistDuration)
		return 0
	}
	tracker.challengeFailures[ip]++
	return 1001
}


This is my firewall.go code, when it is around 2,000 RPS, it starts to very much so lag for new requests/starts to stutter for old ones, how can I fix this?
This is called by main.go whenever a request happens.