package bridge

import (
	"net"
	"sync"
	"time"
)

// rateLimitedConn limits read/write throughput to bytesPerSec (simple token bucket).
type rateLimitedConn struct {
	net.Conn
	bytesPerSec int64
	mu          sync.Mutex
	allowance   float64
	lastCheck   time.Time
}

func newRateLimitedConn(conn net.Conn, bytesPerSec int64) net.Conn {
	if bytesPerSec <= 0 {
		return conn
	}
	return &rateLimitedConn{
		Conn:        conn,
		bytesPerSec: bytesPerSec,
		lastCheck:   time.Now(),
		allowance:   float64(bytesPerSec),
	}
}

func (c *rateLimitedConn) wait(n int) {
	if c.bytesPerSec <= 0 || n <= 0 {
		return
	}
	c.mu.Lock()
	defer c.mu.Unlock()
	now := time.Now()
	elapsed := now.Sub(c.lastCheck).Seconds()
	c.lastCheck = now
	c.allowance += elapsed * float64(c.bytesPerSec)
	if c.allowance > float64(c.bytesPerSec)*2 {
		c.allowance = float64(c.bytesPerSec) * 2
	}
	for float64(n) > c.allowance {
		deficit := float64(n) - c.allowance
		sleep := time.Duration(deficit/float64(c.bytesPerSec)*1e9) * time.Nanosecond
		if sleep < time.Millisecond {
			sleep = time.Millisecond
		}
		c.mu.Unlock()
		time.Sleep(sleep)
		c.mu.Lock()
		now = time.Now()
		elapsed = now.Sub(c.lastCheck).Seconds()
		c.lastCheck = now
		c.allowance += elapsed * float64(c.bytesPerSec)
		if c.allowance > float64(c.bytesPerSec)*2 {
			c.allowance = float64(c.bytesPerSec) * 2
		}
	}
	c.allowance -= float64(n)
}

func (c *rateLimitedConn) Read(b []byte) (int, error) {
	n, err := c.Conn.Read(b)
	if n > 0 {
		c.wait(n)
	}
	return n, err
}

func (c *rateLimitedConn) Write(b []byte) (int, error) {
	c.wait(len(b))
	return c.Conn.Write(b)
}
