#[cfg(feature = "http")]
pub mod http;
pub mod parsing;
pub mod paths;
pub mod site;
pub mod types;

pub use site::SeekingAlphaSite;
pub use types::{
    Article, ArticleContent, FinancialCell, FinancialRow, FinancialStatement, Period, Quote,
    StatementType,
};

use tail_fin_common::page::{ensure_on_domain, page_fetch};
use tail_fin_common::BrowserSession;
use tail_fin_common::TailFinError;

/// Shared trait for operations available on both browser and HTTP clients.
pub trait SeekingAlphaApi {
    /// Fetch income statement for the given ticker.
    fn income_statement(
        &self,
        ticker: &str,
        period: Period,
    ) -> impl std::future::Future<Output = Result<FinancialStatement, TailFinError>> + Send;

    /// Fetch balance sheet for the given ticker.
    fn balance_sheet(
        &self,
        ticker: &str,
        period: Period,
    ) -> impl std::future::Future<Output = Result<FinancialStatement, TailFinError>> + Send;

    /// Fetch cash flow statement for the given ticker.
    fn cash_flow(
        &self,
        ticker: &str,
        period: Period,
    ) -> impl std::future::Future<Output = Result<FinancialStatement, TailFinError>> + Send;

    /// Fetch live quote for the given ticker.
    fn quote(
        &self,
        ticker: &str,
    ) -> impl std::future::Future<Output = Result<Quote, TailFinError>> + Send;

    /// Fetch news articles for a ticker, with optional time range.
    fn news(
        &self,
        ticker: &str,
        count: usize,
        since: Option<i64>,
        until: Option<i64>,
    ) -> impl std::future::Future<Output = Result<Vec<Article>, TailFinError>> + Send;

    /// Fetch analysis articles for a ticker, with optional time range.
    fn analysis(
        &self,
        ticker: &str,
        count: usize,
        since: Option<i64>,
        until: Option<i64>,
    ) -> impl std::future::Future<Output = Result<Vec<Article>, TailFinError>> + Send;

    /// Fetch full article content by ID.
    fn article(
        &self,
        id: &str,
    ) -> impl std::future::Future<Output = Result<ArticleContent, TailFinError>> + Send;
}

/// Browser-based SeekingAlpha client.
///
/// Calls SA's private API via same-origin `fetch()` inside the browser — no
/// CSRF token needed, cookies carried automatically.
pub struct SeekingAlphaClient {
    session: BrowserSession,
}

impl SeekingAlphaClient {
    /// Wrap a browser session for SeekingAlpha API access.
    pub fn new(session: BrowserSession) -> Self {
        Self { session }
    }

    /// Run a same-origin GET fetch in the browser and return the JSON response.
    async fn api_fetch(&self, path: &str) -> Result<serde_json::Value, TailFinError> {
        ensure_on_domain(&self.session, &["seekingalpha.com"]).await?;
        page_fetch(
            &self.session,
            path,
            "GET",
            &serde_json::json!({ "Accept": "application/json" }),
        )
        .await
    }
}

impl SeekingAlphaApi for SeekingAlphaClient {
    async fn income_statement(
        &self,
        ticker: &str,
        period: Period,
    ) -> Result<FinancialStatement, TailFinError> {
        let raw = self
            .api_fetch(&paths::financials_path(
                ticker,
                StatementType::IncomeStatement,
                period,
            ))
            .await?;
        paths::parse_financials_response(&raw, ticker, StatementType::IncomeStatement, period)
    }

    async fn balance_sheet(
        &self,
        ticker: &str,
        period: Period,
    ) -> Result<FinancialStatement, TailFinError> {
        let raw = self
            .api_fetch(&paths::financials_path(
                ticker,
                StatementType::BalanceSheet,
                period,
            ))
            .await?;
        paths::parse_financials_response(&raw, ticker, StatementType::BalanceSheet, period)
    }

    async fn cash_flow(
        &self,
        ticker: &str,
        period: Period,
    ) -> Result<FinancialStatement, TailFinError> {
        let raw = self
            .api_fetch(&paths::financials_path(
                ticker,
                StatementType::CashFlow,
                period,
            ))
            .await?;
        paths::parse_financials_response(&raw, ticker, StatementType::CashFlow, period)
    }

    async fn quote(&self, ticker: &str) -> Result<Quote, TailFinError> {
        let raw = self.api_fetch(&paths::quote_path(ticker)).await?;
        paths::parse_quote_response(&raw, ticker)
    }

    async fn news(
        &self,
        ticker: &str,
        count: usize,
        since: Option<i64>,
        until: Option<i64>,
    ) -> Result<Vec<Article>, TailFinError> {
        let raw = self
            .api_fetch(&paths::articles_path("news", ticker, count, since, until))
            .await?;
        Ok(paths::parse_articles_response(&raw, "news"))
    }

    async fn analysis(
        &self,
        ticker: &str,
        count: usize,
        since: Option<i64>,
        until: Option<i64>,
    ) -> Result<Vec<Article>, TailFinError> {
        let raw = self
            .api_fetch(&paths::articles_path(
                "analysis", ticker, count, since, until,
            ))
            .await?;
        Ok(paths::parse_articles_response(&raw, "analysis"))
    }

    async fn article(&self, id: &str) -> Result<ArticleContent, TailFinError> {
        let raw = self.api_fetch(&paths::article_path(id)).await?;
        paths::parse_article_response(&raw, id)
    }
}
