package k8snet

import "fmt"

// Conntrack models the kernel connection-tracking table (netfilter/NAT),
// a FINITE resource a busy gateway can exhaust. Each tracked flow takes a
// slot; over Max, new flows are dropped (the cryptic
// "nf_conntrack: table full, dropping packet" incident).
type Conntrack struct {
	Max     int
	entries map[string]bool
	Drops   int
}

func NewConntrack(max int) *Conntrack {
	return &Conntrack{Max: max, entries: map[string]bool{}}
}

// Track records a flow (a 4-tuple key). Reusing an existing flow is free;
// a NEW flow consumes a slot and is dropped if the table is full.
func (c *Conntrack) Track(flowKey string) bool {
	if c.entries[flowKey] {
		return true // existing flow: no new slot (keep-alive / pooling)
	}
	if len(c.entries) >= c.Max {
		c.Drops++
		return false // table full -> packet dropped
	}
	c.entries[flowKey] = true
	return true
}

// Count returns the number of tracked flows.
func (c *Conntrack) Count() int { return len(c.entries) }

// SimulateConntrack runs `requests` through a conntrack table of size
// `maxEntries`. With churn, every request is a NEW flow (a fresh
// connection) and the table exhausts; with keep-alive/pooling (reuse),
// all requests ride ONE flow and the table never fills. Returns the
// number of dropped requests.
func SimulateConntrack(maxEntries, requests int, reuse bool) int {
	ct := NewConntrack(maxEntries)
	for i := 0; i < requests; i++ {
		key := "10.0.0.1:443->origin"
		if !reuse {
			key = fmt.Sprintf("10.0.0.1:%d->origin", 20000+i) // unique ephemeral port per request
		}
		ct.Track(key)
	}
	return ct.Drops
}
