//! Multi-pin redundancy service for content persistence.
//!
//! Ensures content survives node failures by maintaining multiple copies
//! across geographically distributed nodes.
//!
//! # Pinning Strategy
//!
//! ```text
//! Content CID
//!     │
//!     ▼
//! ┌──────────────┐
//! │ Pin Manager  │ ─── Monitors pin health
//! └──────────────┘
//!     │
//!     ├──────────────┬──────────────┬──────────────┐
//!     ▼              ▼              ▼              ▼
//! ┌────────┐    ┌────────┐    ┌────────┐    ┌────────┐
//! │Node US │    │Node EU │    │Node AS │    │Node SA │
//! │ East   │    │ West   │    │ Pacific│    │ South  │
//! └────────┘    └────────┘    └────────┘    └────────┘
//! ```
//!
//! # Redundancy Levels
//!
//! - **Standard (3x)**: Pin to 3 nodes minimum
//! - **Enhanced (5x)**: Pin to 5 nodes with geo-distribution
//! - **Critical (7x)**: Pin to 7 nodes across all regions

use crate::cid::ContentId;
use crate::error::{Error, Result};
use parking_lot::RwLock;
use serde::{Deserialize, Serialize};
use std::collections::{HashMap, HashSet};
use std::time::{SystemTime, UNIX_EPOCH};

/// Geographic region for node distribution
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum Region {
    /// North America
    NorthAmerica,
    /// Europe
    Europe,
    /// Asia Pacific
    AsiaPacific,
    /// South America
    SouthAmerica,
    /// Africa
    Africa,
    /// Oceania
    Oceania,
}

impl Region {
    /// All regions
    pub fn all() -> &'static [Region] {
        &[
            Region::NorthAmerica,
            Region::Europe,
            Region::AsiaPacific,
            Region::SouthAmerica,
            Region::Africa,
            Region::Oceania,
        ]
    }

    /// Short code for region
    pub fn code(&self) -> &'static str {
        match self {
            Region::NorthAmerica => "NA",
            Region::Europe => "EU",
            Region::AsiaPacific => "AP",
            Region::SouthAmerica => "SA",
            Region::Africa => "AF",
            Region::Oceania => "OC",
        }
    }
}

/// Redundancy level for pinning
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum RedundancyLevel {
    /// Single copy (no redundancy)
    None,
    /// Standard: 3 copies minimum
    Standard,
    /// Enhanced: 5 copies with geo-distribution
    Enhanced,
    /// Critical: 7 copies across all available regions
    Critical,
}

impl RedundancyLevel {
    /// Minimum number of pins required
    pub fn min_copies(&self) -> usize {
        match self {
            RedundancyLevel::None => 1,
            RedundancyLevel::Standard => 3,
            RedundancyLevel::Enhanced => 5,
            RedundancyLevel::Critical => 7,
        }
    }

    /// Minimum number of unique regions required
    pub fn min_regions(&self) -> usize {
        match self {
            RedundancyLevel::None => 1,
            RedundancyLevel::Standard => 2,
            RedundancyLevel::Enhanced => 3,
            RedundancyLevel::Critical => 4,
        }
    }

    /// Whether geo-distribution is required
    pub fn requires_geo_distribution(&self) -> bool {
        matches!(self, RedundancyLevel::Enhanced | RedundancyLevel::Critical)
    }
}

impl Default for RedundancyLevel {
    fn default() -> Self {
        RedundancyLevel::Standard
    }
}

/// Storage node information
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StorageNode {
    /// Unique node ID
    pub id: String,
    /// Node endpoint URL
    pub endpoint: String,
    /// Geographic region
    pub region: Region,
    /// Current health status
    pub healthy: bool,
    /// Storage capacity in bytes
    pub capacity: u64,
    /// Used storage in bytes
    pub used: u64,
    /// Last health check timestamp
    pub last_check: u64,
    /// Response latency in milliseconds
    pub latency_ms: u32,
}

impl StorageNode {
    /// Create a new storage node
    pub fn new(id: &str, endpoint: &str, region: Region) -> Self {
        Self {
            id: id.to_string(),
            endpoint: endpoint.to_string(),
            region,
            healthy: true,
            capacity: 100 * 1024 * 1024 * 1024, // 100 GB default
            used: 0,
            last_check: 0,
            latency_ms: 0,
        }
    }

    /// Available capacity
    pub fn available(&self) -> u64 {
        self.capacity.saturating_sub(self.used)
    }

    /// Check if node can store content of given size
    pub fn can_store(&self, size: u64) -> bool {
        self.healthy && self.available() >= size
    }
}

/// Pin status for tracking content state
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum PinStatus {
    /// Pin request queued
    Queued,
    /// Pinning in progress
    Pinning,
    /// Successfully pinned
    Pinned,
    /// Pin failed
    Failed,
    /// Content unpinned
    Unpinned,
}

/// Pin record for a single CID-node pair
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PinRecord {
    /// Content ID
    pub cid: ContentId,
    /// Node ID where content is pinned
    pub node_id: String,
    /// Pin status
    pub status: PinStatus,
    /// Content size in bytes
    pub size: u64,
    /// When the pin was created
    pub created_at: u64,
    /// Last verification time
    pub last_verified: u64,
    /// Verification count (successful checks)
    pub verification_count: u32,
}

impl PinRecord {
    /// Create a new pin record
    pub fn new(cid: ContentId, node_id: &str, size: u64) -> Self {
        let now = SystemTime::now()
            .duration_since(UNIX_EPOCH)
            .unwrap()
            .as_secs();

        Self {
            cid,
            node_id: node_id.to_string(),
            status: PinStatus::Queued,
            size,
            created_at: now,
            last_verified: 0,
            verification_count: 0,
        }
    }

    /// Mark as pinned
    pub fn mark_pinned(&mut self) {
        self.status = PinStatus::Pinned;
        self.last_verified = SystemTime::now()
            .duration_since(UNIX_EPOCH)
            .unwrap()
            .as_secs();
        self.verification_count += 1;
    }

    /// Mark as failed
    pub fn mark_failed(&mut self) {
        self.status = PinStatus::Failed;
    }

    /// Update verification time
    pub fn verify(&mut self) {
        self.last_verified = SystemTime::now()
            .duration_since(UNIX_EPOCH)
            .unwrap()
            .as_secs();
        self.verification_count += 1;
    }

    /// Check if pin is healthy (verified recently)
    pub fn is_healthy(&self, max_age_secs: u64) -> bool {
        if self.status != PinStatus::Pinned {
            return false;
        }
        let now = SystemTime::now()
            .duration_since(UNIX_EPOCH)
            .unwrap()
            .as_secs();
        now - self.last_verified < max_age_secs
    }
}

/// Pin request for content
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PinRequest {
    /// Content ID to pin
    pub cid: ContentId,
    /// Requested redundancy level
    pub redundancy: RedundancyLevel,
    /// Preferred regions (empty = any)
    pub preferred_regions: Vec<Region>,
    /// Exclude these nodes
    pub exclude_nodes: Vec<String>,
    /// Request timestamp
    pub requested_at: u64,
    /// Expiration (0 = never)
    pub expires_at: u64,
}

impl PinRequest {
    /// Create a new pin request
    pub fn new(cid: ContentId, redundancy: RedundancyLevel) -> Self {
        let now = SystemTime::now()
            .duration_since(UNIX_EPOCH)
            .unwrap()
            .as_secs();

        Self {
            cid,
            redundancy,
            preferred_regions: Vec::new(),
            exclude_nodes: Vec::new(),
            requested_at: now,
            expires_at: 0,
        }
    }

    /// Set preferred regions
    pub fn with_regions(mut self, regions: Vec<Region>) -> Self {
        self.preferred_regions = regions;
        self
    }

    /// Set expiration time
    pub fn with_expiration(mut self, expires_at: u64) -> Self {
        self.expires_at = expires_at;
        self
    }
}

/// Pin status summary for a CID
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PinSummary {
    /// Content ID
    pub cid: ContentId,
    /// Total pin count
    pub pin_count: usize,
    /// Healthy pin count
    pub healthy_pins: usize,
    /// Regions with pins
    pub regions: Vec<Region>,
    /// Current redundancy met
    pub redundancy_met: bool,
    /// Requested redundancy level
    pub redundancy_level: RedundancyLevel,
    /// All pin records
    pub pins: Vec<PinRecord>,
}

/// Pin manager configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PinManagerConfig {
    /// Default redundancy level
    pub default_redundancy: RedundancyLevel,
    /// Health check interval in seconds
    pub health_check_interval: u64,
    /// Maximum age for pin verification (seconds)
    pub max_verification_age: u64,
    /// Enable automatic re-pinning on failure
    pub auto_repin: bool,
    /// Maximum concurrent pin operations
    pub max_concurrent_pins: usize,
}

impl Default for PinManagerConfig {
    fn default() -> Self {
        Self {
            default_redundancy: RedundancyLevel::Standard,
            health_check_interval: 300,     // 5 minutes
            max_verification_age: 86400,    // 24 hours
            auto_repin: true,
            max_concurrent_pins: 100,
        }
    }
}

/// Pin manager for multi-pin redundancy
pub struct PinManager {
    /// Configuration
    config: PinManagerConfig,
    /// Storage nodes by ID
    nodes: RwLock<HashMap<String, StorageNode>>,
    /// Pin records by CID string
    pins: RwLock<HashMap<String, Vec<PinRecord>>>,
    /// Pending pin requests
    pending: RwLock<Vec<PinRequest>>,
    /// Statistics
    stats: RwLock<PinStats>,
}

/// Pin statistics
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct PinStats {
    /// Total pins
    pub total_pins: u64,
    /// Active pins
    pub active_pins: u64,
    /// Failed pins
    pub failed_pins: u64,
    /// Total bytes pinned
    pub total_bytes: u64,
    /// Pins per region
    pub pins_by_region: HashMap<String, u64>,
}

impl PinManager {
    /// Create a new pin manager
    pub fn new(config: PinManagerConfig) -> Self {
        Self {
            config,
            nodes: RwLock::new(HashMap::new()),
            pins: RwLock::new(HashMap::new()),
            pending: RwLock::new(Vec::new()),
            stats: RwLock::new(PinStats::default()),
        }
    }

    /// Create with default configuration
    pub fn default_manager() -> Self {
        Self::new(PinManagerConfig::default())
    }

    /// Register a storage node
    pub fn register_node(&self, node: StorageNode) {
        let mut nodes = self.nodes.write();
        nodes.insert(node.id.clone(), node);
    }

    /// Remove a storage node
    pub fn remove_node(&self, node_id: &str) {
        let mut nodes = self.nodes.write();
        nodes.remove(node_id);
    }

    /// Get all healthy nodes
    pub fn healthy_nodes(&self) -> Vec<StorageNode> {
        let nodes = self.nodes.read();
        nodes.values().filter(|n| n.healthy).cloned().collect()
    }

    /// Get nodes by region
    pub fn nodes_by_region(&self, region: Region) -> Vec<StorageNode> {
        let nodes = self.nodes.read();
        nodes
            .values()
            .filter(|n| n.region == region && n.healthy)
            .cloned()
            .collect()
    }

    /// Request to pin content
    pub fn request_pin(&self, request: PinRequest) -> Result<()> {
        let mut pending = self.pending.write();
        pending.push(request);
        Ok(())
    }

    /// Pin content with specified redundancy
    pub async fn pin(&self, cid: &ContentId, size: u64, redundancy: RedundancyLevel) -> Result<PinSummary> {
        let required_copies = redundancy.min_copies();
        let required_regions = redundancy.min_regions();

        // Select nodes for pinning
        let selected_nodes = self.select_nodes(cid, size, required_copies, required_regions)?;

        let mut pins = Vec::new();
        let mut regions = HashSet::new();

        for node in selected_nodes {
            let mut record = PinRecord::new(cid.clone(), &node.id, size);

            // Simulate pin operation (in real impl, would call node API)
            record.mark_pinned();
            pins.push(record.clone());
            regions.insert(node.region);

            // Update stats
            {
                let mut stats = self.stats.write();
                stats.total_pins += 1;
                stats.active_pins += 1;
                stats.total_bytes += size;
                *stats.pins_by_region.entry(node.region.code().to_string()).or_insert(0) += 1;
            }
        }

        // Store pin records
        {
            let mut all_pins = self.pins.write();
            all_pins.insert(cid.to_string_repr(), pins.clone());
        }

        let healthy_pins = pins.iter().filter(|p| p.status == PinStatus::Pinned).count();
        let redundancy_met = healthy_pins >= required_copies
            && regions.len() >= required_regions;

        Ok(PinSummary {
            cid: cid.clone(),
            pin_count: pins.len(),
            healthy_pins,
            regions: regions.into_iter().collect(),
            redundancy_met,
            redundancy_level: redundancy,
            pins,
        })
    }

    /// Select optimal nodes for pinning
    fn select_nodes(
        &self,
        cid: &ContentId,
        size: u64,
        required_copies: usize,
        required_regions: usize,
    ) -> Result<Vec<StorageNode>> {
        let nodes = self.nodes.read();

        // Filter nodes that can store the content
        let available: Vec<StorageNode> = nodes
            .values()
            .filter(|n: &&StorageNode| n.can_store(size))
            .cloned()
            .collect();

        if available.len() < required_copies {
            return Err(Error::InsufficientNodes(format!(
                "Need {} nodes, only {} available",
                required_copies,
                available.len()
            )));
        }

        // Group by region
        let mut by_region: HashMap<Region, Vec<StorageNode>> = HashMap::new();
        for node in available {
            by_region.entry(node.region).or_default().push(node);
        }

        // Check region availability
        if by_region.len() < required_regions {
            return Err(Error::InsufficientNodes(format!(
                "Need {} regions, only {} available",
                required_regions,
                by_region.len()
            )));
        }

        // Select nodes with region distribution
        let mut selected = Vec::new();
        let mut regions_used = HashSet::new();

        // First pass: one node per region until we meet region requirement
        for (region, mut region_nodes) in by_region.clone() {
            if selected.len() >= required_copies {
                break;
            }
            if regions_used.len() < required_regions || regions_used.contains(&region) {
                // Sort by latency (prefer fastest)
                region_nodes.sort_by_key(|n| n.latency_ms);
                if let Some(node) = region_nodes.first() {
                    selected.push(node.clone());
                    regions_used.insert(region);
                }
            }
        }

        // Second pass: fill remaining slots
        for (region, mut region_nodes) in by_region {
            if selected.len() >= required_copies {
                break;
            }
            region_nodes.sort_by_key(|n| n.latency_ms);
            for node in region_nodes {
                if selected.len() >= required_copies {
                    break;
                }
                // Avoid duplicate nodes
                if !selected.iter().any(|n: &StorageNode| n.id == node.id) {
                    selected.push(node);
                }
            }
        }

        Ok(selected)
    }

    /// Get pin summary for a CID
    pub fn get_pin_status(&self, cid: &ContentId) -> Option<PinSummary> {
        let pins = self.pins.read();
        let cid_str = cid.to_string_repr();

        pins.get(&cid_str).map(|records: &Vec<PinRecord>| {
            let mut regions = HashSet::new();
            let nodes = self.nodes.read();

            for record in records {
                if let Some(node) = nodes.get(&record.node_id) {
                    regions.insert(node.region);
                }
            }

            let healthy_pins = records
                .iter()
                .filter(|p: &&PinRecord| p.is_healthy(self.config.max_verification_age))
                .count();

            PinSummary {
                cid: cid.clone(),
                pin_count: records.len(),
                healthy_pins,
                regions: regions.into_iter().collect(),
                redundancy_met: healthy_pins >= self.config.default_redundancy.min_copies(),
                redundancy_level: self.config.default_redundancy,
                pins: records.clone(),
            }
        })
    }

    /// Unpin content from all nodes
    pub fn unpin(&self, cid: &ContentId) -> Result<()> {
        let mut pins = self.pins.write();
        let cid_str = cid.to_string_repr();

        if let Some(records) = pins.remove(&cid_str) {
            let records: Vec<PinRecord> = records;
            let mut stats = self.stats.write();
            stats.active_pins = stats.active_pins.saturating_sub(records.len() as u64);
            for record in &records {
                stats.total_bytes = stats.total_bytes.saturating_sub(record.size);
            }
        }

        Ok(())
    }

    /// Get overall statistics
    pub fn stats(&self) -> PinStats {
        self.stats.read().clone()
    }

    /// Check health of all pins
    pub async fn health_check(&self) -> Vec<String> {
        let mut unhealthy_cids: Vec<String> = Vec::new();
        let pins = self.pins.read();

        for (cid_str, records) in pins.iter() {
            let records: &Vec<PinRecord> = records;
            let healthy_count = records
                .iter()
                .filter(|p: &&PinRecord| p.is_healthy(self.config.max_verification_age))
                .count();

            if healthy_count < self.config.default_redundancy.min_copies() {
                unhealthy_cids.push(cid_str.clone());
            }
        }

        unhealthy_cids
    }
}

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

    #[test]
    fn test_redundancy_levels() {
        assert_eq!(RedundancyLevel::None.min_copies(), 1);
        assert_eq!(RedundancyLevel::Standard.min_copies(), 3);
        assert_eq!(RedundancyLevel::Enhanced.min_copies(), 5);
        assert_eq!(RedundancyLevel::Critical.min_copies(), 7);

        assert!(!RedundancyLevel::Standard.requires_geo_distribution());
        assert!(RedundancyLevel::Enhanced.requires_geo_distribution());
    }

    #[test]
    fn test_storage_node() {
        let node = StorageNode::new("node-1", "https://node1.example.com", Region::NorthAmerica);

        assert!(node.healthy);
        assert_eq!(node.available(), 100 * 1024 * 1024 * 1024);
        assert!(node.can_store(1024));
    }

    #[test]
    fn test_pin_record() {
        let cid = ContentId::from_content(b"test content");
        let mut record = PinRecord::new(cid, "node-1", 1024);

        assert_eq!(record.status, PinStatus::Queued);

        record.mark_pinned();
        assert_eq!(record.status, PinStatus::Pinned);
        assert_eq!(record.verification_count, 1);
    }

    #[test]
    fn test_pin_manager() {
        let manager = PinManager::default_manager();

        // Register nodes
        manager.register_node(StorageNode::new("na-1", "https://na1.example.com", Region::NorthAmerica));
        manager.register_node(StorageNode::new("eu-1", "https://eu1.example.com", Region::Europe));
        manager.register_node(StorageNode::new("ap-1", "https://ap1.example.com", Region::AsiaPacific));

        let healthy = manager.healthy_nodes();
        assert_eq!(healthy.len(), 3);

        let na_nodes = manager.nodes_by_region(Region::NorthAmerica);
        assert_eq!(na_nodes.len(), 1);
    }

    #[test]
    fn test_region_codes() {
        assert_eq!(Region::NorthAmerica.code(), "NA");
        assert_eq!(Region::Europe.code(), "EU");
        assert_eq!(Region::AsiaPacific.code(), "AP");
    }

    #[test]
    fn test_pin_request() {
        let cid = ContentId::from_content(b"pin request test");
        let request = PinRequest::new(cid.clone(), RedundancyLevel::Enhanced)
            .with_regions(vec![Region::NorthAmerica, Region::Europe]);

        assert_eq!(request.redundancy, RedundancyLevel::Enhanced);
        assert_eq!(request.preferred_regions.len(), 2);
    }
}
