package auth

import (
	"net"
	"sort"
	"strings"
	"sync"
	"time"
)

type AccessSnapshot struct {
	KeyID          string    `json:"key_id"`
	LastPath       string    `json:"last_path"`
	RemoteAddr     string    `json:"remote_addr"`
	UserAgent      string    `json:"user_agent"`
	RequestCount   int       `json:"request_count"`
	LastAccessedAt time.Time `json:"last_accessed_at"`
}

type AccessTracker struct {
	mu            sync.RWMutex
	entries       map[string]AccessSnapshot
	ipCounts      map[string]int
	agentCounts   map[string]int
	toolCounts    map[string]int
	totalRequests int
}

func NewAccessTracker() *AccessTracker {
	return &AccessTracker{
		entries:     make(map[string]AccessSnapshot),
		ipCounts:    make(map[string]int),
		agentCounts: make(map[string]int),
		toolCounts:  make(map[string]int),
	}
}

func (t *AccessTracker) Record(keyID, path, remoteAddr, userAgent, toolName string, now time.Time) {
	if t == nil || keyID == "" {
		return
	}

	t.mu.Lock()
	defer t.mu.Unlock()

	normalizedRemoteAddr := normalizeRemoteAddr(remoteAddr)

	entry := t.entries[keyID]
	entry.KeyID = keyID
	entry.LastPath = path
	entry.RemoteAddr = normalizedRemoteAddr
	entry.UserAgent = userAgent
	entry.LastAccessedAt = now.UTC()
	entry.RequestCount++
	t.entries[keyID] = entry
	t.totalRequests++

	if normalizedRemoteAddr != "" {
		t.ipCounts[normalizedRemoteAddr]++
	}
	if userAgent != "" {
		t.agentCounts[userAgent]++
	}
	if tool := strings.TrimSpace(toolName); tool != "" {
		t.toolCounts[tool]++
	}
}

func normalizeRemoteAddr(value string) string {
	trimmed := strings.TrimSpace(value)
	if trimmed == "" {
		return ""
	}
	host, _, err := net.SplitHostPort(trimmed)
	if err == nil {
		return host
	}
	return trimmed
}

func (t *AccessTracker) Snapshot() []AccessSnapshot {
	if t == nil {
		return nil
	}

	t.mu.RLock()
	defer t.mu.RUnlock()

	items := make([]AccessSnapshot, 0, len(t.entries))
	for _, entry := range t.entries {
		items = append(items, entry)
	}

	sort.Slice(items, func(i, j int) bool {
		return items[i].LastAccessedAt.After(items[j].LastAccessedAt)
	})

	return items
}

func (t *AccessTracker) ConnectedCount(now time.Time, window time.Duration) int {
	if t == nil {
		return 0
	}

	cutoff := now.UTC().Add(-window)
	t.mu.RLock()
	defer t.mu.RUnlock()

	count := 0
	for _, entry := range t.entries {
		if !entry.LastAccessedAt.Before(cutoff) {
			count++
		}
	}
	return count
}

type RequestAggregate struct {
	Key          string `json:"key"`
	RequestCount int    `json:"request_count"`
}

type AccessMetrics struct {
	TotalRequests    int                `json:"total_requests"`
	UniquePrincipals int                `json:"unique_principals"`
	UniqueIPs        int                `json:"unique_ips"`
	UniqueAgents     int                `json:"unique_agents"`
	UniqueTools      int                `json:"unique_tools"`
	TopIPs           []RequestAggregate `json:"top_ips"`
	TopAgents        []RequestAggregate `json:"top_agents"`
	TopTools         []RequestAggregate `json:"top_tools"`
}

func (t *AccessTracker) Metrics(topN int) AccessMetrics {
	if t == nil {
		return AccessMetrics{}
	}
	if topN <= 0 {
		topN = 10
	}

	t.mu.RLock()
	defer t.mu.RUnlock()

	return AccessMetrics{
		TotalRequests:    t.totalRequests,
		UniquePrincipals: len(t.entries),
		UniqueIPs:        len(t.ipCounts),
		UniqueAgents:     len(t.agentCounts),
		UniqueTools:      len(t.toolCounts),
		TopIPs:           topAggregates(t.ipCounts, topN),
		TopAgents:        topAggregates(t.agentCounts, topN),
		TopTools:         topAggregates(t.toolCounts, topN),
	}
}

func topAggregates(items map[string]int, topN int) []RequestAggregate {
	out := make([]RequestAggregate, 0, len(items))
	for key, count := range items {
		out = append(out, RequestAggregate{Key: key, RequestCount: count})
	}
	sort.Slice(out, func(i, j int) bool {
		if out[i].RequestCount == out[j].RequestCount {
			return out[i].Key < out[j].Key
		}
		return out[i].RequestCount > out[j].RequestCount
	})
	if len(out) > topN {
		out = out[:topN]
	}
	return out
}
