"""
Peer management for sync functionality
"""

import time
import json
from pathlib import Path
from typing import List, Optional, Dict, TYPE_CHECKING
from datetime import datetime

from .peer_model import Peer, TransportEndpoint
from .peer_request import PeerRequest
from .discovery import PeerDiscovery

if TYPE_CHECKING:
    from ..syft_client import SyftClient


class PeerManager:
    """Manages peers for bidirectional communication"""
    
    def __init__(self, client: 'SyftClient'):
        self.client = client
        self._peers_cache: Optional[Dict[str, Peer]] = None
        self._peers_cache_time: Optional[float] = None
        self._cache_ttl = 3600  # 1 hour cache
        self._peers_dir = None
        self._discovery = PeerDiscovery(client)
    
    @property
    def peers(self) -> List[str]:
        """
        List all peers (people you have set up outgoing channels to)
        
        Returns:
            List of email addresses you've added as peers
        """
        peers_dict = self.get_peers_dict()
        return list(peers_dict.keys())
    
    def get_peers_dict(self) -> Dict[str, Peer]:
        """
        Get all peers as a dictionary mapping emails to Peer objects
        
        Returns:
            Dict of email -> Peer objects
        """
        # Check if we have a valid platform with sync capability
        platform = self._get_sync_platform()
        if not platform:
            return {}
        
        # Check if cache is valid
        current_time = time.time()
        if (self._peers_cache is not None and 
            self._peers_cache_time is not None and 
            current_time - self._peers_cache_time < self._cache_ttl):
            return self._peers_cache
        
        # Get peers from the platform
        peers_list = self._get_peers_from_platform(platform)
        
        # Load or create Peer objects
        peers_dict = {}
        for email in peers_list:
            peer = self._load_or_create_peer(email)
            
            # Re-verify which platforms actually have this peer
            platforms_with_peer = {}
            transports_with_peer = {}
            
            for platform_name, plat in self.client._platforms.items():
                # Check each transport on this platform
                for attr_name in dir(plat):
                    if not attr_name.startswith('_'):
                        transport = getattr(plat, attr_name, None)
                        if transport and hasattr(transport, 'list_peers'):
                            try:
                                transport_peers = transport.list_peers()
                                if email in transport_peers:
                                    platforms_with_peer[platform_name] = plat
                                    if platform_name not in transports_with_peer:
                                        transports_with_peer[platform_name] = []
                                    transports_with_peer[platform_name].append(attr_name)
                            except:
                                pass
            
            # Update peer based on what we found
            if platforms_with_peer:
                # Determine correct platform
                # If peer already has a platform that's in the list, keep it
                # This prevents switching between google_personal and google_org unnecessarily
                correct_platform = None
                if peer.platform and peer.platform in platforms_with_peer:
                    correct_platform = peer.platform
                else:
                    # Otherwise, prefer the platform where we found more transports
                    max_transports = 0
                    for plat_name, transport_list in transports_with_peer.items():
                        if len(transport_list) > max_transports:
                            max_transports = len(transport_list)
                            correct_platform = plat_name
                    
                    # If tied, prefer google_personal over google_org
                    if not correct_platform:
                        if 'google_personal' in platforms_with_peer:
                            correct_platform = 'google_personal'
                        elif 'google_org' in platforms_with_peer:
                            correct_platform = 'google_org'
                        else:
                            correct_platform = list(platforms_with_peer.keys())[0]
                
                # Update peer if platform changed or no transports
                if peer.platform != correct_platform or not peer.available_transports:
                    peer.platform = correct_platform
                    peer.available_transports.clear()
                    
                    # Add and verify all transports found on the correct platform
                    for transport_name in transports_with_peer.get(correct_platform, []):
                        peer.add_transport(transport_name)
                        peer.verify_transport(transport_name)
                    
                    # For Google platforms, always add gmail if the platform has it
                    if correct_platform in ['google_personal', 'google_org']:
                        platform_obj = platforms_with_peer.get(correct_platform)
                        if platform_obj and hasattr(platform_obj, 'gmail'):
                            gmail_transport = getattr(platform_obj, 'gmail', None)
                            if gmail_transport and hasattr(gmail_transport, 'is_setup') and gmail_transport.is_setup():
                                if 'gmail' not in peer.available_transports:
                                    peer.add_transport('gmail')
                                peer.verify_transport('gmail')
                    
                    peer.capabilities_last_updated = datetime.now()
                    self._save_peer(peer)
                    
                # Also verify any unverified transports
                elif peer.available_transports:
                    updated = False
                    for transport_name in transports_with_peer.get(peer.platform, []):
                        if transport_name in peer.available_transports and not peer.available_transports[transport_name].verified:
                            peer.verify_transport(transport_name)
                            updated = True
                    
                    # For Google platforms, ensure gmail is included and verified
                    if peer.platform in ['google_personal', 'google_org']:
                        platform_obj = platforms_with_peer.get(peer.platform)
                        if platform_obj and hasattr(platform_obj, 'gmail'):
                            gmail_transport = getattr(platform_obj, 'gmail', None)
                            if gmail_transport and hasattr(gmail_transport, 'is_setup') and gmail_transport.is_setup():
                                if 'gmail' not in peer.available_transports:
                                    peer.add_transport('gmail')
                                    updated = True
                                if not peer.available_transports['gmail'].verified:
                                    peer.verify_transport('gmail')
                                    updated = True
                    
                    if updated:
                        self._save_peer(peer)
            
            # If we didn't find the peer in any transports but it was loaded from disk,
            # keep it but note it needs re-discovery
            if not platforms_with_peer and not peer.available_transports:
                self._discover_peer_capabilities(peer, platform)
            
            peers_dict[email] = peer
        
        # Update cache
        self._peers_cache = peers_dict
        self._peers_cache_time = current_time
        
        return peers_dict
    
    def get_peer(self, email: str) -> Optional[Peer]:
        """
        Get a specific peer by email
        
        Args:
            email: Email address of the peer
            
        Returns:
            Peer object or None if not found
        """
        peers_dict = self.get_peers_dict()
        return peers_dict.get(email)
    
    def add_peer(self, email: str) -> bool:
        """
        Add a peer for bidirectional communication
        
        This will attempt to add the peer on ALL available transports
        to maximize connectivity options.
        
        Args:
            email: Email address of the peer to add
            
        Returns:
            True if peer was added on at least one transport
        """
        if not email or '@' not in email:
            print(f"❌ Invalid email address: {email}")
            return False
        
        # Check if trying to add self
        if email.lower() == self.client.email.lower():
            print("❌ Cannot add yourself as a peer")
            return False
        
        # Try to add peer on all available transports
        successful_transports = []
        failed_transports = []
        
        # Iterate through all platforms and their transports
        for platform_name, platform in self.client._platforms.items():
            # Get all transport attributes from the platform
            for attr_name in dir(platform):
                if not attr_name.startswith('_'):
                    transport = getattr(platform, attr_name, None)
                    
                    # Check if this is a transport with add_peer method
                    if transport and hasattr(transport, 'add_peer'):
                        try:
                            if self.client.verbose:
                                print(f"🔄 Adding {email} on {platform_name}.{attr_name}...")
                            
                            # Call add_peer on the transport
                            result = transport.add_peer(email, verbose=False)
                            
                            if result:
                                successful_transports.append(f"{platform_name}.{attr_name}")
                                if self.client.verbose:
                                    print(f"   ✅ Added on {platform_name}.{attr_name}")
                            else:
                                failed_transports.append(f"{platform_name}.{attr_name}")
                                if self.client.verbose:
                                    print(f"   ❌ Failed on {platform_name}.{attr_name}")
                                    
                        except Exception as e:
                            failed_transports.append(f"{platform_name}.{attr_name}")
                            if self.client.verbose:
                                print(f"   ❌ Error on {platform_name}.{attr_name}: {e}")
        
        # Summary
        if successful_transports:
            if self.client.verbose:
                print(f"\n✅ Peer {email} added successfully on {len(successful_transports)} transport(s)")
                for transport in successful_transports:
                    print(f"   • {transport}")
            
            # Invalidate cache and force rediscovery
            self._invalidate_peers_cache()
            
            # Create or update peer object with discovered transports
            peer = self._load_or_create_peer(email)
            
            # Set platform based on successful transports
            platforms_used = set()
            for transport_path in successful_transports:
                platform_name, transport_name = transport_path.split('.')
                platforms_used.add(platform_name)
                peer.add_transport(transport_name)
                peer.verify_transport(transport_name)
            
            # Set the platform if we have a clear winner
            if len(platforms_used) == 1:
                peer.platform = list(platforms_used)[0]
            elif 'google_personal' in platforms_used:
                peer.platform = 'google_personal'  # Prefer personal
            elif 'google_org' in platforms_used:
                peer.platform = 'google_org'
            
            self._save_peer(peer)
            
            return True
        else:
            print(f"❌ Failed to add peer {email} on any transport")
            return False
    
    def remove_peer(self, email: str) -> bool:
        """
        Remove a peer from all transports
        
        Args:
            email: Email address of the peer to remove
            
        Returns:
            True if peer was removed from at least one transport
        """
        # Check if peer exists
        if email not in self.peers:
            print(f"❌ {email} is not in your peers list")
            return False
        
        # Try to remove peer from all transports
        successful_removals = []
        failed_removals = []
        
        # Iterate through all platforms and their transports
        for platform_name, platform in self.client._platforms.items():
            # Get all transport attributes from the platform
            for attr_name in dir(platform):
                if not attr_name.startswith('_'):
                    transport = getattr(platform, attr_name, None)
                    
                    # Check if this is a transport with remove_peer method
                    if transport and hasattr(transport, 'remove_peer'):
                        try:
                            if self.client.verbose:
                                print(f"🔄 Removing {email} from {platform_name}.{attr_name}...")
                            
                            # Call remove_peer on the transport
                            result = transport.remove_peer(email, verbose=False)
                            
                            if result:
                                successful_removals.append(f"{platform_name}.{attr_name}")
                                if self.client.verbose:
                                    print(f"   ✅ Removed from {platform_name}.{attr_name}")
                            else:
                                failed_removals.append(f"{platform_name}.{attr_name}")
                                if self.client.verbose:
                                    print(f"   ⚠️  Not found on {platform_name}.{attr_name}")
                                    
                        except Exception as e:
                            failed_removals.append(f"{platform_name}.{attr_name}")
                            if self.client.verbose:
                                print(f"   ❌ Error on {platform_name}.{attr_name}: {e}")
        
        # Summary and cleanup
        if successful_removals:
            if self.client.verbose:
                print(f"\n✅ Peer {email} removed from {len(successful_removals)} transport(s)")
            
            # Remove peer file
            try:
                peers_dir = self._get_peers_directory()
                file_name = f"{email.replace('@', '_at_').replace('.', '_')}.json"
                file_path = peers_dir / file_name
                if file_path.exists():
                    file_path.unlink()
            except:
                pass
            
            # Invalidate cache
            self._invalidate_peers_cache()
            return True
        else:
            print(f"⚠️  {email} was not found on any transport")
            return False
    
    def _invalidate_peers_cache(self):
        """Invalidate the peers cache to force a refresh on next access"""
        self._peers_cache = None
        self._peers_cache_time = None
    
    def clear_all_caches(self, verbose: bool = True) -> None:
        """
        Clear all peer caches from disk and memory, forcing re-detection from online sources
        
        This will:
        1. Clear in-memory peers cache
        2. Delete all saved peer JSON files
        3. Delete all discovery cache files
        4. Force fresh discovery on next access
        
        Args:
            verbose: If True, print detailed progress. If False, operate silently.
        """
        if verbose:
            print("🗑️  Clearing all peer caches...")
        
        # Clear in-memory cache
        self._invalidate_peers_cache()
        
        files_cleared = 0
        
        # Clear peer JSON files
        try:
            peers_dir = self._get_peers_directory()
            if peers_dir.exists():
                for file_path in peers_dir.glob("*.json"):
                    try:
                        file_path.unlink()
                        files_cleared += 1
                        if verbose:
                            print(f"   ✓ Deleted peer file: {file_path.name}")
                    except Exception as e:
                        if verbose:
                            print(f"   ⚠️  Could not delete {file_path.name}: {e}")
        except Exception as e:
            if verbose:
                print(f"   ⚠️  Error clearing peer files: {e}")
        
        # Clear discovery cache
        try:
            discovery_cache_dir = self._discovery._get_discovery_cache_dir()
            if discovery_cache_dir.exists():
                for file_path in discovery_cache_dir.glob("*_discovery.json"):
                    try:
                        file_path.unlink()
                        files_cleared += 1
                        if verbose:
                            print(f"   ✓ Deleted discovery cache: {file_path.name}")
                    except Exception as e:
                        if verbose:
                            print(f"   ⚠️  Could not delete {file_path.name}: {e}")
        except Exception as e:
            if verbose:
                print(f"   ⚠️  Error clearing discovery cache: {e}")
        
        if verbose and files_cleared > 0:
            print("✅ All peer caches cleared. Next access will fetch fresh data from online sources.")
        elif verbose:
            print("ℹ️  No peer caches found to clear.")
    
    def _get_sync_platform(self):
        """Get a platform that supports sync functionality"""
        # Look for platforms with sync capabilities
        # Priority order: google_org, google_personal
        for platform_name in ['google_org', 'google_personal']:
            if platform_name in self.client._platforms:
                platform = self.client._platforms[platform_name]
                # Check if it has the required transport
                if hasattr(platform, 'gdrive_files'):
                    return platform
        
        return None
    
    def _get_peers_from_platform(self, platform) -> List[str]:
        """Get peers list from all transports across all platforms"""
        all_peers = set()  # Use set to avoid duplicates
        
        # Iterate through all platforms
        for platform_name, platform in self.client._platforms.items():
            # Get all transport attributes from the platform
            for attr_name in dir(platform):
                if not attr_name.startswith('_'):
                    transport = getattr(platform, attr_name, None)
                    
                    # Check if this is a transport with list_peers method
                    if transport and hasattr(transport, 'list_peers'):
                        try:
                            peers = transport.list_peers()
                            all_peers.update(peers)
                        except:
                            pass
        
        return list(all_peers)
    
    def _get_peers_directory(self) -> Path:
        """Get the directory where peer data is stored"""
        if not self._peers_dir:
            if hasattr(self.client, 'local_syftbox_dir'):
                self._peers_dir = self.client.local_syftbox_dir / '.syft' / 'peers'
            else:
                # Fallback to home directory
                self._peers_dir = Path.home() / '.syft' / 'peers'
            self._peers_dir.mkdir(parents=True, exist_ok=True)
        return self._peers_dir
    
    def _load_or_create_peer(self, email: str) -> Peer:
        """Load peer from disk or create new one"""
        peers_dir = self._get_peers_directory()
        file_name = f"{email.replace('@', '_at_').replace('.', '_')}.json"
        file_path = peers_dir / file_name
        
        if file_path.exists():
            try:
                peer = Peer.load(file_path)
                
                # If peer has no platform but has transports, determine platform
                if not peer.platform and peer.available_transports:
                    # Determine platform from available transports
                    platforms_found = set()
                    
                    # Check which platforms have these transports
                    for platform_name, platform in self.client._platforms.items():
                        platform_has_transports = True
                        for transport_name in peer.available_transports:
                            if not hasattr(platform, transport_name):
                                platform_has_transports = False
                                break
                        if platform_has_transports:
                            platforms_found.add(platform_name)
                    
                    # Set platform if we can determine it
                    if len(platforms_found) == 1:
                        peer.platform = list(platforms_found)[0]
                    elif 'google_org' in platforms_found and email.endswith('@openmined.org'):
                        # For OpenMined emails, prefer google_org
                        peer.platform = 'google_org'
                    elif 'google_personal' in platforms_found:
                        peer.platform = 'google_personal'
                    elif 'google_org' in platforms_found:
                        peer.platform = 'google_org'
                    
                    # Save the updated peer
                    if peer.platform:
                        self._save_peer(peer)
                        
                return peer
            except Exception as e:
                if self.client.verbose:
                    print(f"⚠️  Could not load peer data for {email}: {e}")
        
        # Create new peer
        peer = Peer(email=email)
        
        # Check discovery cache
        cached_discovery = self._discovery.load_discovery_cache(email)
        if cached_discovery:
            # Apply cached discovery data
            peer.platform = cached_discovery.get('platform')
            for transport in cached_discovery.get('transports', []):
                peer.add_transport(transport)
            for transport in cached_discovery.get('verified_transports', []):
                peer.verify_transport(transport)
            if self.client.verbose:
                print(f"📋 Loaded cached capabilities for {email}")
        
        return peer
    
    def _save_peer(self, peer: Peer):
        """Save peer to disk"""
        peers_dir = self._get_peers_directory()
        try:
            peer.save(peers_dir)
        except Exception as e:
            if self.client.verbose:
                print(f"⚠️  Could not save peer data for {peer.email}: {e}")
    
    def _discover_peer_capabilities(self, peer: Peer, platform):
        """Discover what transports a peer has available"""
        # Use the discovery system
        if self._discovery.discover_capabilities(peer):
            # Save discovered capabilities
            self._save_peer(peer)
            self._discovery.save_discovery_cache(peer)
    
    def check_all_peer_requests(self, verbose: bool = True) -> Dict[str, List[PeerRequest]]:
        """
        Check all transports for incoming peer requests
        
        Args:
            verbose: Whether to print summary
            
        Returns:
            Dictionary mapping platform.transport to list of PeerRequest objects
        """
        all_requests = {}
        total_count = 0
        
        # Check each platform and transport
        for platform_name, platform in self.client._platforms.items():
            # Get all transport attributes from the platform
            for attr_name in dir(platform):
                if not attr_name.startswith('_'):
                    transport = getattr(platform, attr_name, None)
                    
                    # Check if this transport has check_peer_requests method
                    if transport and hasattr(transport, 'check_peer_requests'):
                        try:
                            # Get pending requests from this transport
                            request_emails = transport.check_peer_requests()
                            
                            if request_emails:
                                transport_key = f"{platform_name}.{attr_name}"
                                requests = []
                                
                                for email in request_emails:
                                    request = PeerRequest(
                                        email=email,
                                        platform=platform_name,
                                        transport=attr_name
                                    )
                                    requests.append(request)
                                
                                all_requests[transport_key] = requests
                                total_count += len(requests)
                                
                        except Exception as e:
                            if self.client.verbose:
                                print(f"Error checking {platform_name}.{attr_name}: {e}")
        
        # Print summary if verbose
        if verbose and total_count > 0:
            print(f"\n📬 You have {total_count} pending peer request{'s' if total_count != 1 else ''}:")
            
            # Group by email to show unique peers
            unique_peers = {}
            for transport_key, requests in all_requests.items():
                for request in requests:
                    if request.email not in unique_peers:
                        unique_peers[request.email] = []
                    unique_peers[request.email].append(f"{request.platform}.{request.transport}")
            
            # Show each unique peer
            for email, transports in unique_peers.items():
                transports_str = ", ".join(transports)
                print(f"   • {email} (via {transports_str})")
            
            print("\nTo accept a peer request, use: client.add_peer('email@example.com')")
        
        return all_requests


__all__ = ['PeerManager']