//! ChromeDriver binary patcher
//!
//! Patches ChromeDriver binaries to remove automation markers that
//! bot detection systems look for, specifically $cdc_ variables.

use color_eyre::Result;
use std::fs;
use std::path::{Path, PathBuf};

/// ChromeDriver binary patcher that removes automation markers
pub struct ChromeDriverPatcher {
    patterns: Vec<Vec<u8>>,
    cache_dir: PathBuf,
}

impl ChromeDriverPatcher {
    /// Create a new patcher instance
    pub fn new() -> Self {
        let cache_dir = dirs::cache_dir()
            .unwrap_or_else(|| PathBuf::from("/tmp"))
            .join("lazywitness")
            .join("drivers");

        Self {
            patterns: vec![
                // Common $cdc_ patterns from ChromeDriver
                b"cdc_adoQpoasnfa76pfcZLmcfl_Array".to_vec(),
                b"cdc_adoQpoasnfa76pfcZLmcfl_Promise".to_vec(),
                b"cdc_adoQpoasnfa76pfcZLmcfl_Symbol".to_vec(),
                b"$cdc_asdjflasutopfhvcZLmcfl_".to_vec(),
                b"window.cdc_".to_vec(),
            ],
            cache_dir,
        }
    }

    /// Patch a ChromeDriver binary and return path to patched version
    pub fn patch_driver(&self, driver_path: &Path) -> Result<PathBuf> {
        // Create cache directory
        fs::create_dir_all(&self.cache_dir)?;

        // Read the driver binary
        let mut data = fs::read(driver_path)?;
        let original_len = data.len();

        // Find and replace all patterns
        let mut total_replacements = 0;
        for pattern in &self.patterns {
            let count = self.replace_pattern(&mut data, pattern);
            total_replacements += count;
        }

        println!("Patched ChromeDriver: {} replacements in {} bytes",
                 total_replacements, original_len);

        // Save to cache with unique name based on original path
        let original_name = driver_path
            .file_name()
            .and_then(|n| n.to_str())
            .unwrap_or("chromedriver");
        let patched_path = self.cache_dir.join(format!("{}-patched", original_name));

        fs::write(&patched_path, &data)?;

        // Make executable on Unix
        #[cfg(unix)]
        {
            use std::os::unix::fs::PermissionsExt;
            fs::set_permissions(&patched_path, fs::Permissions::from_mode(0o755))?;
        }

        Ok(patched_path)
    }

    /// Replace all occurrences of a pattern with spaces (preserves binary offsets)
    fn replace_pattern(&self, data: &mut [u8], pattern: &[u8]) -> usize {
        use memchr::memmem;

        let mut count = 0;
        let finder = memmem::Finder::new(pattern);
        let mut start = 0;

        while let Some(pos) = finder.find(&data[start..]) {
            let absolute_pos = start + pos;

            // Replace with spaces to maintain binary offsets
            for i in 0..pattern.len() {
                data[absolute_pos + i] = b' ';
            }

            count += 1;
            start = absolute_pos + pattern.len();
        }

        count
    }

    /// Get the cache directory path
    pub fn cache_dir(&self) -> &Path {
        &self.cache_dir
    }
}

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

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

    #[test]
    fn test_pattern_replacement() {
        let patcher = ChromeDriverPatcher::new();
        let mut data = b"window.cdc_test and window.cdc_another".to_vec();

        let count = patcher.replace_pattern(&mut data, b"window.cdc_");

        assert_eq!(count, 2);
        assert!(data.iter().filter(|&&b| b == b' ').count() > 0);
    }
}
