"""
Base Topology Strategy

Abstract base class for all topology strategies.
"""
from abc import ABC, abstractmethod
from dataclasses import dataclass, field
from datetime import datetime
from typing import List, Dict, Any, Optional, TYPE_CHECKING

try:
    from typing import TypedDict
except ImportError:
    from typing_extensions import TypedDict

# Type-safe metadata structure
class PeerMetadata(TypedDict, total=False):
    """Typed dictionary for peer metadata with common fields."""
    capabilities: list[str]
    trust_score: float
    region: str
    last_latency_ms: float
    custom_data: Dict[str, Any]

@dataclass(frozen=False)
class PeerInfo:
    """Information about a peer in the network."""
    peer_id: str
    domain: str
    data_schema_hash: str
    model_version: str = "0.1.0"
    last_seen: datetime = field(default_factory=datetime.now)
    metadata: Dict[str, Any] = field(default_factory=dict)
    # Age tracking for Cyclon-style protocols
    age: int = 0

    def update_last_seen(self) -> None:
        """Update the last_seen timestamp to now."""
        self.last_seen = datetime.now()

    def increment_age(self) -> None:
        """Increment age for Cyclon-style eviction."""
        self.age += 1

    def reset_age(self) -> None:
        """Reset age (typically after shuffle)."""
        self.age = 0

@dataclass
class SelectionContext:
    """Context provided to topology strategy for target selection."""
    my_peer_id: str
    my_domain: str
    my_data_schema_hash: str
    known_peers: List[PeerInfo]
    current_round: int = 0
    metadata: Dict[str, Any] = field(default_factory=dict)

    def get_compatible_peers(self, exclude_self: bool = True) -> List[PeerInfo]:
        """
        Get compatible peers from known_peers.

        Args:
            exclude_self: If True, exclude self peer from results

        Returns:
            List of compatible PeerInfo objects
        """
        compatible = []
        for peer in self.known_peers:
            if exclude_self and peer.peer_id == self.my_peer_id:
                continue
            if (peer.domain == self.my_domain and
                peer.data_schema_hash == self.my_data_schema_hash):
                compatible.append(peer)
        return compatible

class TopologyStrategy(ABC):
    """
    Abstract base class for topology strategies.

    A topology strategy determines which peers to communicate with
    during the gossip learning process.
    """

    def __init__(self, **kwargs: Any) -> None:
        """Initialize the topology strategy with configuration."""
        self.config: Dict[str, Any] = kwargs

    @abstractmethod
    async def select_targets(
        self,
        context: SelectionContext,
        count: int = 3
    ) -> List[str]:
        """
        Select peer IDs to send model updates to.

        Args:
            context: Current execution context including known peers
            count: Maximum number of targets to select

        Returns:
            List of peer IDs to send updates to
        """
        pass

    @abstractmethod
    async def should_accept_connection(
        self,
        context: SelectionContext,
        peer_info: PeerInfo
    ) -> bool:
        """
        Decide whether to accept an incoming connection from a peer.

        Args:
            context: Current execution context
            peer_info: Information about the peer requesting connection

        Returns:
            True if connection should be accepted, False otherwise
        """
        pass

    async def periodic_maintenance(self, context: SelectionContext) -> None:
        """
        Perform periodic maintenance tasks (e.g., shuffling peer list).

        Args:
            context: Current execution context
        """
        pass

    def get_active_view(self) -> List[PeerInfo]:
        """
        Get the current list of active peers (Partial View).

        Default implementation returns empty list. Subclasses should
        override this if they maintain a partial view.

        Returns:
            List of PeerInfo objects representing the current active view.
        """
        return []

    async def on_peer_disconnected(self, peer_id: str) -> None:
        """
        Called when a peer disconnects.

        Args:
            peer_id: ID of the disconnected peer
        """
        pass

    async def on_new_peer_discovered(self, peer_info: PeerInfo) -> None:
        """
        Called when a new peer is discovered.

        Args:
            peer_info: Information about the newly discovered peer
        """
        pass
