use crate::error::AppError;
use crate::mapping::{map_financial_statements, map_profile};
use common_contracts::dtos::{CompanyProfileDto, TimeSeriesFinancialDto};
use common_contracts::provider::DataProvider;
use anyhow::anyhow;
use std::sync::Arc;
use tokio::sync::RwLock;
use tracing::{info, warn};

#[derive(Clone)]
pub struct YFinanceDataProvider {
    client: reqwest::Client,
    is_enabled: bool,
    crumb: Arc<RwLock<Option<String>>>,
}

impl DataProvider for YFinanceDataProvider {
    fn name(&self) -> &str {
        "yfinance"
    }

    fn is_enabled(&self) -> bool {
        self.is_enabled
    }
}

impl YFinanceDataProvider {
    pub fn new(is_enabled: bool) -> Self {
        let client = reqwest::Client::builder()
            .cookie_store(true)
            .user_agent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36")
            .timeout(std::time::Duration::from_secs(30))
            .redirect(reqwest::redirect::Policy::default())
            .build()
            .unwrap_or_else(|_| reqwest::Client::new());

        Self {
            client,
            is_enabled,
            crumb: Arc::new(RwLock::new(None)),
        }
    }

    async fn ensure_crumb(&self) -> Result<String, AppError> {
        // 1. Fast path: read lock
        if let Some(crumb) = self.crumb.read().await.as_ref() {
            return Ok(crumb.clone());
        }

        // 2. Slow path: write lock
        let mut lock = self.crumb.write().await;
        // Double-check if another thread filled it while we waited
        if let Some(crumb) = lock.as_ref() {
            return Ok(crumb.clone());
        }

        info!("Fetching new Yahoo Finance crumb...");

        // 3. Fetch cookie from fc.yahoo.com
        // We don't care about the body, just the cookies which reqwest handles automatically
        let _ = self.client
            .get("https://fc.yahoo.com")
            .send()
            .await
            .map_err(|e| AppError::ServiceRequest(e))?;

        // 4. Fetch crumb
        // Using query2 as it is often more reliable / used by libraries
        let crumb_resp = self.client
            .get("https://query2.finance.yahoo.com/v1/test/getcrumb")
            .send()
            .await
            .map_err(|e| AppError::ServiceRequest(e))?;
        
        let crumb_resp = crumb_resp.error_for_status().map_err(|e| AppError::ServiceRequest(e))?;
        let crumb_text = crumb_resp.text().await.map_err(|e| AppError::ServiceRequest(e))?;
        
        if crumb_text.contains("Too Many Requests") || crumb_text.len() > 100 {
             warn!("Fetched crumb looks invalid: {}", crumb_text);
             return Err(AppError::DataParsing(anyhow!("Invalid crumb received from Yahoo Finance")));
        }

        info!("Successfully fetched crumb: {}", crumb_text);
        *lock = Some(crumb_text.clone());
        
        Ok(crumb_text)
    }

    /// Tests connectivity by ensuring we can get a crumb AND fetch a small piece of data.
    /// This bypasses the crumb cache check implicitly because the fetch will fail if network is down.
    /// However, to be absolutely sure we are testing connectivity, we should force a fetch.
    pub async fn ping(&self) -> Result<(), AppError> {
        // 1. Ensure we have a crumb (this might use cache)
        let crumb = self.ensure_crumb().await?;
        
        // 2. Perform a real request to verify connectivity and crumb validity.
        // We fetch a lightweight endpoint, e.g., quoteType for a stable symbol like "SPY".
        let url = format!(
            "https://query2.finance.yahoo.com/v10/finance/quoteSummary/SPY?modules=quoteType&crumb={}",
            crumb
        );

        info!("Ping: sending verification request to Yahoo Finance...");
        let resp = self.client
            .get(&url)
            .send()
            .await
            .map_err(|e| AppError::ServiceRequest(e))?;
            
        if !resp.status().is_success() {
            warn!("Ping failed: Yahoo Finance returned status {}", resp.status());
            // If unauthorized, maybe crumb is stale? We could invalidate cache here, 
            // but for now let's just report failure.
            return Err(AppError::ServiceRequest(resp.error_for_status().unwrap_err()));
        }

        Ok(())
    }

    pub async fn fetch_all_data(
        &self,
        symbol: &str,
    ) -> Result<(CompanyProfileDto, Vec<TimeSeriesFinancialDto>), AppError> {
        let (summary_raw, financials_raw) = self.fetch_raw_data(symbol).await?;
        let profile = map_profile(&summary_raw, symbol)?;
        let financials = map_financial_statements(&financials_raw, symbol)?;
        Ok((profile, financials))
    }

    async fn fetch_raw_data(
        &self,
        symbol: &str,
    ) -> Result<(serde_json::Value, serde_json::Value), AppError> {
        let crumb = self.ensure_crumb().await?;

        // Convert suffix based on legacy Python implementation logic
        // Shanghai: 600519.SH -> 600519.SS
        // Shenzhen: 000001.SZ -> 000001 (Strip .SZ)
        let yahoo_symbol = if symbol.ends_with(".SH") {
            symbol.replace(".SH", ".SS")
        } else {
            symbol.to_string()
        };

        info!("Fetching raw data for symbol: {} (mapped from {})", yahoo_symbol, symbol);

        // Yahoo quoteSummary: assetProfile + quoteType
        // Use query2.finance.yahoo.com
        let summary_url = format!(
            "https://query2.finance.yahoo.com/v10/finance/quoteSummary/{}?modules=assetProfile,quoteType&crumb={}",
            yahoo_symbol, crumb
        );
        // Yahoo financials: income/balance/cashflow histories
        let financials_url = format!(
            "https://query2.finance.yahoo.com/v10/finance/quoteSummary/{}?modules=incomeStatementHistory,balanceSheetHistory,cashflowStatementHistory&crumb={}",
            yahoo_symbol, crumb
        );

        let summary_task = self.client.get(&summary_url).send();
        let financials_task = self.client.get(&financials_url).send();

        let (summary_res, financials_res) = tokio::try_join!(summary_task, financials_task)
            .map_err(|e| AppError::ServiceRequest(e))?;

        let summary_res = summary_res.error_for_status().map_err(|e| AppError::ServiceRequest(e))?;
        let financials_res = financials_res.error_for_status().map_err(|e| AppError::ServiceRequest(e))?;

        let summary_json: serde_json::Value = summary_res
            .json()
            .await
            .map_err(|e| AppError::DataParsing(anyhow!(e)))?;
        let financials_json: serde_json::Value = financials_res
            .json()
            .await
            .map_err(|e| AppError::DataParsing(anyhow!(e)))?;

        Ok((summary_json, financials_json))
    }
}
