//! Peer management for Synor network.

use crate::message::StatusMessage;
use hashbrown::HashMap;
use libp2p::{Multiaddr, PeerId};
use parking_lot::RwLock;
use std::collections::VecDeque;
use std::time::{Duration, Instant};

/// Peer connection state.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum PeerState {
    /// Connection is being established.
    Connecting,
    /// Connected, awaiting handshake.
    Connected,
    /// Handshake complete, peer is active.
    Active,
    /// Peer is being disconnected.
    Disconnecting,
    /// Peer is disconnected.
    Disconnected,
    /// Peer is banned.
    Banned,
}

impl PeerState {
    /// Returns true if the peer is usable for requests.
    pub fn is_usable(&self) -> bool {
        matches!(self, PeerState::Active)
    }
}

/// Direction of peer connection.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Direction {
    /// We initiated the connection.
    Outbound,
    /// Peer initiated the connection.
    Inbound,
}

/// Information about a connected peer.
#[derive(Clone, Debug)]
pub struct PeerInfo {
    /// Peer ID.
    pub peer_id: PeerId,
    /// Known addresses.
    pub addresses: Vec<Multiaddr>,
    /// Connection state.
    pub state: PeerState,
    /// Connection direction.
    pub direction: Direction,
    /// When the connection was established.
    pub connected_at: Option<Instant>,
    /// Status from handshake.
    pub status: Option<StatusMessage>,
    /// Latency (from ping).
    pub latency: Option<Duration>,
    /// Bytes sent.
    pub bytes_sent: u64,
    /// Bytes received.
    pub bytes_received: u64,
    /// Number of successful requests.
    pub successful_requests: u32,
    /// Number of failed requests.
    pub failed_requests: u32,
    /// Reputation score.
    pub reputation: i32,
    /// Last activity time.
    pub last_activity: Instant,
    /// User agent.
    pub user_agent: Option<String>,
    /// Protocol version.
    pub protocol_version: Option<u32>,
}

impl PeerInfo {
    /// Creates new peer info for an outbound connection.
    pub fn new_outbound(peer_id: PeerId, address: Multiaddr) -> Self {
        PeerInfo {
            peer_id,
            addresses: vec![address],
            state: PeerState::Connecting,
            direction: Direction::Outbound,
            connected_at: None,
            status: None,
            latency: None,
            bytes_sent: 0,
            bytes_received: 0,
            successful_requests: 0,
            failed_requests: 0,
            reputation: 0,
            last_activity: Instant::now(),
            user_agent: None,
            protocol_version: None,
        }
    }

    /// Creates new peer info for an inbound connection.
    pub fn new_inbound(peer_id: PeerId) -> Self {
        PeerInfo {
            peer_id,
            addresses: Vec::new(),
            state: PeerState::Connected,
            direction: Direction::Inbound,
            connected_at: Some(Instant::now()),
            status: None,
            latency: None,
            bytes_sent: 0,
            bytes_received: 0,
            successful_requests: 0,
            failed_requests: 0,
            reputation: 0,
            last_activity: Instant::now(),
            user_agent: None,
            protocol_version: None,
        }
    }

    /// Returns the best known address.
    pub fn best_address(&self) -> Option<&Multiaddr> {
        self.addresses.first()
    }

    /// Updates the peer's status from handshake.
    pub fn set_status(&mut self, status: StatusMessage) {
        self.user_agent = Some(status.user_agent.clone());
        self.protocol_version = Some(status.version);
        self.status = Some(status);
        self.state = PeerState::Active;
    }

    /// Returns the peer's best blue score.
    pub fn best_blue_score(&self) -> Option<u64> {
        self.status.as_ref().map(|s| s.best_blue_score)
    }

    /// Returns the peer's best DAA score.
    pub fn best_daa_score(&self) -> Option<u64> {
        self.status.as_ref().map(|s| s.best_daa_score)
    }

    /// Adds reputation points.
    pub fn add_reputation(&mut self, points: i32) {
        self.reputation = self.reputation.saturating_add(points);
    }

    /// Records a successful request.
    pub fn record_success(&mut self) {
        self.successful_requests += 1;
        self.add_reputation(1);
        self.last_activity = Instant::now();
    }

    /// Records a failed request.
    pub fn record_failure(&mut self) {
        self.failed_requests += 1;
        self.add_reputation(-5);
        self.last_activity = Instant::now();
    }

    /// Returns the success rate (0.0 to 1.0).
    pub fn success_rate(&self) -> f64 {
        let total = self.successful_requests + self.failed_requests;
        if total == 0 {
            1.0
        } else {
            self.successful_requests as f64 / total as f64
        }
    }

    /// Returns the connection duration.
    pub fn connection_duration(&self) -> Option<Duration> {
        self.connected_at.map(|t| t.elapsed())
    }

    /// Returns time since last activity.
    pub fn idle_time(&self) -> Duration {
        self.last_activity.elapsed()
    }
}

/// Peer reputation thresholds.
pub mod reputation {
    /// Reputation below which peer is banned.
    pub const BAN_THRESHOLD: i32 = -100;
    /// Reputation for sending invalid data.
    pub const INVALID_DATA: i32 = -50;
    /// Reputation for timeout.
    pub const TIMEOUT: i32 = -10;
    /// Reputation for good block.
    pub const GOOD_BLOCK: i32 = 5;
    /// Reputation for valid transaction.
    pub const GOOD_TRANSACTION: i32 = 1;
}

/// Manages peer connections and state.
pub struct PeerManager {
    /// All known peers.
    peers: RwLock<HashMap<PeerId, PeerInfo>>,
    /// Banned peers with ban expiry.
    banned: RwLock<HashMap<PeerId, Instant>>,
    /// Recent peer addresses for discovery.
    recent_addresses: RwLock<VecDeque<(PeerId, Multiaddr)>>,
    /// Maximum number of peers.
    max_peers: usize,
    /// Maximum inbound connections.
    max_inbound: usize,
    /// Maximum outbound connections.
    max_outbound: usize,
    /// Ban duration.
    ban_duration: Duration,
}

impl PeerManager {
    /// Creates a new peer manager.
    pub fn new(max_inbound: usize, max_outbound: usize) -> Self {
        PeerManager {
            peers: RwLock::new(HashMap::new()),
            banned: RwLock::new(HashMap::new()),
            recent_addresses: RwLock::new(VecDeque::with_capacity(1000)),
            max_peers: max_inbound + max_outbound,
            max_inbound,
            max_outbound,
            ban_duration: Duration::from_secs(3600), // 1 hour default
        }
    }

    /// Returns the number of connected peers.
    pub fn connected_count(&self) -> usize {
        self.peers
            .read()
            .values()
            .filter(|p| p.state.is_usable())
            .count()
    }

    /// Returns the number of inbound connections.
    pub fn inbound_count(&self) -> usize {
        self.peers
            .read()
            .values()
            .filter(|p| p.direction == Direction::Inbound && p.state.is_usable())
            .count()
    }

    /// Returns the number of outbound connections.
    pub fn outbound_count(&self) -> usize {
        self.peers
            .read()
            .values()
            .filter(|p| p.direction == Direction::Outbound && p.state.is_usable())
            .count()
    }

    /// Checks if we can accept a new inbound connection.
    pub fn can_accept_inbound(&self) -> bool {
        self.inbound_count() < self.max_inbound
    }

    /// Checks if we can make a new outbound connection.
    pub fn can_connect_outbound(&self) -> bool {
        self.outbound_count() < self.max_outbound
    }

    /// Checks if a peer is banned.
    pub fn is_banned(&self, peer_id: &PeerId) -> bool {
        let banned = self.banned.read();
        if let Some(expiry) = banned.get(peer_id) {
            if Instant::now() < *expiry {
                return true;
            }
        }
        false
    }

    /// Bans a peer.
    pub fn ban(&self, peer_id: &PeerId) {
        let expiry = Instant::now() + self.ban_duration;
        self.banned.write().insert(*peer_id, expiry);
        if let Some(peer) = self.peers.write().get_mut(peer_id) {
            peer.state = PeerState::Banned;
        }
    }

    /// Unbans a peer.
    pub fn unban(&self, peer_id: &PeerId) {
        self.banned.write().remove(peer_id);
    }

    /// Adds a new peer.
    pub fn add_peer(&self, peer_id: PeerId, info: PeerInfo) -> bool {
        if self.is_banned(&peer_id) {
            return false;
        }

        let mut peers = self.peers.write();
        if peers.len() >= self.max_peers && !peers.contains_key(&peer_id) {
            return false;
        }

        peers.insert(peer_id, info);
        true
    }

    /// Gets peer info.
    pub fn get_peer(&self, peer_id: &PeerId) -> Option<PeerInfo> {
        self.peers.read().get(peer_id).cloned()
    }

    /// Updates peer info.
    pub fn update_peer<F>(&self, peer_id: &PeerId, f: F) -> bool
    where
        F: FnOnce(&mut PeerInfo),
    {
        // Check if peer exists and needs banning (must release lock before calling ban)
        let should_ban = {
            let mut peers = self.peers.write();
            if let Some(peer) = peers.get_mut(peer_id) {
                f(peer);

                // Check if reputation dropped below threshold
                if peer.reputation <= reputation::BAN_THRESHOLD {
                    peer.state = PeerState::Banned;
                    true // Need to ban after releasing lock
                } else {
                    false
                }
            } else {
                return false;
            }
        }; // Lock released here

        // Ban after releasing the peers lock to avoid deadlock
        if should_ban {
            self.banned
                .write()
                .insert(*peer_id, Instant::now() + self.ban_duration);
        }

        true
    }

    /// Removes a peer.
    pub fn remove_peer(&self, peer_id: &PeerId) -> Option<PeerInfo> {
        self.peers.write().remove(peer_id)
    }

    /// Gets all active peers.
    pub fn active_peers(&self) -> Vec<PeerInfo> {
        self.peers
            .read()
            .values()
            .filter(|p| p.state.is_usable())
            .cloned()
            .collect()
    }

    /// Gets all peer IDs.
    pub fn peer_ids(&self) -> Vec<PeerId> {
        self.peers.read().keys().copied().collect()
    }

    /// Gets peers sorted by reputation (best first).
    pub fn peers_by_reputation(&self) -> Vec<PeerInfo> {
        let mut peers: Vec<_> = self.active_peers();
        peers.sort_by(|a, b| b.reputation.cmp(&a.reputation));
        peers
    }

    /// Gets peers with the highest blue score.
    pub fn peers_by_blue_score(&self) -> Vec<PeerInfo> {
        let mut peers: Vec<_> = self
            .active_peers()
            .into_iter()
            .filter(|p| p.status.is_some())
            .collect();
        peers.sort_by(|a, b| {
            b.best_blue_score()
                .unwrap_or(0)
                .cmp(&a.best_blue_score().unwrap_or(0))
        });
        peers
    }

    /// Selects a random active peer.
    pub fn random_peer(&self) -> Option<PeerInfo> {
        use rand::seq::SliceRandom;
        let peers = self.active_peers();
        peers.choose(&mut rand::thread_rng()).cloned()
    }

    /// Selects multiple random active peers.
    pub fn random_peers(&self, count: usize) -> Vec<PeerInfo> {
        use rand::seq::SliceRandom;
        let mut peers = self.active_peers();
        peers.shuffle(&mut rand::thread_rng());
        peers.truncate(count);
        peers
    }

    /// Adds a discovered address.
    pub fn add_discovered_address(&self, peer_id: PeerId, address: Multiaddr) {
        let mut recent = self.recent_addresses.write();
        if recent.len() >= 1000 {
            recent.pop_front();
        }
        recent.push_back((peer_id, address));
    }

    /// Gets recently discovered addresses.
    pub fn recent_addresses(&self) -> Vec<(PeerId, Multiaddr)> {
        self.recent_addresses.read().iter().cloned().collect()
    }

    /// Cleans up expired bans and stale peers.
    pub fn cleanup(&self) {
        let now = Instant::now();

        // Remove expired bans
        self.banned.write().retain(|_, expiry| *expiry > now);

        // Remove disconnected peers
        self.peers
            .write()
            .retain(|_, p| p.state != PeerState::Disconnected);
    }

    /// Returns statistics about peers.
    pub fn stats(&self) -> PeerStats {
        let peers = self.peers.read();
        let active: Vec<_> = peers.values().filter(|p| p.state.is_usable()).collect();

        PeerStats {
            total: peers.len(),
            active: active.len(),
            inbound: active
                .iter()
                .filter(|p| p.direction == Direction::Inbound)
                .count(),
            outbound: active
                .iter()
                .filter(|p| p.direction == Direction::Outbound)
                .count(),
            banned: self.banned.read().len(),
            avg_latency: active
                .iter()
                .filter_map(|p| p.latency)
                .map(|d| d.as_millis() as u64)
                .sum::<u64>()
                .checked_div(active.len() as u64),
            avg_reputation: if active.is_empty() {
                0
            } else {
                active.iter().map(|p| p.reputation as i64).sum::<i64>() / active.len() as i64
            } as i32,
        }
    }
}

/// Peer statistics.
#[derive(Clone, Debug)]
pub struct PeerStats {
    /// Total peers.
    pub total: usize,
    /// Active peers.
    pub active: usize,
    /// Inbound connections.
    pub inbound: usize,
    /// Outbound connections.
    pub outbound: usize,
    /// Banned peers.
    pub banned: usize,
    /// Average latency in ms.
    pub avg_latency: Option<u64>,
    /// Average reputation.
    pub avg_reputation: i32,
}

#[cfg(test)]
mod tests {
    use super::*;

    fn random_peer_id() -> PeerId {
        PeerId::random()
    }

    #[test]
    fn test_peer_manager_add_remove() {
        let manager = PeerManager::new(10, 10);

        let peer_id = random_peer_id();
        let info = PeerInfo::new_inbound(peer_id);

        assert!(manager.add_peer(peer_id, info));
        assert!(manager.get_peer(&peer_id).is_some());

        manager.remove_peer(&peer_id);
        assert!(manager.get_peer(&peer_id).is_none());
    }

    #[test]
    fn test_peer_ban() {
        let manager = PeerManager::new(10, 10);
        let peer_id = random_peer_id();

        manager.ban(&peer_id);
        assert!(manager.is_banned(&peer_id));

        // Cannot add banned peer
        let info = PeerInfo::new_inbound(peer_id);
        assert!(!manager.add_peer(peer_id, info));
    }

    #[test]
    fn test_peer_reputation() {
        let manager = PeerManager::new(10, 10);
        let peer_id = random_peer_id();

        let info = PeerInfo::new_inbound(peer_id);
        manager.add_peer(peer_id, info);

        // Decrease reputation
        for _ in 0..25 {
            manager.update_peer(&peer_id, |p| {
                p.add_reputation(reputation::INVALID_DATA);
            });
        }

        // Should be banned now
        assert!(manager.is_banned(&peer_id));
    }

    #[test]
    fn test_connection_limits() {
        let manager = PeerManager::new(2, 2);

        // Add 2 inbound
        for _ in 0..2 {
            let id = random_peer_id();
            let mut info = PeerInfo::new_inbound(id);
            info.state = PeerState::Active;
            manager.add_peer(id, info);
        }

        assert!(!manager.can_accept_inbound());
        assert!(manager.can_connect_outbound());
    }
}
