//! `Site` implementation for Seeking Alpha.
//!
//! Unlike Twitter, SA's home page doesn't have streaming polls — the default
//! `Site::refresh` (`navigate + get_cookies_for_domain`) works fine here.
//! We only override `validate` + `detect_auth_failure`.

use std::time::Duration;

use async_trait::async_trait;
use tail_fin_core::{
    AuthFailureKind, BrowserSession, FailureIndicators, SessionStatus, Site, SiteError,
};

pub struct SeekingAlphaSite;

#[async_trait]
impl Site for SeekingAlphaSite {
    fn id(&self) -> &'static str {
        "seekingalpha"
    }

    fn display_name(&self) -> &'static str {
        "Seeking Alpha"
    }

    fn cookie_domain_patterns(&self) -> &'static [&'static str] {
        &["*.seekingalpha.com"]
    }

    fn refresh_url(&self) -> &'static str {
        // Homepage triggers cf_clearance + session cookie emission.
        "https://seekingalpha.com/"
    }

    fn refresh_interval_min(&self) -> Duration {
        // SA is cookie-heavy with Cloudflare + PerimeterX; keep this
        // conservative to avoid triggering anti-bot challenges.
        Duration::from_secs(120)
    }

    // refresh: use default (navigate + get_cookies_for_domain).
    // SA home doesn't need scroll-to-trigger-GraphQL tricks like Twitter.

    /// Validate by checking for the `user_id` cookie — it's the anchor auth
    /// cookie for authenticated SA sessions. Cheaper than an API ping and
    /// doesn't risk triggering PerimeterX.
    async fn validate(&self, session: &BrowserSession) -> Result<SessionStatus, SiteError> {
        let cookies = session
            .get_cookies_for_domain("*.seekingalpha.com")
            .await
            .map_err(|e| SiteError::ValidationFailed {
                site: self.id(),
                reason: format!("get_cookies_for_domain: {e}"),
            })?;

        let has_user_id = cookies.iter().any(|c| {
            c.get("name").and_then(|v| v.as_str()) == Some("user_id")
                && c.get("value")
                    .and_then(|v| v.as_str())
                    .map(|v| !v.is_empty())
                    .unwrap_or(false)
        });

        if !has_user_id {
            return Ok(SessionStatus::Expired);
        }

        // user_id is present — but `cf_clearance` may be expired. Hit the
        // homepage with http_ping to check Cloudflare is still trusting us.
        let status = session
            .http_ping("https://seekingalpha.com/")
            .await
            .map_err(|e| SiteError::ValidationFailed {
                site: self.id(),
                reason: format!("homepage ping: {e}"),
            })?;

        Ok(match status {
            200 => SessionStatus::Valid,
            401 | 403 => SessionStatus::Expired,
            429 => SessionStatus::Blocked {
                reason: "SA rate limit".into(),
                retry_after: Some(Duration::from_secs(600)),
            },
            503 => SessionStatus::Blocked {
                reason: "Cloudflare / PerimeterX challenge".into(),
                retry_after: Some(Duration::from_secs(900)),
            },
            0 => SessionStatus::Unknown,
            other => SessionStatus::Degrading {
                estimated_expiry: None,
                hint: format!("unexpected HTTP {other} from homepage"),
            },
        })
    }

    fn detect_auth_failure(&self, indicators: &FailureIndicators) -> Option<AuthFailureKind> {
        match indicators.status {
            Some(401) | Some(403) => Some(AuthFailureKind::CookieExpired),
            Some(429) => Some(AuthFailureKind::RateLimited {
                retry_after: Duration::from_secs(600),
            }),
            // Cloudflare block pages usually 503 + specific body.
            Some(503) => Some(AuthFailureKind::AntibotBlock {
                challenge_type: "cloudflare-503".into(),
            }),
            // PerimeterX block body signatures (check the body preview).
            _ if indicators.body_preview.contains("px-captcha")
                || indicators
                    .body_preview
                    .contains("Please complete the verification")
                || indicators
                    .body_preview
                    .contains("Access to this page has been denied") =>
            {
                Some(AuthFailureKind::AntibotBlock {
                    challenge_type: "perimeterx".into(),
                })
            }
            _ => None,
        }
    }
}
