package p2p

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

// PeerRanker implements smart peer selection and load balancing
type PeerRanker struct {
	maxPeersToReturn int
	preferLocal      bool
	geoIP           *GeoIPDatabase
	peerHistory     map[string]*PeerQuality
	qualityMutex    sync.RWMutex
}

// PeerQuality tracks peer performance history
type PeerQuality struct {
	SuccessfulConnections int64
	FailedConnections     int64
	AverageSpeed          float64 // bytes/sec
	LastConnected         time.Time
	ReputationScore       float64 // 0.0 - 1.0
}

// GeoIPDatabase simulates geographic IP lookup
type GeoIPDatabase struct {
	// In production, this would be a real GeoIP database
	enabled bool
}

// NewPeerRanker creates a new peer ranking system
func NewPeerRanker(maxPeers int, preferLocal bool) *PeerRanker {
	return &PeerRanker{
		maxPeersToReturn: maxPeers,
		preferLocal:      preferLocal,
		geoIP:           &GeoIPDatabase{enabled: false}, // Disabled for now
		peerHistory:     make(map[string]*PeerQuality),
	}
}

// RankPeers intelligently ranks and selects best peers for a client
func (pr *PeerRanker) RankPeers(peers []PeerInfo, clientIP string) []PeerInfo {
	if len(peers) == 0 {
		return peers
	}
	
	// Calculate scores for each peer
	type scoredPeer struct {
		peer  PeerInfo
		score float64
	}
	
	var scored []scoredPeer
	clientCountry := pr.getCountryCode(clientIP)
	
	for _, peer := range peers {
		score := pr.calculatePeerScore(peer, clientIP, clientCountry)
		scored = append(scored, scoredPeer{peer: peer, score: score})
	}
	
	// Sort by score (highest first)
	sort.Slice(scored, func(i, j int) bool {
		return scored[i].score > scored[j].score
	})
	
	// Return top peers up to limit
	result := make([]PeerInfo, 0, pr.maxPeersToReturn)
	for i, sp := range scored {
		if i >= pr.maxPeersToReturn {
			break
		}
		result = append(result, sp.peer)
	}
	
	return result
}

// calculatePeerScore computes a comprehensive score for peer ranking
func (pr *PeerRanker) calculatePeerScore(peer PeerInfo, clientIP, clientCountry string) float64 {
	score := float64(peer.Quality) // Base quality from source
	
	// 1. WebSeed gets highest priority (always first)
	if peer.Source == "webseed" {
		return 1000.0 // Always highest score
	}
	
	// 2. Geographic proximity bonus
	if pr.preferLocal && pr.geoIP.enabled {
		peerCountry := pr.getCountryCode(peer.IP)
		if peerCountry == clientCountry {
			score += 50.0 // Local peers get significant boost
		} else if pr.isSameContinent(clientCountry, peerCountry) {
			score += 20.0 // Same continent gets smaller boost
		}
	}
	
	// 3. Network proximity (same subnet bonus)
	if pr.isSameSubnet(clientIP, peer.IP) {
		score += 30.0
	}
	
	// 4. Peer history and reputation
	pr.qualityMutex.RLock()
	if quality, exists := pr.peerHistory[pr.peerKey(peer)]; exists {
		// Factor in success rate
		if quality.SuccessfulConnections+quality.FailedConnections > 0 {
			successRate := float64(quality.SuccessfulConnections) / 
				float64(quality.SuccessfulConnections+quality.FailedConnections)
			score += successRate * 40.0 // Up to 40 point bonus for reliable peers
		}
		
		// Factor in speed history
		if quality.AverageSpeed > 0 {
			// Bonus for fast peers (normalized, max 20 points)
			speedBonus := math.Min(quality.AverageSpeed/1024/1024, 20.0) // MB/s -> points
			score += speedBonus
		}
		
		// Reputation score
		score += quality.ReputationScore * 25.0
		
		// Recency bonus - prefer recently seen peers
		recencyHours := time.Since(quality.LastConnected).Hours()
		if recencyHours < 1 {
			score += 15.0 // Very recent
		} else if recencyHours < 24 {
			score += 10.0 // Recent
		}
	}
	pr.qualityMutex.RUnlock()
	
	// 5. Port analysis (avoid suspicious ports)
	if pr.isSuspiciousPort(peer.Port) {
		score -= 20.0
	}
	
	// 6. Ensure minimum score for valid peers
	if score < 1.0 {
		score = 1.0
	}
	
	return score
}

// UpdatePeerQuality updates peer performance history
func (pr *PeerRanker) UpdatePeerQuality(peer PeerInfo, success bool, speed float64) {
	pr.qualityMutex.Lock()
	defer pr.qualityMutex.Unlock()
	
	key := pr.peerKey(peer)
	quality, exists := pr.peerHistory[key]
	if !exists {
		quality = &PeerQuality{
			ReputationScore: 0.5, // Start with neutral reputation
		}
		pr.peerHistory[key] = quality
	}
	
	// Update connection statistics
	if success {
		quality.SuccessfulConnections++
		// Boost reputation for successful connections
		quality.ReputationScore = math.Min(1.0, quality.ReputationScore+0.1)
	} else {
		quality.FailedConnections++
		// Decrease reputation for failed connections
		quality.ReputationScore = math.Max(0.0, quality.ReputationScore-0.2)
	}
	
	// Update speed (exponential moving average)
	if speed > 0 {
		if quality.AverageSpeed == 0 {
			quality.AverageSpeed = speed
		} else {
			// 80% old speed, 20% new speed
			quality.AverageSpeed = quality.AverageSpeed*0.8 + speed*0.2
		}
	}
	
	quality.LastConnected = time.Now()
}

// Helper methods for peer analysis
func (pr *PeerRanker) peerKey(peer PeerInfo) string {
	return peer.IP + ":" + string(rune(peer.Port))
}

func (pr *PeerRanker) getCountryCode(ip string) string {
	if !pr.geoIP.enabled {
		return "Unknown"
	}
	// In production, this would query a real GeoIP database
	// For now, simulate based on IP ranges
	if strings.HasPrefix(ip, "192.168.") || strings.HasPrefix(ip, "10.") {
		return "Local"
	}
	return "Unknown"
}

func (pr *PeerRanker) isSameContinent(country1, country2 string) bool {
	// Simplified continent mapping
	continentMap := map[string]string{
		"US": "NA", "CA": "NA", "MX": "NA",
		"GB": "EU", "DE": "EU", "FR": "EU",
		"JP": "AS", "CN": "AS", "IN": "AS",
	}
	
	return continentMap[country1] == continentMap[country2]
}

func (pr *PeerRanker) isSameSubnet(ip1, ip2 string) bool {
	// Parse IPs and check if they're in same /24 subnet
	parsedIP1 := net.ParseIP(ip1)
	parsedIP2 := net.ParseIP(ip2)
	
	if parsedIP1 == nil || parsedIP2 == nil {
		return false
	}
	
	// Create /24 subnet mask
	mask := net.CIDRMask(24, 32)
	return parsedIP1.Mask(mask).Equal(parsedIP2.Mask(mask))
}

func (pr *PeerRanker) isSuspiciousPort(port int) bool {
	// Flag potentially suspicious ports
	suspiciousPorts := map[int]bool{
		22:   true, // SSH
		23:   true, // Telnet
		25:   true, // SMTP
		53:   true, // DNS
		80:   true, // HTTP (could be misconfigured server)
		443:  true, // HTTPS (could be misconfigured server)
		3389: true, // RDP
	}
	
	// Also flag ports < 1024 (privileged ports are suspicious for P2P)
	return suspiciousPorts[port] || port < 1024
}

// CleanupStaleEntries removes old peer quality data
func (pr *PeerRanker) CleanupStaleEntries() {
	pr.qualityMutex.Lock()
	defer pr.qualityMutex.Unlock()
	
	cutoff := time.Now().Add(-7 * 24 * time.Hour) // Remove data older than 7 days
	
	for key, quality := range pr.peerHistory {
		if quality.LastConnected.Before(cutoff) {
			delete(pr.peerHistory, key)
		}
	}
}