//! Dynamic SNI management and emergency updates

use serde::{Deserialize, Serialize};
use std::collections::{HashMap, HashSet};
use std::sync::Arc;
use tokio::sync::RwLock;
use chrono::{DateTime, Utc};

/// SNI update command
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SniUpdate {
    pub remove: Vec<String>,
    pub add: Vec<String>,
    #[serde(default)]
    pub country_code: Option<String>,
    #[serde(default)]
    pub emergency: bool,
}

/// SNI entry with metadata
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SniEntry {
    pub domain: String,
    pub country_code: String,
    pub added_at: DateTime<Utc>,
    pub blocked: bool,
    pub priority: u8, // Higher = preferred
}

/// Dynamic SNI manager for control plane
pub struct SniManager {
    /// Global SNI list
    #[allow(dead_code)]
    global_snis: Arc<RwLock<Vec<SniEntry>>>,
    /// Country-specific SNIs
    country_snis: Arc<RwLock<HashMap<String, Vec<SniEntry>>>>,
    /// Blocked domains (emergency blacklist)
    blocked_domains: Arc<RwLock<HashSet<String>>>,
    /// Pending updates for nodes to fetch
    pending_updates: Arc<RwLock<Vec<SniUpdate>>>,
}

impl SniManager {
    pub fn new() -> Self {
        let manager = Self {
            global_snis: Arc::new(RwLock::new(Vec::new())),
            country_snis: Arc::new(RwLock::new(HashMap::new())),
            blocked_domains: Arc::new(RwLock::new(HashSet::new())),
            pending_updates: Arc::new(RwLock::new(Vec::new())),
        };
        
        // Initialize with default domains
        tokio::spawn(async move {
            // This would be initialized synchronously in practice
        });
        
        manager
    }

    /// Initialize with default SNI mappings
    pub async fn init_defaults(&self) {
        let defaults = vec![
            ("RU", vec!["gosuslugi.ru", "sberbank.ru", "yandex.ru", "vk.com", "mail.ru"]),
            ("US", vec!["apple.com", "microsoft.com", "amazon.com", "google.com", "cloudflare.com"]),
            ("DE", vec!["sparkasse.de", "deutsche-bank.de", "bund.de", "spiegel.de"]),
            ("NO", vec!["bankid.no", "vipps.no", "altinn.no", "vg.no", "nrk.no"]),
            ("CN", vec!["qq.com", "baidu.com", "taobao.com", "weibo.com"]),
        ];
        
        let mut country_snis = self.country_snis.write().await;
        
        for (country, domains) in defaults {
            let entries: Vec<SniEntry> = domains.into_iter().map(|d| SniEntry {
                domain: d.to_string(),
                country_code: country.to_string(),
                added_at: Utc::now(),
                blocked: false,
                priority: 50,
            }).collect();
            
            country_snis.insert(country.to_string(), entries);
        }
    }

    /// Apply an SNI update
    pub async fn apply_update(&self, update: SniUpdate) {
        // Add to blocked list
        {
            let mut blocked = self.blocked_domains.write().await;
            for domain in &update.remove {
                blocked.insert(domain.clone());
            }
        }
        
        // Add new domains
        if !update.add.is_empty() {
            let country = update.country_code.clone().unwrap_or_else(|| "GLOBAL".to_string());
            let mut country_snis = self.country_snis.write().await;
            
            let entries = country_snis.entry(country.clone()).or_insert_with(Vec::new);
            
            for domain in &update.add {
                entries.push(SniEntry {
                    domain: domain.clone(),
                    country_code: country.clone(),
                    added_at: Utc::now(),
                    blocked: false,
                    priority: if update.emergency { 100 } else { 50 },
                });
            }
        }
        
        // Store update for nodes to fetch
        self.pending_updates.write().await.push(update);
        
        tracing::info!("SNI update applied");
    }

    /// Get active SNIs for a country
    pub async fn get_snis_for_country(&self, country: &str) -> Vec<String> {
        let country_snis = self.country_snis.read().await;
        let blocked = self.blocked_domains.read().await;
        
        country_snis
            .get(country)
            .map(|entries| {
                entries
                    .iter()
                    .filter(|e| !e.blocked && !blocked.contains(&e.domain))
                    .map(|e| e.domain.clone())
                    .collect()
            })
            .unwrap_or_default()
    }

    /// Get all active SNIs
    pub async fn get_active_snis(&self) -> Vec<String> {
        let country_snis = self.country_snis.read().await;
        let blocked = self.blocked_domains.read().await;
        
        country_snis
            .values()
            .flatten()
            .filter(|e| !e.blocked && !blocked.contains(&e.domain))
            .map(|e| e.domain.clone())
            .collect()
    }

    /// Get pending updates for nodes
    pub async fn get_pending_updates(&self) -> Vec<SniUpdate> {
        self.pending_updates.read().await.clone()
    }

    /// Clear pending updates after nodes have fetched them
    pub async fn clear_pending_updates(&self) {
        self.pending_updates.write().await.clear();
    }

    /// Check if domain is blocked
    pub async fn is_blocked(&self, domain: &str) -> bool {
        self.blocked_domains.read().await.contains(domain)
    }

    /// Block a domain immediately
    pub async fn block_domain(&self, domain: String) {
        self.blocked_domains.write().await.insert(domain.clone());
        
        // Create emergency update for nodes
        let update = SniUpdate {
            remove: vec![domain],
            add: vec![],
            country_code: None,
            emergency: true,
        };
        
        self.pending_updates.write().await.push(update);
    }

    /// Unblock a domain
    pub async fn unblock_domain(&self, domain: &str) {
        self.blocked_domains.write().await.remove(domain);
    }

    /// Get statistics
    pub async fn stats(&self) -> SniStats {
        let country_snis = self.country_snis.read().await;
        let blocked = self.blocked_domains.read().await;
        
        let total_domains: usize = country_snis.values().map(|v| v.len()).sum();
        let countries = country_snis.len();
        
        SniStats {
            total_domains,
            blocked_domains: blocked.len(),
            countries,
            pending_updates: self.pending_updates.read().await.len(),
        }
    }
}

impl Default for SniManager {
    fn default() -> Self {
        Self::new()
    }
}

/// SNI statistics
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SniStats {
    pub total_domains: usize,
    pub blocked_domains: usize,
    pub countries: usize,
    pub pending_updates: usize,
}

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

    #[tokio::test]
    async fn test_sni_manager() {
        let manager = SniManager::new();
        manager.init_defaults().await;
        
        let ru_snis = manager.get_snis_for_country("RU").await;
        assert!(!ru_snis.is_empty());
        assert!(ru_snis.contains(&"yandex.ru".to_string()));
    }

    #[tokio::test]
    async fn test_block_domain() {
        let manager = SniManager::new();
        manager.init_defaults().await;
        
        // Block a domain
        manager.block_domain("yandex.ru".to_string()).await;
        
        // Should no longer appear in active list
        let ru_snis = manager.get_snis_for_country("RU").await;
        assert!(!ru_snis.contains(&"yandex.ru".to_string()));
        
        // Should have pending update
        let updates = manager.get_pending_updates().await;
        assert!(!updates.is_empty());
    }
}
