use chrono::NaiveDate;
use serde::Deserialize;
use serde_json::json;
use tokio;

use crate::{
    error::AppError,
    mapping::{map_financial_statements, TushareFinancials},
    ts_client::TushareClient,
};
use common_contracts::dtos::{CompanyProfileDto, TimeSeriesFinancialDto};
use common_contracts::provider::DataProvider;
use common_contracts::symbol_utils::CanonicalSymbol;

#[derive(Clone)]
pub struct TushareDataProvider {
    client: TushareClient,
}

impl DataProvider for TushareDataProvider {
    fn name(&self) -> &str {
        "tushare"
    }

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

impl TushareDataProvider {
    pub fn new(api_url: String, api_token: String) -> Self {
        Self {
            client: TushareClient::new(api_url, api_token),
        }
    }

    pub async fn fetch_all_data(
        &self,
        symbol: &str,
    ) -> Result<(CompanyProfileDto, Vec<TimeSeriesFinancialDto>), AppError> {
        let api_symbol = CanonicalSymbol::new(symbol, &common_contracts::symbol_utils::Market::CN).to_tushare();
        
        let (
            stock_basic,
            stock_company,
            balancesheet,
            income,
            cashflow,
            fina_indicator,
            repurchase,
            dividend,
            stk_holdernumber,
        ) = self.fetch_raw_data(&api_symbol).await?;

        // Build CompanyProfileDto (strict typed, no silent fallbacks)
        let ts_code = stock_basic
            .get(0)
            .map(|r| r.ts_code.clone())
            .ok_or_else(|| AppError::Mapping("stock_basic missing first row".to_string()))?;
        let name = stock_basic
                .get(0)
            .and_then(|r| r.name.clone())
            .ok_or_else(|| AppError::Mapping("stock_basic.name missing".to_string()))?;
        let industry = stock_basic.get(0).and_then(|r| r.industry.clone());
        let list_date = stock_basic
                .get(0)
                .and_then(|r| r.list_date.clone())
            .map(|d| NaiveDate::parse_from_str(&d, "%Y%m%d"))
            .transpose()
            .map_err(|e| AppError::Mapping(format!("Invalid list_date: {}", e)))?;

        let profile = CompanyProfileDto {
            symbol: symbol.to_string(),
            name,
            industry,
            list_date,
            additional_info: Some(json!({ "tushare_ts_code": ts_code })),
            updated_at: None,
        };

        // Map time-series financials into DTOs
        let raw_financials = TushareFinancials {
            balancesheet,
            income,
            cashflow,
            fina_indicator,
            repurchase,
            dividend,
            stk_holdernumber,
            employees: stock_company.get(0).and_then(|r| r.employees),
        };
        let financials = map_financial_statements(symbol, raw_financials)?;

        Ok((profile, financials))
    }

    async fn fetch_raw_data(
        &self,
        symbol: &str,
    ) -> Result<
        (
            Vec<StockBasic>,
            Vec<StockCompany>,
            Vec<BalanceSheet>,
            Vec<Income>,
            Vec<Cashflow>,
            Vec<FinaIndicator>,
            Vec<Repurchase>,
            Vec<Dividend>,
            Vec<StkHolderNumber>,
        ),
        AppError,
    > {
        let params = json!({ "ts_code": symbol });

        let stock_basic_task = self
            .client
            .send_request::<StockBasic>("stock_basic", params.clone(), "");
        let stock_company_task = self.client.send_request::<StockCompany>(
            "stock_company",
            params.clone(),
            "ts_code,exchange,chairman,manager,secretary,reg_capital,setup_date,province,city,introduction,website,email,office,employees,main_business,business_scope",
        );
        let balancesheet_task = self.client.send_request::<BalanceSheet>(
            "balancesheet",
            params.clone(),
            "ts_code,ann_date,f_ann_date,end_date,report_type,comp_type,money_cap,inventories,prepayment,accounts_receiv,goodwill,lt_eqt_invest,fix_assets,total_assets,accounts_pay,adv_receipts,contract_liab,st_borr,lt_borr,total_cur_assets,total_cur_liab,total_ncl,total_liab,total_hldr_eqy_exc_min_int",
        );
        let income_task = self.client.send_request::<Income>(
            "income",
            params.clone(),
            "ts_code,ann_date,f_ann_date,end_date,report_type,comp_type,total_revenue,revenue,sell_exp,admin_exp,rd_exp,operate_profit,total_profit,income_tax,n_income,n_income_attr_p,ebit,ebitda,netprofit_margin,grossprofit_margin",
        );
        let cashflow_task = self.client.send_request::<Cashflow>(
            "cashflow",
            params.clone(),
            "ts_code,ann_date,f_ann_date,end_date,comp_type,report_type,n_cashflow_act,c_pay_acq_const_fiolta,c_paid_to_for_empl,depr_fa_coga_dpba",
        );
        let fina_indicator_task = self.client.send_request::<FinaIndicator>(
            "fina_indicator",
            params.clone(),
            "ts_code,end_date,ann_date,grossprofit_margin,netprofit_margin,tax_to_ebt,roe,roa,roic,invturn_days,arturn_days,fa_turn,tr_yoy,dt_netprofit_yoy,assets_turn",
        );
        let repurchase_task = self.client.send_request::<Repurchase>(
            "repurchase",
            params.clone(),
            "ts_code,ann_date,end_date,proc,exp_date,vol,amount,high_limit,low_limit",
        );
        let dividend_task = self.client.send_request::<Dividend>(
            "dividend",
            params.clone(),
            "ts_code,end_date,cash_div_tax,pay_date,base_share",
        );
        let stk_holdernumber_task = self.client.send_request::<StkHolderNumber>(
            "stk_holdernumber",
            params.clone(),
            "ts_code,ann_date,end_date,holder_num",
        );

        let (
            stock_basic,
            stock_company,
            balancesheet,
            income,
            cashflow,
            fina_indicator,
            repurchase,
            dividend,
            stk_holdernumber,
        ) = tokio::try_join!(
            stock_basic_task,
            stock_company_task,
            balancesheet_task,
            income_task,
            cashflow_task,
            fina_indicator_task,
            repurchase_task,
            dividend_task,
            stk_holdernumber_task,
        )?;

        Ok((
            stock_basic,
            stock_company,
            balancesheet,
            income,
            cashflow,
            fina_indicator,
            repurchase,
            dividend,
            stk_holdernumber,
        ))
    }
}

#[derive(Deserialize, Debug, Clone)]
#[allow(dead_code)]
pub struct StockBasic {
    pub ts_code: String,
    pub symbol: Option<String>,
    pub name: Option<String>,
    pub area: Option<String>,
    pub industry: Option<String>,
    pub market: Option<String>,
    pub exchange: Option<String>,
    pub list_date: Option<String>,
}

#[derive(Deserialize, Debug, Clone)]
#[allow(dead_code)]
pub struct StockCompany {
    pub ts_code: String,
    pub exchange: Option<String>,
    pub chairman: Option<String>,
    pub manager: Option<String>,
    pub secretary: Option<String>,
    pub reg_capital: Option<f64>,
    pub setup_date: Option<String>,
    pub province: Option<String>,
    pub city: Option<String>,
    pub introduction: Option<String>,
    pub website: Option<String>,
    pub email: Option<String>,
    pub office: Option<String>,
    pub employees: Option<f64>,
    pub main_business: Option<String>,
    pub business_scope: Option<String>,
}

#[derive(Deserialize, Debug, Clone)]
#[allow(dead_code)]
pub struct BalanceSheet {
    pub ts_code: String,
    pub ann_date: Option<String>,
    pub end_date: Option<String>,
    pub report_type: Option<String>,
    pub money_cap: Option<f64>,
    pub inventories: Option<f64>,
    pub prepayment: Option<f64>,
    pub accounts_receiv: Option<f64>,
    pub goodwill: Option<f64>,
    pub lt_eqt_invest: Option<f64>,
    pub fix_assets: Option<f64>,
    pub total_assets: Option<f64>,
    pub accounts_pay: Option<f64>,
    pub adv_receipts: Option<f64>,
    pub contract_liab: Option<f64>,
    pub st_borr: Option<f64>,
    pub lt_borr: Option<f64>,
    pub total_cur_assets: Option<f64>,
    pub total_cur_liab: Option<f64>,
    pub total_ncl: Option<f64>,
    pub total_liab: Option<f64>,
    pub total_hldr_eqy_exc_min_int: Option<f64>,
}

#[derive(Deserialize, Debug, Clone)]
#[allow(dead_code)]
pub struct Income {
    pub ts_code: String,
    pub ann_date: Option<String>,
    pub end_date: Option<String>,
    pub report_type: Option<String>,
    pub total_revenue: Option<f64>,
    pub revenue: Option<f64>,
    pub sell_exp: Option<f64>,
    pub admin_exp: Option<f64>,
    pub rd_exp: Option<f64>,
    pub operate_profit: Option<f64>,
    pub total_profit: Option<f64>,
    pub income_tax: Option<f64>,
    pub n_income: Option<f64>,
    pub n_income_attr_p: Option<f64>,
    pub ebit: Option<f64>,
    pub ebitda: Option<f64>,
    pub netprofit_margin: Option<f64>,
    pub grossprofit_margin: Option<f64>,
}

#[derive(Deserialize, Debug, Clone)]
#[allow(dead_code)]
pub struct Cashflow {
    pub ts_code: String,
    pub ann_date: Option<String>,
    pub end_date: Option<String>,
    pub report_type: Option<String>,
    pub n_cashflow_act: Option<f64>,
    pub c_pay_acq_const_fiolta: Option<f64>,
    pub c_paid_to_for_empl: Option<f64>,
    pub depr_fa_coga_dpba: Option<f64>,
}

#[derive(Deserialize, Debug, Clone)]
#[allow(dead_code)]
pub struct FinaIndicator {
    pub ts_code: String,
    pub end_date: Option<String>,
    pub ann_date: Option<String>,
    pub grossprofit_margin: Option<f64>,
    pub netprofit_margin: Option<f64>,
    pub tax_to_ebt: Option<f64>,
    pub roe: Option<f64>,
    pub roa: Option<f64>,
    pub roic: Option<f64>,
    pub invturn_days: Option<f64>,
    pub arturn_days: Option<f64>,
    pub fa_turn: Option<f64>,
    pub tr_yoy: Option<f64>,
    pub dt_netprofit_yoy: Option<f64>,
    pub assets_turn: Option<f64>,
}

#[derive(Deserialize, Debug, Clone)]
#[allow(dead_code)]
pub struct Repurchase {
    pub ts_code: String,
    pub ann_date: Option<String>,
    pub end_date: Option<String>,
    pub vol: Option<f64>,
    pub amount: Option<f64>,
    pub high_limit: Option<f64>,
    pub low_limit: Option<f64>,
}

#[derive(Deserialize, Debug, Clone)]
#[allow(dead_code)]
pub struct Dividend {
    pub ts_code: String,
    pub end_date: Option<String>,
    pub cash_div_tax: Option<f64>,
    pub pay_date: Option<String>,
    pub base_share: Option<f64>,
}

#[derive(Deserialize, Debug, Clone)]
#[allow(dead_code)]
pub struct StkHolderNumber {
    pub ts_code: String,
    pub ann_date: Option<String>,
    pub end_date: Option<String>,
    pub holder_num: Option<f64>,
}


