//! # orb-browse
//!
//! A TUI browser widget for Rust with WebDriver automation and bot detection bypass.
//!
//! **orb-browse** provides a reusable browser component that can be embedded in terminal
//! applications using ratatui, with the unique ability to bypass bot detection systems.
//!
//! ## Features
//!
//! - 🌐 **WebDriver Automation** - Full browser control via W3C WebDriver
//! - 🥷 **Bot Detection Bypass** - Patches ChromeDriver to avoid detection
//! - 🎨 **TUI Widget** - Embeddable ratatui widget for terminal apps
//! - 📸 **Screenshot Capture** - Capture web pages as images
//! - 🚀 **Auto-Setup** - Downloads and configures ChromeDriver automatically
//!
//! ## Quick Start
//!
//! ```no_run
//! use orb_browse::OrbBrowser;
//!
//! #[tokio::main]
//! async fn main() -> Result<(), Box<dyn std::error::Error>> {
//!     // Create a browser instance
//!     let browser = OrbBrowser::new().await?;
//!
//!     // Navigate and capture screenshot
//!     let screenshot = browser.capture("https://google.com", 1920, 1080).await?;
//!
//!     // Save screenshot
//!     std::fs::write("google.png", &screenshot)?;
//!
//!     Ok(())
//! }
//! ```
//!
//! ## Use as TUI Widget
//!
//! ```no_run
//! use orb_browse::{OrbBrowser, widget::{BrowserWidget, BrowserState}};
//!
//! # async fn example() -> Result<(), Box<dyn std::error::Error>> {
//! let browser = OrbBrowser::new().await?;
//! let mut state = BrowserState::new();
//!
//! // Navigate to a URL
//! state.navigate(&browser, "https://example.com".to_string()).await?;
//!
//! // In your ratatui render loop:
//! // frame.render_widget(BrowserWidget::new(&state), area);
//! # Ok(())
//! # }
//! ```
//!
//! ## How It Works
//!
//! 1. **Binary Patching**: Removes `$cdc_` markers from ChromeDriver
//! 2. **Automation Bypass Flags**: Disables `--enable-automation` and automation hints
//! 3. **JavaScript Injection**: Overrides `navigator.webdriver` and mocks APIs
//! 4. **WebDriver Protocol**: Uses W3C WebDriver (not detectable CDP)
//!
//! This achieves functional parity with Python's `undetected-chromedriver`.

pub mod patcher;
pub mod injections;

#[cfg(feature = "webdriver")]
pub mod client;

#[cfg(feature = "tui")]
pub mod widget;

// Re-exports for convenience
pub use patcher::ChromeDriverPatcher;
pub use injections::COMPREHENSIVE_BOOTSTRAP;

#[cfg(feature = "webdriver")]
pub use client::OrbBrowser;

use color_eyre::Result;
use std::path::PathBuf;
use std::process::{Command, Child};
use std::fs;
use std::net::TcpListener;

/// Get the installed Chrome/Chromium version
fn get_chrome_version() -> Result<String> {
    let chrome_path = find_chrome()
        .ok_or_else(|| color_eyre::eyre::eyre!("Chrome/Chromium not found on system"))?;

    let output = Command::new(&chrome_path)
        .arg("--version")
        .output()
        .map_err(|e| color_eyre::eyre::eyre!("Failed to get Chrome version: {}", e))?;

    let version_str = String::from_utf8(output.stdout)
        .map_err(|e| color_eyre::eyre::eyre!("Failed to parse Chrome version: {}", e))?;

    // Parse version string like "Chromium 143.0.7499.192" or "Google Chrome 131.0.6778.204"
    let version = version_str
        .split_whitespace()
        .nth(1)
        .and_then(|v| v.split('.').next())
        .ok_or_else(|| color_eyre::eyre::eyre!("Failed to parse Chrome version from: {}", version_str.trim()))?;

    Ok(version.to_string())
}

/// Get the full ChromeDriver version string for a given Chrome major version
fn get_chromedriver_version(chrome_major: &str) -> Result<String> {
    // Query the Chrome for Testing JSON endpoint to get the latest stable version
    let url = format!(
        "https://googlechromelabs.github.io/chrome-for-testing/latest-versions-per-milestone-with-downloads.json"
    );

    let output = Command::new("curl")
        .arg("-sL")
        .arg(&url)
        .output()
        .map_err(|e| color_eyre::eyre::eyre!("Failed to fetch ChromeDriver version info: {}", e))?;

    if !output.status.success() {
        return Err(color_eyre::eyre::eyre!("Failed to fetch ChromeDriver version info"));
    }

    let json_str = String::from_utf8(output.stdout)
        .map_err(|e| color_eyre::eyre::eyre!("Failed to parse version JSON: {}", e))?;

    // Parse JSON to find the version for this milestone
    let json: serde_json::Value = serde_json::from_str(&json_str)
        .map_err(|e| color_eyre::eyre::eyre!("Failed to parse version JSON: {}", e))?;

    let version = json
        .get("milestones")
        .and_then(|m| m.get(chrome_major))
        .and_then(|v| v.get("version"))
        .and_then(|v| v.as_str())
        .ok_or_else(|| color_eyre::eyre::eyre!(
            "ChromeDriver version {} not found. Your Chrome version may be too new or too old. \
             Visit https://googlechromelabs.github.io/chrome-for-testing/ for available versions.",
            chrome_major
        ))?;

    Ok(version.to_string())
}

/// Download ChromeDriver for the current system
fn download_chromedriver() -> Result<PathBuf> {
    let cache_dir = dirs::cache_dir()
        .unwrap_or_else(|| PathBuf::from("/tmp"))
        .join("orb-browse/drivers");

    fs::create_dir_all(&cache_dir)?;

    // Detect installed Chrome version
    let chrome_major = get_chrome_version()?;

    // Get matching ChromeDriver version
    let driver_version = get_chromedriver_version(&chrome_major)?;

    // Version-specific driver path to cache different versions
    let driver_path = cache_dir.join(format!("chromedriver-{}", chrome_major));

    // If already exists for this version, return it
    if driver_path.exists() {
        return Ok(driver_path);
    }

    // Determine ChromeDriver download URL based on platform
    let (os, arch) = if cfg!(target_os = "linux") {
        if cfg!(target_arch = "x86_64") {
            ("linux64", "chromedriver-linux64")
        } else {
            return Err(color_eyre::eyre::eyre!("Unsupported architecture"));
        }
    } else if cfg!(target_os = "macos") {
        if cfg!(target_arch = "aarch64") {
            ("mac-arm64", "chromedriver-mac-arm64")
        } else {
            ("mac-x64", "chromedriver-mac-x64")
        }
    } else {
        return Err(color_eyre::eyre::eyre!("Unsupported OS"));
    };

    // Download matching ChromeDriver version
    let url = format!(
        "https://storage.googleapis.com/chrome-for-testing-public/{}/{}/chromedriver-{}.zip",
        driver_version, os, os
    );

    // Download with curl
    let zip_path = cache_dir.join(format!("chromedriver-{}.zip", chrome_major));
    let status = Command::new("curl")
        .arg("-L")
        .arg("-o")
        .arg(&zip_path)
        .arg(&url)
        .stdout(std::process::Stdio::null())
        .stderr(std::process::Stdio::null())
        .status()?;

    if !status.success() {
        return Err(color_eyre::eyre::eyre!(
            "Failed to download ChromeDriver {} for Chrome {}. \
             Check your internet connection or visit https://googlechromelabs.github.io/chrome-for-testing/",
            driver_version, chrome_major
        ));
    }

    // Extract with unzip
    let status = Command::new("unzip")
        .arg("-o")
        .arg(&zip_path)
        .arg("-d")
        .arg(&cache_dir)
        .stdout(std::process::Stdio::null())
        .stderr(std::process::Stdio::null())
        .status()?;

    if !status.success() {
        return Err(color_eyre::eyre::eyre!("Failed to extract ChromeDriver"));
    }

    // Move the binary to the expected location
    let extracted_path = cache_dir.join(arch).join("chromedriver");
    fs::rename(&extracted_path, &driver_path)?;

    // Make executable
    #[cfg(unix)]
    {
        use std::os::unix::fs::PermissionsExt;
        let mut perms = fs::metadata(&driver_path)?.permissions();
        perms.set_mode(0o755);
        fs::set_permissions(&driver_path, perms)?;
    }

    // Clean up
    let _ = fs::remove_file(&zip_path);
    let _ = fs::remove_dir_all(cache_dir.join(arch));

    // Clean up old cached drivers for different Chrome versions
    if let Ok(entries) = fs::read_dir(&cache_dir) {
        for entry in entries.flatten() {
            let path = entry.path();
            if let Some(name) = path.file_name().and_then(|n| n.to_str()) {
                // Remove old versioned drivers and the legacy "chromedriver" symlink
                if (name.starts_with("chromedriver-") && name != format!("chromedriver-{}", chrome_major))
                    || name == "chromedriver" {
                    let _ = fs::remove_file(&path);
                }
            }
        }
    }

    // Create a symlink for backward compatibility
    #[cfg(unix)]
    {
        let symlink_path = cache_dir.join("chromedriver");
        let _ = fs::remove_file(&symlink_path); // Remove if exists
        let _ = std::os::unix::fs::symlink(&driver_path, &symlink_path);
    }

    Ok(driver_path)
}

/// Find an available port for ChromeDriver
fn find_available_port() -> Result<u16> {
    let listener = TcpListener::bind("127.0.0.1:0")?;
    let port = listener.local_addr()?.port();
    Ok(port)
}

/// Launch patched ChromeDriver and return the WebDriver URL and process handle
///
/// This function:
/// 1. Downloads ChromeDriver if not present
/// 2. Patches it to remove $cdc_ markers
/// 3. Launches it on a random available port
/// 4. Returns the WebDriver endpoint URL and process handle
///
/// ## Example
///
/// ```no_run
/// use orb_browse::launch_patched_chromedriver;
///
/// # fn main() -> color_eyre::Result<()> {
/// let (webdriver_url, mut process) = launch_patched_chromedriver()?;
/// println!("ChromeDriver running at: {}", webdriver_url);
///
/// // ... use the WebDriver ...
///
/// // Clean up
/// process.kill()?;
/// # Ok(())
/// # }
/// ```
pub fn launch_patched_chromedriver() -> Result<(String, Child)> {
    // Download ChromeDriver
    let original_driver = download_chromedriver()?;

    // Patch it
    let patcher = ChromeDriverPatcher::new();
    let patched_driver = patcher.patch_driver(&original_driver)?;

    // Find available port
    let port = find_available_port()?;

    // Check if verbose mode is enabled via environment variable
    let verbose = std::env::var("ORB_BROWSE_VERBOSE").is_ok();

    // Launch ChromeDriver with optional verbose output
    let mut cmd = Command::new(&patched_driver);
    cmd.arg(format!("--port={}", port));

    if verbose {
        eprintln!("[orb-browse] Launching ChromeDriver on port {} with verbose output", port);
        cmd.arg("--verbose");
    } else {
        cmd.arg("--silent")
            .arg("--log-level=OFF")
            .stdout(std::process::Stdio::null())
            .stderr(std::process::Stdio::null());
    }

    let mut child = cmd
        .spawn()
        .map_err(|e| color_eyre::eyre::eyre!("Failed to launch ChromeDriver: {}", e))?;

    let webdriver_url = format!("http://localhost:{}", port);

    // Wait for ChromeDriver to become responsive (up to 3 seconds)
    let mut attempts = 0;
    let max_attempts = 30;
    loop {
        std::thread::sleep(std::time::Duration::from_millis(100));
        attempts += 1;

        // Check if ChromeDriver process is still alive
        match child.try_wait() {
            Ok(Some(status)) => {
                return Err(color_eyre::eyre::eyre!(
                    "ChromeDriver exited immediately with status: {}.\n\
                     This is often caused by:\n\
                     1. Missing dependencies (libnss3, libx11, etc.)\n\
                     2. Chrome/ChromeDriver version mismatch\n\
                     3. Chrome binary not executable\n\
                     \n\
                     Run with ORB_BROWSE_VERBOSE=1 to see detailed error output:\n\
                     ORB_BROWSE_VERBOSE=1 cargo run",
                    status
                ));
            }
            Ok(None) => {
                // Still running, try to connect
                if std::net::TcpStream::connect(format!("127.0.0.1:{}", port)).is_ok() {
                    if verbose {
                        eprintln!("[orb-browse] ChromeDriver ready on port {}", port);
                    }
                    return Ok((webdriver_url, child));
                }
            }
            Err(e) => {
                return Err(color_eyre::eyre::eyre!("Failed to check ChromeDriver status: {}", e));
            }
        }

        if attempts >= max_attempts {
            let _ = child.kill();
            return Err(color_eyre::eyre::eyre!(
                "ChromeDriver failed to become responsive after 3 seconds.\n\
                 The process is running but not accepting connections.\n\
                 Run with ORB_BROWSE_VERBOSE=1 to see detailed output:\n\
                 ORB_BROWSE_VERBOSE=1 cargo run"
            ));
        }
    }
}

/// Find Chrome/Chromium binary on the system
///
/// Searches common Chrome/Chromium installation locations.
/// You can override detection with the ORB_BROWSE_CHROME_PATH environment variable:
/// ```bash
/// export ORB_BROWSE_CHROME_PATH=/usr/bin/chromium-browser
/// ```
pub fn find_chrome() -> Option<PathBuf> {
    // Check environment variable override first
    if let Ok(chrome_path) = std::env::var("ORB_BROWSE_CHROME_PATH") {
        let p = PathBuf::from(chrome_path);
        if p.exists() {
            return Some(p);
        } else {
            eprintln!("Warning: ORB_BROWSE_CHROME_PATH points to non-existent file: {}", p.display());
        }
    }

    let candidates = vec![
        // Common installation paths
        "/usr/bin/chromium-browser",
        "/usr/bin/chromium",
        "/usr/bin/google-chrome",
        "/usr/bin/google-chrome-stable",
        "/usr/bin/chrome",
        "/snap/bin/chromium",
        "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
        "/Applications/Chromium.app/Contents/MacOS/Chromium",
    ];

    for candidate in candidates {
        let p = PathBuf::from(candidate);
        if p.exists() {
            return Some(p);
        }
    }

    None
}
