use chromiumoxide::{Browser, BrowserConfig};
use futures::StreamExt;
use std::time::Duration;
use tokio::time::{sleep, timeout};

const POLL_INTERVAL: Duration = Duration::from_millis(500);
const DEFAULT_TIMEOUT: Duration = Duration::from_secs(30);

/// Launches a headless Chrome browser, navigates to `url`, and polls until the
/// element matched by `xpath` has a non-empty `src` attribute, then returns it.
///
/// Equivalent to Selenium's:
///   `WebDriverWait(driver, 30).until(lambda d: d.find_element(By.XPATH, xpath).get_attribute("src"))`
pub async fn wait_for_src(url: &str, xpath: &str) -> Option<String> {
    wait_for_attribute(url, xpath, "src", DEFAULT_TIMEOUT).await
}

/// Like [`wait_for_src`] but lets the caller choose the attribute name and timeout.
pub async fn wait_for_attribute(
    url: &str,
    xpath: &str,
    attribute: &str,
    wait_timeout: Duration,
) -> Option<String> {
    let (mut browser, page) = open_page(url).await?;

    // Embed xpath and attribute as JSON strings so they are safely quoted inside
    // the JS template — avoids any injection from untrusted caller values.
    let xpath_js = serde_json::to_string(xpath).unwrap_or_default();
    let attr_js = serde_json::to_string(attribute).unwrap_or_default();
    let js = format!(
        r#"(function() {{
            var r = document.evaluate({xpath_js}, document, null,
                        XPathResult.FIRST_ORDERED_NODE_TYPE, null);
            var el = r.singleNodeValue;
            if (!el) return '';
            return el.getAttribute({attr_js}) || el[{attr_js}] || '';
        }})()"#
    );

    let result = timeout(wait_timeout, async {
        loop {
            match page.evaluate(js.as_str()).await {
                Ok(val) => {
                    if let Ok(s) = val.into_value::<String>() {
                        if !s.is_empty() {
                            return Some(s);
                        }
                    }
                }
                Err(e) => eprintln!("[webdriver] evaluate error: {e}"),
            }
            sleep(POLL_INTERVAL).await;
        }
    })
    .await
    .ok()
    .flatten();

    let _ = browser.close().await;
    result
}

// ── internals ─────────────────────────────────────────────────────────────────

async fn open_page(
    url: &str,
) -> Option<(Browser, chromiumoxide::Page)> {
    let config = BrowserConfig::builder()
        // ── headless & sandbox ────────────────────────────────────────
        .arg("--headless=new")
        .arg("--no-sandbox")
        .arg("--disable-dev-shm-usage")
        .arg("--no-first-run")
        .arg("--no-default-browser-check")
        // ── JS timer / scheduler: prevent Chrome from throttling
        //    background timers and renderers (critical for player init) ──
        .arg("--disable-background-timer-throttling")
        .arg("--disable-backgrounding-occluded-windows")
        .arg("--disable-renderer-backgrounding")
        .arg("--disable-ipc-flooding-protection")
        .arg("--disable-hang-monitor")
        // ── skip rendering work we don't need ────────────────────────
        .arg("--disable-gpu")
        .arg("--disable-software-rasterizer")
        .arg("--disable-accelerated-2d-canvas")
        .arg("--disable-webgl")
        .arg("--disable-3d-apis")
        .arg("--blink-settings=imagesEnabled=false")
        .arg("--mute-audio")
        // ── cut background network / update noise ────────────────────
        .arg("--disable-background-networking")
        .arg("--disable-client-side-phishing-detection")
        .arg("--disable-component-update")
        .arg("--disable-domain-reliability")
        .arg("--disable-sync")
        .arg("--metrics-recording-only")
        .arg("--safebrowsing-disable-auto-update")
        // ── misc overhead ─────────────────────────────────────────────
        .arg("--disable-extensions")
        .arg("--disable-translate")
        .arg("--disable-default-apps")
        .arg("--disable-features=site-per-process")
        .arg("--password-store=basic")
        .arg("--use-mock-keychain")
        .build()
        .map_err(|e| eprintln!("[webdriver] BrowserConfig error: {e}"))
        .ok()?;

    let (browser, mut handler) = Browser::launch(config)
        .await
        .map_err(|e| eprintln!("[webdriver] Browser launch error: {e}"))
        .ok()?;

    tokio::spawn(async move {
        loop {
            if handler.next().await.is_none() {
                break;
            }
        }
    });

    let page = browser
        .new_page(url)
        .await
        .map_err(|e| eprintln!("[webdriver] new_page error: {e}"))
        .ok()?;

    Some((browser, page))
}
