package p2p

import (
	"fmt"
	"log"
	"net"
	"sort"
	"sync"
	"time"

	"torrentGateway/internal/dht"
	"torrentGateway/internal/tracker"
)

// PeerInfo represents a peer from any source (tracker, DHT, WebSeed)
type PeerInfo struct {
	IP       string
	Port     int
	PeerID   string
	Source   string // "tracker", "dht", "webseed"
	Quality  int    // Higher is better
	LastSeen time.Time
}

// P2PCoordinator manages integration between tracker, DHT, and WebSeed
type P2PCoordinator struct {
	tracker   *tracker.Tracker
	dht       *dht.DHTBootstrap
	gateway   Gateway
	announcer *Announcer
	
	// Peer management
	peerCache map[string][]PeerInfo // infoHash -> peers
	cacheMutex sync.RWMutex
	
	// Configuration
	preferWebSeed   bool
	announceToAll   bool
	peerExchange    bool
	maxPeersReturn  int
}

// Gateway interface for P2P coordinator
type Gateway interface {
	CreateTorrent(fileHash string) (*TorrentInfo, error)
	WebSeedPeer() PeerInfo
	EnableWebSeed(infoHash string) error
	PublishToNostr(torrent *TorrentInfo) error
	GetPort() int
}

// TorrentInfo represents torrent metadata
type TorrentInfo struct {
	InfoHash    string
	Name        string
	Size        int64
	PieceLength int
	Pieces      []string
	WebSeedURL  string
}

// Announcer handles Nostr announcements
type Announcer interface {
	AnnounceNewTorrent(torrent *TorrentInfo) error
}

// NewCoordinator creates a new P2P coordinator
func NewCoordinator(gateway Gateway, tracker *tracker.Tracker, dht *dht.DHTBootstrap) *P2PCoordinator {
	return &P2PCoordinator{
		tracker:        tracker,
		dht:           dht,
		gateway:       gateway,
		peerCache:     make(map[string][]PeerInfo),
		preferWebSeed: true,
		announceToAll: true,
		peerExchange:  true,
		maxPeersReturn: 50,
	}
}

// OnFileUploaded coordinates all P2P components when a file is uploaded
func (p *P2PCoordinator) OnFileUploaded(fileHash string, filename string) error {
	log.Printf("P2P: Coordinating upload for file %s (%s)", fileHash[:8], filename)
	
	// 1. Create torrent
	torrent, err := p.gateway.CreateTorrent(fileHash)
	if err != nil {
		return fmt.Errorf("failed to create torrent: %v", err)
	}
	
	// 2. Register with tracker if available
	if p.tracker != nil {
		webSeedPeer := p.gateway.WebSeedPeer()
		err = p.tracker.RegisterTorrent(torrent.InfoHash, []PeerInfo{webSeedPeer})
		if err != nil {
			log.Printf("P2P: Failed to register with tracker: %v", err)
		} else {
			log.Printf("P2P: Registered torrent %s with tracker", torrent.InfoHash[:8])
		}
	}
	
	// 3. Announce to DHT if available
	if p.dht != nil {
		err = p.dht.AnnounceNewTorrent(torrent.InfoHash, p.gateway.GetPort())
		if err != nil {
			log.Printf("P2P: Failed to announce to DHT: %v", err)
		} else {
			log.Printf("P2P: Announced torrent %s to DHT", torrent.InfoHash[:8])
		}
	}
	
	// 4. Enable WebSeed serving
	err = p.gateway.EnableWebSeed(torrent.InfoHash)
	if err != nil {
		log.Printf("P2P: Failed to enable WebSeed: %v", err)
	} else {
		log.Printf("P2P: Enabled WebSeed for torrent %s", torrent.InfoHash[:8])
	}
	
	// 5. Publish to Nostr if announcer is available
	if p.announcer != nil {
		err = p.announcer.AnnounceNewTorrent(torrent)
		if err != nil {
			log.Printf("P2P: Failed to announce to Nostr: %v", err)
		} else {
			log.Printf("P2P: Published torrent %s to Nostr", torrent.InfoHash[:8])
		}
	}
	
	return nil
}

// GetPeers implements unified peer discovery across all sources
func (p *P2PCoordinator) GetPeers(infoHash string) []PeerInfo {
	p.cacheMutex.Lock()
	defer p.cacheMutex.Unlock()
	
	// Check cache first (5 minute TTL)
	if cached, exists := p.peerCache[infoHash]; exists {
		if len(cached) > 0 && time.Since(cached[0].LastSeen) < 5*time.Minute {
			return p.selectBestPeers(cached)
		}
	}
	
	var allPeers []PeerInfo
	
	// 1. Always include WebSeed if available (highest priority)
	if p.preferWebSeed {
		webSeedPeer := p.gateway.WebSeedPeer()
		webSeedPeer.Quality = 100 // Highest quality
		webSeedPeer.Source = "webseed"
		webSeedPeer.LastSeen = time.Now()
		allPeers = append(allPeers, webSeedPeer)
	}
	
	// 2. Get tracker peers
	if p.tracker != nil {
		trackerPeers := p.getTrackerPeers(infoHash)
		for _, peer := range trackerPeers {
			peer.Source = "tracker"
			peer.Quality = 80 // High quality
			allPeers = append(allPeers, peer)
		}
	}
	
	// 3. Get DHT peers
	if p.dht != nil {
		dhtPeers := p.getDHTPeers(infoHash)
		for _, peer := range dhtPeers {
			peer.Source = "dht"
			peer.Quality = 60 // Medium quality
			allPeers = append(allPeers, peer)
		}
	}
	
	// Deduplicate and cache
	dedupedPeers := p.deduplicate(allPeers)
	p.peerCache[infoHash] = dedupedPeers
	
	return p.selectBestPeers(dedupedPeers)
}

// rankPeers sorts peers by quality and connection reliability
func (p *P2PCoordinator) rankPeers(peers []PeerInfo) []PeerInfo {
	sort.Slice(peers, func(i, j int) bool {
		// Sort by quality first, then by last seen
		if peers[i].Quality != peers[j].Quality {
			return peers[i].Quality > peers[j].Quality
		}
		return peers[i].LastSeen.After(peers[j].LastSeen)
	})
	return peers
}

// selectBestPeers returns the best peers up to maxPeersReturn limit
func (p *P2PCoordinator) selectBestPeers(peers []PeerInfo) []PeerInfo {
	ranked := p.rankPeers(peers)
	if len(ranked) > p.maxPeersReturn {
		return ranked[:p.maxPeersReturn]
	}
	return ranked
}

// deduplicate removes duplicate peers based on IP:Port
func (p *P2PCoordinator) deduplicate(peers []PeerInfo) []PeerInfo {
	seen := make(map[string]bool)
	var unique []PeerInfo
	
	for _, peer := range peers {
		key := fmt.Sprintf("%s:%d", peer.IP, peer.Port)
		if !seen[key] {
			seen[key] = true
			unique = append(unique, peer)
		}
	}
	
	return unique
}

// Helper methods to get peers from different sources
func (p *P2PCoordinator) getTrackerPeers(infoHash string) []PeerInfo {
	if p.tracker == nil {
		return nil
	}
	
	// This would integrate with the tracker's peer storage
	// For now, return empty slice - tracker integration needed
	return []PeerInfo{}
}

func (p *P2PCoordinator) getDHTPeers(infoHash string) []PeerInfo {
	if p.dht == nil {
		return nil
	}
	
	// This would integrate with DHT peer discovery
	// For now, return empty slice - DHT integration needed  
	return []PeerInfo{}
}

// AnnounceToExternalServices announces torrent to DHT and other external services
func (p *P2PCoordinator) AnnounceToExternalServices(infoHash string, port int) error {
	var errs []string
	
	// Announce to DHT
	if p.dht != nil {
		if err := p.dht.AnnounceNewTorrent(infoHash, port); err != nil {
			errs = append(errs, fmt.Sprintf("DHT: %v", err))
		} else {
			log.Printf("P2P: Successfully announced %s to DHT", infoHash[:8])
		}
	}
	
	// Could add other external services here (like PEX, other trackers, etc.)
	
	if len(errs) > 0 {
		return fmt.Errorf("external announce errors: %v", errs)
	}
	
	return nil
}

// GetStats returns comprehensive P2P statistics
func (p *P2PCoordinator) GetStats() map[string]interface{} {
	stats := make(map[string]interface{})
	
	// Tracker stats (would need tracker interface methods)
	if p.tracker != nil {
		stats["tracker"] = map[string]interface{}{
			"status": "active",
		}
	}
	
	// DHT stats (would need DHT interface methods)
	if p.dht != nil {
		stats["dht"] = map[string]interface{}{
			"status": "active", 
		}
	}
	
	// WebSeed stats (from existing implementation)
	stats["webseed"] = map[string]interface{}{
		"status": "integrated",
	}
	
	// Coordination stats
	p.cacheMutex.RLock()
	cacheSize := len(p.peerCache)
	p.cacheMutex.RUnlock()
	
	stats["coordination"] = map[string]interface{}{
		"cached_peer_lists": cacheSize,
		"prefer_webseed":    p.preferWebSeed,
		"announce_to_all":   p.announceToAll,
		"peer_exchange":     p.peerExchange,
	}
	
	return stats
}

// SetAnnouncer sets the Nostr announcer
func (p *P2PCoordinator) SetAnnouncer(announcer *Announcer) {
	p.announcer = announcer
}

// OnPeerConnect handles new peer connections for coordination
func (p *P2PCoordinator) OnPeerConnect(infoHash string, peer PeerInfo) {
	// Update peer cache with new connection
	p.cacheMutex.Lock()
	defer p.cacheMutex.Unlock()
	
	peers := p.peerCache[infoHash]
	
	// Update existing peer or add new one
	found := false
	for i, existingPeer := range peers {
		if existingPeer.IP == peer.IP && existingPeer.Port == peer.Port {
			peers[i].LastSeen = time.Now()
			peers[i].Quality += 10 // Boost quality for active peers
			found = true
			break
		}
	}
	
	if !found {
		peer.LastSeen = time.Now()
		peers = append(peers, peer)
	}
	
	p.peerCache[infoHash] = peers
}