// client_network_handler.go - Handles clients with multiple network interfaces
// This is the CORRECT implementation focusing on CLIENT multi-interface support

package main

import (
	"fmt"
	"net"
	"net/http"
	"strings"
	"sync"
	"time"
)

// ClientConnectionTracker manages clients that switch between network interfaces
type ClientConnectionTracker struct {
	sessions    map[string]*ClientSession  // sessionID -> session info
	ipToSession map[string]string          // IP -> sessionID for quick lookup
	mutex       sync.RWMutex
	config      *ClientNetworkConfig
}

// ClientSession represents a client that may connect from multiple IPs/interfaces
type ClientSession struct {
	SessionID    string
	ClientIPs    []string          // All IPs this session has used
	ConnectionType string          // mobile, wifi, ethernet, unknown
	LastSeen     time.Time
	UploadInfo   *UploadSessionInfo
	NetworkQuality float64         // 0-100 quality score
	mutex        sync.RWMutex
}

// UploadSessionInfo tracks upload progress across network switches
type UploadSessionInfo struct {
	FileName       string
	TotalSize      int64
	UploadedBytes  int64
	ChunkSize      int64
	LastChunkID    int
	Chunks         map[int]bool  // chunkID -> received
	Started        time.Time
	LastActivity   time.Time
}

// ClientNetworkConfig holds configuration for client network handling
type ClientNetworkConfig struct {
	SessionBasedTracking      bool          `toml:"session_based_tracking" mapstructure:"session_based_tracking"`
	AllowIPChanges           bool          `toml:"allow_ip_changes" mapstructure:"allow_ip_changes"`
	SessionMigrationTimeout  time.Duration // Will be parsed from string in main.go
	MaxIPChangesPerSession   int           `toml:"max_ip_changes_per_session" mapstructure:"max_ip_changes_per_session"`
	ClientConnectionDetection bool         `toml:"client_connection_detection" mapstructure:"client_connection_detection"`
	AdaptToClientNetwork     bool          `toml:"adapt_to_client_network" mapstructure:"adapt_to_client_network"`
}

// ConnectionType represents different client connection types
type ConnectionType int

const (
	ConnectionUnknown ConnectionType = iota
	ConnectionMobile   // LTE/5G
	ConnectionWiFi     // WiFi
	ConnectionEthernet // Wired
)

func (ct ConnectionType) String() string {
	switch ct {
	case ConnectionMobile:
		return "mobile"
	case ConnectionWiFi:
		return "wifi"
	case ConnectionEthernet:
		return "ethernet"
	default:
		return "unknown"
	}
}

// NewClientConnectionTracker creates a new tracker for multi-interface clients
func NewClientConnectionTracker(config *ClientNetworkConfig) *ClientConnectionTracker {
	return &ClientConnectionTracker{
		sessions:    make(map[string]*ClientSession),
		ipToSession: make(map[string]string),
		config:      config,
	}
}

// DetectClientConnectionType analyzes the request to determine client connection type
func (cct *ClientConnectionTracker) DetectClientConnectionType(r *http.Request) string {
	// Check User-Agent for mobile indicators
	userAgent := strings.ToLower(r.Header.Get("User-Agent"))
	
	// Mobile detection
	if containsAny(userAgent, "mobile", "android", "iphone", "ipad", "phone") {
		return "mobile"
	}
	
	// Check for specific network indicators in headers
	if xForwardedFor := r.Header.Get("X-Forwarded-For"); xForwardedFor != "" {
		// This might indicate the client is behind a mobile carrier NAT
		// Additional logic could be added here
	}
	
	// Check connection patterns (this would need more sophisticated logic)
	clientIP := getClientIP(r)
	if cct.isLikelyMobileIP(clientIP) {
		return "mobile"
	}
	
	// Default assumption for unknown
	return "unknown"
}

// TrackClientSession tracks a client session across potential IP changes
func (cct *ClientConnectionTracker) TrackClientSession(sessionID string, clientIP string, r *http.Request) *ClientSession {
	cct.mutex.Lock()
	defer cct.mutex.Unlock()
	
	// Check if this IP is already associated with a different session
	if existingSessionID, exists := cct.ipToSession[clientIP]; exists && existingSessionID != sessionID {
		// This IP was previously used by a different session
		// This could indicate a client that switched networks
		if cct.config.AllowIPChanges {
			// Remove old association
			delete(cct.ipToSession, clientIP)
		}
	}
	
	// Get or create session
	session, exists := cct.sessions[sessionID]
	if !exists {
		session = &ClientSession{
			SessionID:      sessionID,
			ClientIPs:      []string{clientIP},
			ConnectionType: cct.DetectClientConnectionType(r),
			LastSeen:       time.Now(),
			NetworkQuality: 100.0, // Start with good quality
		}
		cct.sessions[sessionID] = session
	} else {
		session.mutex.Lock()
		// Add this IP if it's not already tracked
		if !contains(session.ClientIPs, clientIP) {
			if len(session.ClientIPs) < cct.config.MaxIPChangesPerSession {
				session.ClientIPs = append(session.ClientIPs, clientIP)
				fmt.Printf("Client session %s now using new IP: %s (total IPs: %d)\n", 
					sessionID, clientIP, len(session.ClientIPs))
			}
		}
		session.LastSeen = time.Now()
		session.mutex.Unlock()
	}
	
	// Update IP to session mapping
	cct.ipToSession[clientIP] = sessionID
	
	return session
}

// GetOptimalChunkSize returns the optimal chunk size for a client's connection type
func (cct *ClientConnectionTracker) GetOptimalChunkSize(session *ClientSession) int64 {
	switch session.ConnectionType {
	case "mobile":
		return 256 * 1024 // 256KB for mobile/LTE
	case "wifi":
		return 2 * 1024 * 1024 // 2MB for WiFi
	case "ethernet":
		return 8 * 1024 * 1024 // 8MB for ethernet
	default:
		return 1 * 1024 * 1024 // 1MB default
	}
}

// GetOptimalTimeout returns the optimal timeout for a client's connection type
func (cct *ClientConnectionTracker) GetOptimalTimeout(session *ClientSession, baseTimeout time.Duration) time.Duration {
	switch session.ConnectionType {
	case "mobile":
		return time.Duration(float64(baseTimeout) * 2.0) // 2x timeout for mobile
	case "wifi":
		return baseTimeout // Standard timeout for WiFi
	case "ethernet":
		return time.Duration(float64(baseTimeout) * 0.8) // 0.8x timeout for ethernet
	default:
		return baseTimeout
	}
}

// HandleClientReconnection handles when a client reconnects from a different IP
func (cct *ClientConnectionTracker) HandleClientReconnection(sessionID string, newIP string, r *http.Request) error {
	cct.mutex.Lock()
	defer cct.mutex.Unlock()
	
	session, exists := cct.sessions[sessionID]
	if !exists {
		return fmt.Errorf("session %s not found", sessionID)
	}
	
	session.mutex.Lock()
	defer session.mutex.Unlock()
	
	// Check if this is actually a new IP
	if contains(session.ClientIPs, newIP) {
		// Client reconnected from known IP
		session.LastSeen = time.Now()
		return nil
	}
	
	// This is a new IP for this session - client likely switched networks
	if len(session.ClientIPs) >= cct.config.MaxIPChangesPerSession {
		return fmt.Errorf("session %s exceeded maximum IP changes (%d)", 
			sessionID, cct.config.MaxIPChangesPerSession)
	}
	
	// Add new IP and update connection type
	session.ClientIPs = append(session.ClientIPs, newIP)
	session.ConnectionType = cct.DetectClientConnectionType(r)
	session.LastSeen = time.Now()
	
	// Update IP mapping
	cct.ipToSession[newIP] = sessionID
	
	fmt.Printf("Client session %s reconnected from new IP %s (connection type: %s)\n",
		sessionID, newIP, session.ConnectionType)
	
	return nil
}

// ResumeUpload handles resuming an upload when client switches networks
func (cct *ClientConnectionTracker) ResumeUpload(sessionID string, uploadInfo *UploadSessionInfo) error {
	cct.mutex.RLock()
	session, exists := cct.sessions[sessionID]
	cct.mutex.RUnlock()
	
	if !exists {
		return fmt.Errorf("session %s not found for upload resume", sessionID)
	}
	
	session.mutex.Lock()
	session.UploadInfo = uploadInfo
	session.LastSeen = time.Now()
	session.mutex.Unlock()
	
	fmt.Printf("Resumed upload for session %s: %s (%d/%d bytes)\n",
		sessionID, uploadInfo.FileName, uploadInfo.UploadedBytes, uploadInfo.TotalSize)
	
	return nil
}

// CleanupStaleSession removes sessions that haven't been seen recently
func (cct *ClientConnectionTracker) CleanupStaleSessions() {
	cct.mutex.Lock()
	defer cct.mutex.Unlock()
	
	cutoff := time.Now().Add(-cct.config.SessionMigrationTimeout)
	
	for sessionID, session := range cct.sessions {
		if session.LastSeen.Before(cutoff) {
			// Remove from IP mappings
			for _, ip := range session.ClientIPs {
				delete(cct.ipToSession, ip)
			}
			// Remove session
			delete(cct.sessions, sessionID)
			fmt.Printf("Cleaned up stale session: %s\n", sessionID)
		}
	}
}

// isLikelyMobileIP attempts to determine if an IP is from a mobile carrier
func (cct *ClientConnectionTracker) isLikelyMobileIP(ip string) bool {
	// This is a simplified check - in practice, you'd check against
	// known mobile carrier IP ranges
	
	parsedIP := net.ParseIP(ip)
	if parsedIP == nil {
		return false
	}
	
	// Example: Some mobile carriers use specific IP ranges
	// This would need to be populated with actual carrier ranges
	mobileRanges := []string{
		"10.0.0.0/8",    // Some carriers use 10.x for mobile
		"172.16.0.0/12", // Some carriers use 172.x for mobile
	}
	
	for _, rangeStr := range mobileRanges {
		_, cidr, err := net.ParseCIDR(rangeStr)
		if err != nil {
			continue
		}
		if cidr.Contains(parsedIP) {
			return true
		}
	}
	
	return false
}

// Helper function to start cleanup routine
func (cct *ClientConnectionTracker) StartCleanupRoutine() {
	go func() {
		ticker := time.NewTicker(5 * time.Minute) // Clean up every 5 minutes
		defer ticker.Stop()
		
		for range ticker.C {
			cct.CleanupStaleSessions()
		}
	}()
}
