use std::collections::HashMap;

use serde::{Deserialize, Serialize};

use crate::terminal::{FilingRef, Frequency};

#[derive(Debug, Clone, Deserialize)]
pub(crate) struct TickerDirectoryEntry {
    pub cik_str: u64,
    pub ticker: String,
    pub title: String,
}

#[derive(Debug, Clone)]
pub(crate) struct ResolvedCompany {
    pub ticker: String,
    pub company_name: String,
    pub cik: String,
}

#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct CompanySubmissions {
    pub name: String,
    pub filings: CompanyFilings,
}

#[derive(Debug, Clone, Deserialize)]
pub(crate) struct CompanyFilings {
    pub recent: RecentFilings,
    #[serde(default)]
    pub files: Vec<CompanyFilingsFile>,
}

#[derive(Debug, Clone, Deserialize)]
pub(crate) struct CompanyFilingsFile {
    pub name: String,
}

#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct RecentFilings {
    pub accession_number: Vec<String>,
    pub filing_date: Vec<String>,
    pub report_date: Vec<Option<String>>,
    pub form: Vec<String>,
    pub primary_document: Vec<String>,
}

impl RecentFilings {
    pub(crate) fn rows(&self) -> Vec<FilingRef> {
        self.accession_number
            .iter()
            .enumerate()
            .map(|(index, accession_number)| FilingRef {
                accession_number: accession_number.clone(),
                filing_date: self
                    .filing_date
                    .get(index)
                    .cloned()
                    .unwrap_or_else(|| "0000-00-00".to_string()),
                report_date: self.report_date.get(index).cloned().flatten(),
                form: self.form.get(index).cloned().unwrap_or_default(),
                primary_document: self.primary_document.get(index).cloned(),
            })
            .collect()
    }
}

#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct CompanyFactsResponse {
    pub entity_name: String,
    pub facts: TaxonomyFacts,
}

#[derive(Debug, Clone, Deserialize, Default)]
pub(crate) struct TaxonomyFacts {
    #[serde(rename = "us-gaap", default)]
    pub us_gaap: HashMap<String, CompanyConceptFacts>,
    #[serde(rename = "ifrs-full", default)]
    pub ifrs_full: HashMap<String, CompanyConceptFacts>,
    #[serde(rename = "dei", default)]
    pub dei: HashMap<String, CompanyConceptFacts>,
}

#[derive(Debug, Clone, Deserialize)]
pub(crate) struct CompanyConceptFacts {
    #[serde(default)]
    pub units: HashMap<String, Vec<CompanyFactRecord>>,
}

#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct CompanyFactRecord {
    pub end: String,
    #[serde(default)]
    pub start: Option<String>,
    pub val: serde_json::Value,
    pub filed: String,
    pub form: String,
    #[serde(default)]
    pub fy: Option<serde_json::Value>,
    #[serde(default)]
    pub fp: Option<String>,
    #[serde(default)]
    pub accn: Option<String>,
}

#[derive(Debug, Clone, Deserialize)]
pub(crate) struct FilingIndex {
    pub directory: FilingIndexDirectory,
}

#[derive(Debug, Clone, Deserialize)]
pub(crate) struct FilingIndexDirectory {
    pub item: Vec<FilingIndexItem>,
}

#[derive(Debug, Clone, Deserialize)]
pub(crate) struct FilingIndexItem {
    pub name: String,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum UnitFamily {
    Currency,
    CurrencyPerShare,
    Shares,
    Pure,
}

impl UnitFamily {
    pub fn from_str(s: &str) -> Result<Self, String> {
        match s.to_lowercase().as_str() {
            "currency" => Ok(UnitFamily::Currency),
            "currency_per_share" => Ok(UnitFamily::CurrencyPerShare),
            "shares" => Ok(UnitFamily::Shares),
            "pure" => Ok(UnitFamily::Pure),
            _ => Err(format!("Unknown unit family: {}", s)),
        }
    }

    pub const fn as_str(self) -> &'static str {
        match self {
            UnitFamily::Currency => "currency",
            UnitFamily::CurrencyPerShare => "currency_per_share",
            UnitFamily::Shares => "shares",
            UnitFamily::Pure => "pure",
        }
    }

    pub const fn uses_currency(self) -> bool {
        matches!(self, UnitFamily::Currency | UnitFamily::CurrencyPerShare)
    }
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) enum EarningsPeriodKey {
    Annual { fiscal_year: i32 },
    Quarterly { fiscal_year: i32, quarter: u8 },
}

impl EarningsPeriodKey {
    pub(crate) fn token(&self) -> String {
        match self {
            Self::Annual { fiscal_year } => fiscal_year.to_string(),
            Self::Quarterly {
                fiscal_year,
                quarter,
            } => format!("{fiscal_year}Q{quarter}"),
        }
    }

    pub(crate) fn sort_key(&self) -> String {
        self.token()
    }
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct EarningsRange {
    pub start: EarningsPeriodKey,
    pub end: EarningsPeriodKey,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct EarningsQuery {
    pub frequency: Frequency,
    pub range: Option<EarningsRange>,
    pub requested_currency: Option<String>,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct BalanceSheetQuery {
    pub frequency: Frequency,
    pub range: Option<EarningsRange>,
    pub requested_currency: Option<String>,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct CashFlowQuery {
    pub frequency: Frequency,
    pub range: Option<EarningsRange>,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct FilingsQuery {
    pub page: usize,
    pub page_size: usize,
    pub query: Option<String>,
}

#[derive(Debug, Clone)]
pub(crate) struct NormalizedFact {
    pub taxonomy: &'static str,
    pub concept: String,
    pub unit_family: UnitFamily,
    pub currency_code: Option<String>,
    pub value: f64,
    pub filed: String,
    pub form: String,
    pub fiscal_year: Option<String>,
    pub fiscal_period: Option<String>,
    pub period_start: Option<String>,
    pub period_end: String,
    pub accession_number: Option<String>,
}

#[derive(Debug, Clone)]
pub(crate) struct PeriodRow {
    pub label: String,
    pub fiscal_year: Option<String>,
    pub fiscal_period: Option<String>,
    pub period_start: Option<String>,
    pub period_end: String,
    pub filed_date: String,
    pub form: String,
    pub accession_number: Option<String>,
}

impl PeriodRow {
    pub(crate) fn from_fact(fact: &NormalizedFact, label: String) -> Self {
        Self {
            label,
            fiscal_year: fact.fiscal_year.clone(),
            fiscal_period: fact.fiscal_period.clone(),
            period_start: fact.period_start.clone(),
            period_end: fact.period_end.clone(),
            filed_date: fact.filed.clone(),
            form: fact.form.clone(),
            accession_number: fact.accession_number.clone(),
        }
    }
}

#[derive(Debug, Clone)]
pub(crate) struct LatestXbrlFact {
    pub concept: String,
    pub unit_family: Option<UnitFamily>,
    pub currency_code: Option<String>,
    pub value: f64,
    #[allow(dead_code)]
    pub context_ref: String,
    #[allow(dead_code)]
    pub period_start: Option<String>,
    pub period_end: Option<String>,
    #[allow(dead_code)]
    pub is_instant: bool,
    pub has_dimensions: bool,
}

#[derive(Debug, Clone, Default)]
pub(crate) struct ParsedXbrlDocument {
    pub facts: Vec<LatestXbrlFact>,
}

#[derive(Debug, Clone, Copy)]
pub(crate) struct ConceptCandidate {
    pub prefix: &'static str,
    pub local_name: &'static str,
    pub unit_family: UnitFamily,
}
