use crate::error::AppError;
use crate::finnhub::{FinnhubFinancialsReported, FinnhubProfile, ReportItem};
use anyhow::anyhow;
use chrono::NaiveDate;
use common_contracts::dtos::{CompanyProfileDto, TimeSeriesFinancialDto};
use std::collections::HashMap;

pub fn map_profile_dto(profile_raw: &FinnhubProfile, symbol: &str) -> Result<CompanyProfileDto, AppError> {
    let name = profile_raw
        .name
        .clone()
        .ok_or_else(|| AppError::DataParsing(anyhow!("Profile name missing")))?;
    let industry = profile_raw.finnhub_industry.clone();
    let list_date = profile_raw
        .ipo
        .as_ref()
        .and_then(|s| NaiveDate::parse_from_str(s, "%Y-%m-%d").ok());

    Ok(CompanyProfileDto {
        symbol: symbol.to_string(),
        name,
        industry,
        list_date,
        additional_info: None,
        updated_at: None,
    })
}

pub fn map_financial_dtos(
    financials_raw: &FinnhubFinancialsReported,
    symbol: &str,
) -> Result<Vec<TimeSeriesFinancialDto>, AppError> {
    let mut out: Vec<TimeSeriesFinancialDto> = Vec::new();
    for annual in &financials_raw.data {
        // Handle cases where end_date might include time or be whitespace-padded
        let date_str = annual.end_date.trim();
        let date_part = if date_str.len() > 10 {
            &date_str[0..10]
        } else {
            date_str
        };

        let period_date = NaiveDate::parse_from_str(date_part, "%Y-%m-%d")
            .map_err(|e| AppError::DataParsing(anyhow!("Invalid end_date '{}': {}", annual.end_date, e)))?;
        let bs = &annual.report.bs;
        let ic = &annual.report.ic;
        let cf = &annual.report.cf;

        let mut push_metric = |name: &str, value_opt: Option<f64>| {
            if let Some(v) = value_opt {
                out.push(TimeSeriesFinancialDto {
            symbol: symbol.to_string(),
                    metric_name: name.to_string(),
                    period_date,
                    value: v,
                    source: Some("finnhub".to_string()),
                });
            }
        };

        let revenue = pick_value(ic, &["Revenues"], &["Total revenue", "Revenue"]);
        let net_income = pick_value(ic, &["NetIncomeLoss"], &["Net income"]);
        let total_assets = pick_value(bs, &["Assets"], &["Total assets"]);
        let total_equity = pick_value(
            bs,
            &[
                "StockholdersEquityIncludingPortionAttributableToNoncontrollingInterest",
                "StockholdersEquity",
            ],
            &["Total equity"],
        );
        let goodwill = pick_value(bs, &["Goodwill"], &["Goodwill"]);
        let ocf = pick_value(
            cf,
            &["NetCashProvidedByUsedInOperatingActivities"],
            &["Net cash provided by operating activities"],
        );
        let capex = pick_value(
            cf,
            &["CapitalExpenditures", "PaymentsToAcquirePropertyPlantAndEquipment"],
            &["Capital expenditures"],
        );

        push_metric("revenue", revenue);
        push_metric("net_income", net_income);
        push_metric("total_assets", total_assets);
        push_metric("total_equity", total_equity);
        push_metric("goodwill", goodwill);
        push_metric("operating_cash_flow", ocf);
        push_metric("capital_expenditure", capex);
        if let (Some(ocf_v), Some(capex_v)) = (ocf, capex) {
            push_metric("__free_cash_flow", Some(ocf_v - capex_v));
            }
    }
    Ok(out)
}


fn pick_value(
    report_block: &[ReportItem],
    concept_candidates: &[&str],
    label_candidates: &[&str],
) -> Option<f64> {
    let normalize = |s: &str| s.chars().filter(|c| c.is_alphanumeric()).collect::<String>().to_lowercase();

    let by_concept: HashMap<_, _> = report_block.iter().map(|item| (normalize(&item.concept), item.value)).collect();
    let by_label: HashMap<_, _> = report_block.iter().map(|item| (normalize(&item.label), item.value)).collect();

    for key in concept_candidates {
        if let Some(&value) = by_concept.get(&normalize(key)) {
            return Some(value);
        }
    }
    for key in label_candidates {
        if let Some(&value) = by_label.get(&normalize(key)) {
            return Some(value);
        }
    }
    None
}
