//! WinTUN driver extraction and management
//!
//! Extracts wintun.dll from embedded resources and installs the TUN adapter.

use std::path::PathBuf;
use anyhow::{Context, Result};
use std::fs;

/// Embedded wintun.dll (x64) - placeholder, actual DLL is in resources
const WINTUN_DLL: &[u8] = include_bytes!("../resources/wintun.dll");

/// WinTUN adapter GUID (consistent across installs)
#[allow(dead_code)]
const ADAPTER_GUID: &str = "{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}";

/// TUN adapter name
#[allow(dead_code)]
const ADAPTER_NAME: &str = "ostp TUN";

/// Extract wintun.dll to installation directory
pub async fn extract_driver(install_path: &PathBuf) -> Result<()> {
    let dll_path = install_path.join("wintun.dll");
    
    // Create directory if needed
    fs::create_dir_all(install_path)
        .context("Failed to create installation directory")?;
    
    // Check if already exists with correct version
    if dll_path.exists() {
        let existing = fs::read(&dll_path)?;
        if existing == WINTUN_DLL {
            return Ok(()); // Already up to date
        }
    }
    
    // Write the DLL
    fs::write(&dll_path, WINTUN_DLL)
        .context("Failed to write wintun.dll")?;
    
    Ok(())
}

/// Create the TUN adapter using wintun API
#[allow(dead_code)]
pub async fn create_adapter(install_path: &PathBuf) -> Result<()> {
    // Load wintun.dll dynamically
    let dll_path = install_path.join("wintun.dll");
    
    if !dll_path.exists() {
        anyhow::bail!("wintun.dll not found at {:?}", dll_path);
    }
    
    // Use libloading to call WintunCreateAdapter
    // This is a simplified version - full implementation would use the wintun crate
    
    #[cfg(windows)]
    {
        use std::ffi::OsStr;
        use std::os::windows::ffi::OsStrExt;
        
        // Convert strings to wide strings for Windows API
        let _adapter_name: Vec<u16> = OsStr::new(ADAPTER_NAME)
            .encode_wide()
            .chain(std::iter::once(0))
            .collect();
        
        let _tunnel_type: Vec<u16> = OsStr::new("Ospab")
            .encode_wide()
            .chain(std::iter::once(0))
            .collect();
        
        // Parse GUID
        let _guid = parse_guid(ADAPTER_GUID)?;
        
        // In production, we'd call:
        // WintunCreateAdapter(adapter_name, tunnel_type, &guid)
        // For now, we'll use the wintun crate when it's integrated
        
        tracing::info!("TUN adapter creation would happen here");
    }
    
    Ok(())
}

/// Delete the TUN adapter
pub async fn delete_adapter() -> Result<()> {
    #[cfg(windows)]
    {
        // WintunDeleteAdapter would be called here
        tracing::info!("TUN adapter deletion would happen here");
    }
    
    Ok(())
}

/// Remove wintun driver files
pub async fn remove_driver() -> Result<()> {
    // Delete adapter first
    delete_adapter().await?;
    
    // Find and remove DLL from common locations
    let paths = [
        PathBuf::from(r"C:\Program Files\Ospab Network\OSTP\wintun.dll"),
    ];
    
    for path in &paths {
        if path.exists() {
            fs::remove_file(path).ok();
        }
    }
    
    Ok(())
}

/// Check if wintun is installed and working
pub fn is_installed() -> bool {
    let system_dll = PathBuf::from(r"C:\Program Files\Ospab Network\OSTP\wintun.dll");
    system_dll.exists()
}

/// Get wintun version from DLL
#[allow(dead_code)]
pub fn get_version() -> Option<String> {
    // Would extract version info from PE header
    // For now, return embedded version
    Some("0.14.1".to_string())
}

/// Parse a GUID string into bytes
#[cfg(windows)]
#[allow(dead_code)]
fn parse_guid(s: &str) -> Result<[u8; 16]> {
    let s = s.trim_matches(|c| c == '{' || c == '}');
    let parts: Vec<&str> = s.split('-').collect();
    
    if parts.len() != 5 {
        anyhow::bail!("Invalid GUID format");
    }
    
    let mut guid = [0u8; 16];
    
    // Parse each part
    let d1 = u32::from_str_radix(parts[0], 16)?;
    let d2 = u16::from_str_radix(parts[1], 16)?;
    let d3 = u16::from_str_radix(parts[2], 16)?;
    let d4 = u16::from_str_radix(parts[3], 16)?;
    let d5 = u64::from_str_radix(parts[4], 16)?;
    
    guid[0..4].copy_from_slice(&d1.to_le_bytes());
    guid[4..6].copy_from_slice(&d2.to_le_bytes());
    guid[6..8].copy_from_slice(&d3.to_le_bytes());
    guid[8..10].copy_from_slice(&d4.to_be_bytes());
    guid[10..16].copy_from_slice(&d5.to_be_bytes()[2..8]);
    
    Ok(guid)
}

#[cfg(test)]
mod tests {
    use super::*;
    
    #[test]
    #[cfg(windows)]
    fn test_parse_guid() {
        let guid = parse_guid(ADAPTER_GUID).unwrap();
        assert_eq!(guid.len(), 16);
    }
}
