//! Live end-to-end tests for `SeekingAlphaSite` against real seekingalpha.com.
//!
//! Requires:
//! - Chrome installed
//! - `~/.tail-fin/sa-cookies.txt` with a logged-in session
//!   (run `tail-fin --connect 127.0.0.1:9222 sa export-cookies` first)
//!
//! All tests are `#[ignore]`-gated.
//!
//! Run (must be serial — each test launches its own Chrome on the same
//! user-data-dir, so parallel execution hits `SingletonLock: File exists`):
//!   cargo test -p tail-fin-sa --test sa_live -- \
//!       --ignored --test-threads=1 --nocapture

use std::path::PathBuf;
use std::sync::Arc;

use tail_fin_common::{BrowserSession, SessionManager, SessionStatus, Site};
use tail_fin_sa::SeekingAlphaSite;

fn cookies_path() -> PathBuf {
    let home = std::env::var("HOME").expect("HOME not set");
    PathBuf::from(home).join(".tail-fin/sa-cookies.txt")
}

fn load_cookie_jar(path: &std::path::Path) -> Vec<serde_json::Value> {
    let data = std::fs::read_to_string(path).expect("cannot read cookies file");
    let mut cookies = Vec::new();
    for line in data.lines() {
        let line = line.trim();
        if line.is_empty() || line.starts_with('#') {
            continue;
        }
        let parts: Vec<&str> = line.splitn(7, '\t').collect();
        if parts.len() < 6 {
            continue;
        }
        let domain = parts[0];
        let path = parts[2];
        let secure = parts[3].eq_ignore_ascii_case("true");
        let expiry: i64 = parts[4].parse().unwrap_or(0);
        let name = parts[5];
        let value = parts.get(6).copied().unwrap_or("");

        let mut c = serde_json::json!({
            "name": name,
            "value": value,
            "domain": domain,
            "path": path,
            "secure": secure,
            "httpOnly": false,
        });
        if expiry > 0 {
            c["expires"] = serde_json::json!(expiry as f64);
        }
        cookies.push(c);
    }
    cookies
}

async fn session_with_cookies() -> BrowserSession {
    let session = BrowserSession::builder()
        .headed(false)
        .viewport(1440, 900)
        .build()
        .await
        .expect("browser launch");

    // Navigate to SA first so the cookies land on the right origin.
    session
        .navigate("https://seekingalpha.com/")
        .await
        .expect("initial navigate");
    session.wait(std::time::Duration::from_secs(1)).await;

    let jar = load_cookie_jar(&cookies_path());
    assert!(!jar.is_empty(), "cookie jar must not be empty");
    session.set_cookies(jar).await.expect("inject cookies");

    session
}

// ---------------------------------------------------------------------------
// SeekingAlphaSite::refresh (uses default Site::refresh impl)
// ---------------------------------------------------------------------------

#[tokio::test]
#[ignore]
async fn sa_site_refresh_returns_user_id() {
    let session = session_with_cookies().await;

    let cookies = SeekingAlphaSite
        .refresh(&session)
        .await
        .expect("refresh failed");

    eprintln!("refresh returned {} cookies", cookies.len());
    assert!(!cookies.is_empty(), "expected non-empty cookies");

    // Every returned cookie should be from seekingalpha.com
    for c in &cookies {
        let domain = c.get("domain").and_then(|v| v.as_str()).unwrap_or("");
        assert!(
            domain.contains("seekingalpha.com"),
            "unexpected cookie domain: {domain}"
        );
    }

    // Ideally user_id is present (auth anchor). If not, cookies are stale.
    let has_user_id = cookies
        .iter()
        .any(|c| c.get("name").and_then(|v| v.as_str()) == Some("user_id"));
    if !has_user_id {
        eprintln!("(note: user_id missing — cookies likely stale)");
    }
}

// ---------------------------------------------------------------------------
// SeekingAlphaSite::validate
// ---------------------------------------------------------------------------

#[tokio::test]
#[ignore]
async fn sa_site_validate() {
    let session = session_with_cookies().await;

    let status = SeekingAlphaSite
        .validate(&session)
        .await
        .expect("validate failed");

    eprintln!("validate returned: {status:?}");
    match status {
        SessionStatus::Valid => {}
        SessionStatus::Degrading { .. } => {}
        SessionStatus::Expired => {
            eprintln!("(note: cookies may be stale — re-export and re-run)");
        }
        SessionStatus::Blocked { reason, .. } => {
            eprintln!("(note: blocked by {reason} — PerimeterX/Cloudflare challenge)");
        }
        SessionStatus::Unknown => {
            panic!("validate returned Unknown — the probe itself failed");
        }
    }
}

// ---------------------------------------------------------------------------
// SessionManager with SeekingAlphaSite — end to end
// ---------------------------------------------------------------------------

#[tokio::test]
#[ignore]
async fn sa_session_manager_lifecycle() {
    let session = session_with_cookies().await;
    let manager = SessionManager::new(Arc::new(SeekingAlphaSite), session);

    // First refresh should hit.
    let first = manager
        .refresh_if_stale()
        .await
        .expect("first refresh failed");
    assert!(first.is_some(), "first refresh_if_stale should hit");
    let first_cookies = first.unwrap();
    assert!(!first_cookies.is_empty());
    eprintln!("first refresh returned {} cookies", first_cookies.len());

    // Immediate second call should debounce (refresh_interval_min = 120s).
    let second = manager
        .refresh_if_stale()
        .await
        .expect("second refresh_if_stale failed");
    assert!(second.is_none(), "second refresh_if_stale should debounce");

    // Force refresh ignores debounce.
    let forced = manager.refresh().await.expect("forced refresh failed");
    assert!(!forced.is_empty());

    // Validate final state.
    let status = manager.validate().await.expect("validate failed");
    eprintln!("final status: {status:?}");
    assert!(
        !matches!(status, SessionStatus::Unknown),
        "validate should not fail"
    );
}
