//! WebDriver client with bot detection bypass

use crate::{find_chrome, injections::COMPREHENSIVE_BOOTSTRAP, launch_patched_chromedriver};
use color_eyre::Result;
use fantoccini::ClientBuilder;
use serde_json::json;
use std::process::Child;
use base64::Engine;

/// A browser client that bypasses bot detection
///
/// This wraps fantoccini's WebDriver client with:
/// - Patched ChromeDriver (no $cdc_ markers)
/// - Automation bypass flags
/// - JavaScript injection to hide automation markers
///
/// ## Example
///
/// ```no_run
/// use orb_browse::OrbBrowser;
///
/// #[tokio::main]
/// async fn main() -> Result<(), Box<dyn std::error::Error>> {
///     let browser = OrbBrowser::new().await?;
///     let screenshot = browser.capture("https://google.com", 1920, 1080).await?;
///     std::fs::write("google.png", &screenshot)?;
///     Ok(())
/// }
/// ```
pub struct OrbBrowser {
    client: fantoccini::Client,
    _chromedriver: Child,
}

impl OrbBrowser {
    /// Create a new browser instance
    ///
    /// This will:
    /// 1. Download and patch ChromeDriver (if not already cached)
    /// 2. Launch ChromeDriver on a random port
    /// 3. Connect a WebDriver client with bot detection bypass
    pub async fn new() -> Result<Self> {
        Self::with_size(1920, 1080).await
    }

    /// Create a new browser with custom window size
    pub async fn with_size(width: u32, height: u32) -> Result<Self> {
        let chrome_path = find_chrome()
            .ok_or_else(|| color_eyre::eyre::eyre!("Chrome/Chromium not found"))?;

        // Launch patched ChromeDriver
        let (webdriver_url, chromedriver_process) = launch_patched_chromedriver()?;

        // Build Chrome capabilities with automation bypass flags
        let mut caps = serde_json::Map::new();
        caps.insert("browserName".to_string(), json!("chrome"));

        let mut chrome_options = serde_json::Map::new();
        chrome_options.insert("binary".to_string(), json!(chrome_path.to_str().unwrap()));

        let window_size_arg = format!("--window-size={},{}", width, height);
        let args = vec![
            "--disable-blink-features=AutomationControlled",
            "--disable-web-security",
            "--disable-dev-shm-usage",
            "--no-first-run",
            "--disable-infobars",
            "--disable-extensions",
            "--disable-gpu",
            "--no-sandbox",
            "--disable-setuid-sandbox",
            window_size_arg.as_str(),
            "--user-agent=Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36",
        ];
        chrome_options.insert("args".to_string(), json!(args));

        // Exclude automation switches
        let excluded_switches = vec!["enable-automation", "enable-logging"];
        chrome_options.insert("excludeSwitches".to_string(), json!(excluded_switches));

        // Add prefs to hide automation
        let prefs = json!({
            "credentials_enable_service": false,
            "profile.password_manager_enabled": false,
        });
        chrome_options.insert("prefs".to_string(), prefs);

        caps.insert("goog:chromeOptions".to_string(), json!(chrome_options));

        // Connect to ChromeDriver
        let client = ClientBuilder::native()
            .capabilities(caps)
            .connect(&webdriver_url)
            .await
            .map_err(|e| color_eyre::eyre::eyre!("Failed to connect to ChromeDriver: {}", e))?;

        Ok(Self {
            client,
            _chromedriver: chromedriver_process,
        })
    }

    /// Navigate to a URL
    pub async fn goto(&self, url: &str) -> Result<()> {
        self.client
            .goto(url)
            .await
            .map_err(|e| color_eyre::eyre::eyre!("Failed to navigate: {}", e))?;

        // Inject bypass script after navigation
        if !url.starts_with("file://") {
            let _ = self
                .client
                .execute(COMPREHENSIVE_BOOTSTRAP, vec![])
                .await;
        }

        Ok(())
    }

    /// Capture a screenshot of a URL
    ///
    /// Returns PNG bytes
    pub async fn capture(&self, url: &str, _width: u32, _height: u32) -> Result<Vec<u8>> {
        // Inject bypass script before navigation
        if !url.starts_with("file://") {
            let _ = self
                .client
                .execute(COMPREHENSIVE_BOOTSTRAP, vec![])
                .await;
        }

        self.goto(url).await?;

        // Wait for page load
        tokio::time::sleep(tokio::time::Duration::from_millis(1000)).await;

        // Inject again after page load (some sites re-check)
        if !url.starts_with("file://") {
            let _ = self
                .client
                .execute(COMPREHENSIVE_BOOTSTRAP, vec![])
                .await;
        }

        // Capture screenshot
        let screenshot_b64 = self
            .client
            .screenshot()
            .await
            .map_err(|e| color_eyre::eyre::eyre!("Failed to capture screenshot: {}", e))?;

        // Decode base64
        let screenshot_bytes = base64::prelude::BASE64_STANDARD
            .decode(&screenshot_b64)
            .map_err(|e| color_eyre::eyre::eyre!("Failed to decode screenshot: {}", e))?;

        Ok(screenshot_bytes)
    }

    /// Get the underlying fantoccini client for advanced usage
    pub fn client(&self) -> &fantoccini::Client {
        &self.client
    }

    /// Close the browser
    pub async fn close(mut self) -> Result<()> {
        let _ = self.client.close().await;
        let _ = self._chromedriver.kill();
        Ok(())
    }
}
