// Package netmon provides network interface monitoring with historical data
package netmon

import (
	"context"
	"log/slog"
	"strings"
	"sync"
	"time"

	"github.com/dustin/go-humanize"
	psnet "github.com/shirou/gopsutil/v3/net"
)

const (
	ringBufferSize    = 60 // Keep 60 seconds of history
	sampleInterval    = time.Second
	rateWindowSeconds = 5 // Window size for rate calculation
	bitsPerByte       = 8
)

// Sample represents a single point-in-time network sample
type Sample struct {
	BytesSent uint64
	BytesRecv uint64
	Timestamp time.Time
}

// InterfaceStats holds the ring buffer for a single interface
type InterfaceStats struct {
	samples    [ringBufferSize]Sample
	head       int // Points to the oldest sample
	count      int // Number of valid samples
	lastSample Sample
}

// Stats represents current stats for an interface
type Stats struct {
	Name         string
	BytesSent    uint64
	BytesRecv    uint64
	BitsSentRate uint64 // bits per second
	BitsRecvRate uint64 // bits per second
}

// FormatSentRate returns the send rate as a human-readable string
func (s *Stats) FormatSentRate() string {
	return humanize.SI(float64(s.BitsSentRate), "bit/s")
}

// FormatRecvRate returns the receive rate as a human-readable string
func (s *Stats) FormatRecvRate() string {
	return humanize.SI(float64(s.BitsRecvRate), "bit/s")
}

// Monitor tracks network statistics for all interfaces
type Monitor struct {
	mu         sync.RWMutex
	interfaces map[string]*InterfaceStats
	logger     *slog.Logger
	ctx        context.Context
	cancel     context.CancelFunc
	wg         sync.WaitGroup
}

// New creates a new network monitor
func New(logger *slog.Logger) *Monitor {
	ctx, cancel := context.WithCancel(context.Background())
	m := &Monitor{
		interfaces: make(map[string]*InterfaceStats),
		logger:     logger,
		ctx:        ctx,
		cancel:     cancel,
	}
	return m
}

// Start begins monitoring network interfaces
func (m *Monitor) Start() {
	m.wg.Add(1)
	go m.monitorLoop()
}

// Stop stops the monitor
func (m *Monitor) Stop() {
	m.cancel()
	m.wg.Wait()
}

// GetStats returns current stats for all interfaces
func (m *Monitor) GetStats() []Stats {
	m.mu.RLock()
	defer m.mu.RUnlock()

	var stats []Stats
	for name, ifaceStats := range m.interfaces {
		// Skip interfaces with no samples
		if ifaceStats.count == 0 {
			continue
		}

		// Calculate rates over available samples (up to 5 seconds)
		rate := m.calculateRate(ifaceStats, rateWindowSeconds)

		stats = append(stats, Stats{
			Name:         name,
			BytesSent:    ifaceStats.lastSample.BytesSent,
			BytesRecv:    ifaceStats.lastSample.BytesRecv,
			BitsSentRate: uint64(rate.sentRate * bitsPerByte), // Convert to bits/sec
			BitsRecvRate: uint64(rate.recvRate * bitsPerByte), // Convert to bits/sec
		})
	}

	return stats
}

// GetStatsForInterface returns stats for a specific interface
func (m *Monitor) GetStatsForInterface(name string) (*Stats, bool) {
	m.mu.RLock()
	defer m.mu.RUnlock()

	ifaceStats, ok := m.interfaces[name]
	if !ok || ifaceStats.count == 0 {
		return nil, false
	}

	rate := m.calculateRate(ifaceStats, 5)

	return &Stats{
		Name:         name,
		BytesSent:    ifaceStats.lastSample.BytesSent,
		BytesRecv:    ifaceStats.lastSample.BytesRecv,
		BitsSentRate: uint64(rate.sentRate * 8), // Convert to bits/sec
		BitsRecvRate: uint64(rate.recvRate * 8), // Convert to bits/sec
	}, true
}

type rateInfo struct {
	sentRate float64 // bytes per second
	recvRate float64 // bytes per second
}

// calculateRate calculates the average rate over the last n seconds
func (m *Monitor) calculateRate(ifaceStats *InterfaceStats, seconds int) rateInfo {
	if ifaceStats.count <= 1 {
		return rateInfo{}
	}

	// Determine how many samples to use (up to requested seconds)
	samplesToUse := seconds
	if samplesToUse > ifaceStats.count-1 {
		samplesToUse = ifaceStats.count - 1
	}
	if samplesToUse > ringBufferSize-1 {
		samplesToUse = ringBufferSize - 1
	}

	// Get the most recent sample
	newestIdx := (ifaceStats.head + ifaceStats.count - 1) % ringBufferSize
	newest := ifaceStats.samples[newestIdx]

	// Get the sample from n seconds ago
	oldestIdx := (ifaceStats.head + ifaceStats.count - 1 - samplesToUse) % ringBufferSize
	oldest := ifaceStats.samples[oldestIdx]

	// Calculate time difference
	timeDiff := newest.Timestamp.Sub(oldest.Timestamp).Seconds()
	if timeDiff <= 0 {
		return rateInfo{}
	}

	// Calculate rates
	bytesSentDiff := float64(newest.BytesSent - oldest.BytesSent)
	bytesRecvDiff := float64(newest.BytesRecv - oldest.BytesRecv)

	return rateInfo{
		sentRate: bytesSentDiff / timeDiff,
		recvRate: bytesRecvDiff / timeDiff,
	}
}

// monitorLoop continuously samples network statistics
func (m *Monitor) monitorLoop() {
	defer m.wg.Done()

	ticker := time.NewTicker(sampleInterval)
	defer ticker.Stop()

	// Take initial sample
	m.takeSample()

	for {
		select {
		case <-m.ctx.Done():
			return
		case <-ticker.C:
			m.takeSample()
		}
	}
}

// takeSample captures current network statistics
func (m *Monitor) takeSample() {
	counters, err := psnet.IOCounters(true)
	if err != nil {
		m.logger.Warn("failed to get network counters", "error", err)
		return
	}

	m.mu.Lock()
	defer m.mu.Unlock()

	now := time.Now()
	currentInterfaces := make(map[string]bool)

	for _, counter := range counters {
		// Skip loopback and docker interfaces
		if counter.Name == "lo" || strings.HasPrefix(counter.Name, "docker") {
			continue
		}

		currentInterfaces[counter.Name] = true

		// Get or create interface stats
		ifaceStats, exists := m.interfaces[counter.Name]
		if !exists {
			ifaceStats = &InterfaceStats{}
			m.interfaces[counter.Name] = ifaceStats
		}

		// Create new sample
		sample := Sample{
			BytesSent: counter.BytesSent,
			BytesRecv: counter.BytesRecv,
			Timestamp: now,
		}

		// Add to ring buffer
		if ifaceStats.count < ringBufferSize {
			// Buffer not full yet
			idx := ifaceStats.count
			ifaceStats.samples[idx] = sample
			ifaceStats.count++
		} else {
			// Buffer is full, overwrite oldest
			ifaceStats.samples[ifaceStats.head] = sample
			ifaceStats.head = (ifaceStats.head + 1) % ringBufferSize
		}

		ifaceStats.lastSample = sample
	}

	// Remove interfaces that no longer exist
	for name := range m.interfaces {
		if !currentInterfaces[name] {
			delete(m.interfaces, name)
		}
	}
}
