//! Dynamic SNI & TLS Mimicry Engine

use std::collections::HashMap;
use std::sync::Arc;
use tokio::sync::RwLock;

/// Geo-based SNI target selection with dynamic updates
pub struct MimicryEngine {
    geo_sni_map: HashMap<String, Vec<String>>,
    /// Blocked domains (received from control plane)
    blocked: Arc<RwLock<std::collections::HashSet<String>>>,
}

impl MimicryEngine {
    pub fn new() -> Self {
        let mut geo_sni_map = HashMap::new();

        // Default geo-SNI mappings for contextual mimicry
        geo_sni_map.insert(
            "RU".into(),
            vec![
                "gosuslugi.ru".into(),
                "sberbank.ru".into(),
                "yandex.ru".into(),
            ],
        );
        geo_sni_map.insert(
            "NO".into(),
            vec!["bankid.no".into(), "vipps.no".into(), "altinn.no".into()],
        );
        geo_sni_map.insert(
            "DE".into(),
            vec![
                "sparkasse.de".into(),
                "deutsche-bank.de".into(),
                "bund.de".into(),
            ],
        );
        geo_sni_map.insert(
            "US".into(),
            vec![
                "apple.com".into(),
                "microsoft.com".into(),
                "amazon.com".into(),
            ],
        );
        geo_sni_map.insert(
            "CN".into(),
            vec!["qq.com".into(), "baidu.com".into(), "taobao.com".into()],
        );

        Self { 
            geo_sni_map,
            blocked: Arc::new(RwLock::new(std::collections::HashSet::new())),
        }
    }

    /// Select SNI based on geo-location
    pub fn select_sni(&self, country_code: &str) -> Option<&str> {
        self.geo_sni_map
            .get(country_code)
            .and_then(|list| list.first().map(|s| s.as_str()))
    }

    /// Get random SNI from geo list for anti-fingerprinting
    pub fn random_sni(&self, country_code: &str) -> Option<&str> {
        self.geo_sni_map.get(country_code).and_then(|list| {
            if list.is_empty() {
                None
            } else {
                let idx = rand::random::<usize>() % list.len();
                Some(list[idx].as_str())
            }
        })
    }

    /// Add custom geo-SNI mapping
    pub fn add_mapping(&mut self, country: String, domains: Vec<String>) {
        self.geo_sni_map.insert(country, domains);
    }

    /// Update SNI list from control plane (async)
    pub async fn update_from_control_plane(&mut self, country: String, domains: Vec<String>) {
        self.geo_sni_map.insert(country, domains);
    }

    /// Block a domain (emergency update from control plane)
    pub async fn block_domain(&self, domain: &str) {
        self.blocked.write().await.insert(domain.to_string());
    }

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

    /// Get safe random SNI (excludes blocked domains)
    pub async fn safe_random_sni(&self, country_code: &str) -> Option<String> {
        let blocked = self.blocked.read().await;
        
        self.geo_sni_map.get(country_code).and_then(|list| {
            let safe: Vec<&String> = list.iter()
                .filter(|d| !blocked.contains(*d))
                .collect();
            
            if safe.is_empty() {
                None
            } else {
                let idx = rand::random::<usize>() % safe.len();
                Some(safe[idx].clone())
            }
        })
    }

    /// Apply bulk SNI update
    pub async fn apply_update(&mut self, update: SniUpdate) {
        // Block removed domains
        {
            let mut blocked = self.blocked.write().await;
            for domain in &update.remove {
                blocked.insert(domain.clone());
            }
        }
        
        // Add new domains
        if !update.add.is_empty() {
            let country = update.country.unwrap_or_else(|| "GLOBAL".to_string());
            let entry = self.geo_sni_map.entry(country).or_insert_with(Vec::new);
            for domain in update.add {
                if !entry.contains(&domain) {
                    entry.push(domain);
                }
            }
        }
    }

    /// Get all available SNIs for a country (excluding blocked)
    pub async fn get_available_snis(&self, country_code: &str) -> Vec<String> {
        let blocked = self.blocked.read().await;
        
        self.geo_sni_map
            .get(country_code)
            .map(|list| {
                list.iter()
                    .filter(|d| !blocked.contains(*d))
                    .cloned()
                    .collect()
            })
            .unwrap_or_default()
    }
}

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

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

/// TLS ClientHello builder for REALITY-like mimicry
pub struct TlsHelloBuilder {
    sni: String,
    random_session_id: bool,
}

impl TlsHelloBuilder {
    pub fn new(sni: impl Into<String>) -> Self {
        Self {
            sni: sni.into(),
            random_session_id: true,
        }
    }

    /// Build minimal TLS 1.3 ClientHello-like header
    pub fn build(&self) -> Vec<u8> {
        let mut hello = Vec::with_capacity(256);

        // TLS record header
        hello.push(0x16); // Handshake
        hello.extend_from_slice(&[0x03, 0x01]); // TLS 1.0 for compat

        // Placeholder for length (will update)
        let len_pos = hello.len();
        hello.extend_from_slice(&[0x00, 0x00]);

        // Handshake header
        hello.push(0x01); // ClientHello
        let hs_len_pos = hello.len();
        hello.extend_from_slice(&[0x00, 0x00, 0x00]); // length placeholder

        // Client version (TLS 1.2 presented, 1.3 in extensions)
        hello.extend_from_slice(&[0x03, 0x03]);

        // Random (32 bytes)
        let random: [u8; 32] = rand::random();
        hello.extend_from_slice(&random);

        // Session ID (32 bytes if random)
        if self.random_session_id {
            hello.push(32);
            let session_id: [u8; 32] = rand::random();
            hello.extend_from_slice(&session_id);
        } else {
            hello.push(0);
        }

        // Cipher suites (TLS 1.3 suites)
        hello.extend_from_slice(&[0x00, 0x06]); // 3 suites
        hello.extend_from_slice(&[0x13, 0x01]); // TLS_AES_128_GCM_SHA256
        hello.extend_from_slice(&[0x13, 0x02]); // TLS_AES_256_GCM_SHA384
        hello.extend_from_slice(&[0x13, 0x03]); // TLS_CHACHA20_POLY1305_SHA256

        // Compression (null)
        hello.extend_from_slice(&[0x01, 0x00]);

        // Extensions with SNI
        let ext_start = hello.len();
        hello.extend_from_slice(&[0x00, 0x00]); // ext length placeholder

        // SNI extension
        hello.extend_from_slice(&[0x00, 0x00]); // type: server_name
        let sni_bytes = self.sni.as_bytes();
        let sni_ext_len = sni_bytes.len() + 5;
        hello.extend_from_slice(&(sni_ext_len as u16).to_be_bytes());
        hello.extend_from_slice(&((sni_bytes.len() + 3) as u16).to_be_bytes());
        hello.push(0x00); // host_name type
        hello.extend_from_slice(&(sni_bytes.len() as u16).to_be_bytes());
        hello.extend_from_slice(sni_bytes);

        // supported_versions extension
        hello.extend_from_slice(&[0x00, 0x2b]); // type
        hello.extend_from_slice(&[0x00, 0x03]); // length
        hello.push(0x02); // versions length
        hello.extend_from_slice(&[0x03, 0x04]); // TLS 1.3

        // Update lengths
        let ext_len = hello.len() - ext_start - 2;
        hello[ext_start] = (ext_len >> 8) as u8;
        hello[ext_start + 1] = ext_len as u8;

        let total_len = hello.len() - 5;
        hello[len_pos] = (total_len >> 8) as u8;
        hello[len_pos + 1] = total_len as u8;

        let hs_len = hello.len() - hs_len_pos - 3;
        hello[hs_len_pos] = 0;
        hello[hs_len_pos + 1] = (hs_len >> 8) as u8;
        hello[hs_len_pos + 2] = hs_len as u8;

        hello
    }
}
