mod catalog;
pub(crate) mod concepts;
mod dei;
pub(crate) mod facts;
mod mapper;
pub(crate) mod package;
mod presentation;

use crate::mappings::{CustomConceptMapping, TargetField};
use crate::terminal::types::AvailableXbrlConcept;
use crate::terminal::{
    EarningsPanelData, EarningsPeriod, EarningsPeriodOption, FilingRef, Frequency, SourceStatus,
};

use super::client::SecEdgarClient;
use super::facts::{company_name, default_earnings_period_limit};
use super::mapping_support::FormulaCheckTargets;
use super::service::EdgarLookupError;
use super::types::{CompanySubmissions, EarningsQuery, ResolvedCompany};
use concepts::extract_available_concepts;
use mapper::{
    dominant_statement_currency, is_clearly_quarter_length, statement_currency_codes,
    FilingEarningsRecord,
};

pub(crate) async fn load_concepts_for_ticker(
    client: &SecEdgarClient,
    ticker: &str,
) -> Result<Vec<AvailableXbrlConcept>, EdgarLookupError> {
    let company = client.resolve_company(ticker).await?;
    let submissions = client.load_submissions(&company.cik).await?;

    let mut concepts = Vec::<AvailableXbrlConcept>::new();

    for frequency in [Frequency::Annual, Frequency::Quarterly] {
        let filings =
            catalog::load_candidate_filings(client, &company.cik, &submissions, frequency)
                .await
                .unwrap_or_default();

        for filing in &filings {
            if concepts.len() >= 2000 {
                break;
            }

            let package = match package::load_filing_package(client, &company.cik, filing).await {
                Ok(pkg) => pkg,
                Err(_) => continue,
            };

            let filing_concepts =
                extract_available_concepts(&package.document, &filing.accession_number);
            concepts.extend(filing_concepts);

            if !concepts.is_empty() {
                break;
            }
        }

        if !concepts.is_empty() {
            break;
        }
    }

    let mut seen = std::collections::HashSet::<(String, String, String)>::new();
    concepts
        .retain(|c| seen.insert((c.taxonomy.clone(), c.concept.clone(), c.unit_family.clone())));

    Ok(concepts)
}

#[derive(Debug, Clone)]
pub(crate) struct FilingNativeEarningsData {
    pub periods: Vec<EarningsPeriod>,
    pub available_periods: Vec<EarningsPeriodOption>,
    pub selected_period_start: Option<String>,
    pub selected_period_end: Option<String>,
    pub selected_currency: Option<String>,
    pub available_currencies: Vec<String>,
    pub latest_filing: Option<FilingRef>,
    pub source_status: SourceStatus,
    pub available_concepts: Vec<AvailableXbrlConcept>,
    pub mapping_warnings: Vec<String>,
}

#[derive(Debug, Clone)]
struct CandidateEarningsFiling {
    filing: FilingRef,
    document: facts::FilingXbrlDocument,
    identity: dei::PeriodIdentity,
    available_currencies: Vec<String>,
    dominant_currency: Option<String>,
}

pub(crate) async fn load_earnings_data(
    client: &SecEdgarClient,
    company: &ResolvedCompany,
    submissions: &CompanySubmissions,
    query: &EarningsQuery,
    custom_mappings: &[CustomConceptMapping],
) -> Result<FilingNativeEarningsData, EdgarLookupError> {
    let filings =
        catalog::load_candidate_filings(client, &company.cik, submissions, query.frequency).await?;
    if filings.is_empty() {
        return Err(EdgarLookupError::NoEligibleFilings {
            ticker: company.ticker.clone(),
            frequency: query.frequency,
        });
    }

    let mut records = Vec::<FilingEarningsRecord>::new();
    let mut skipped_reasons = Vec::<String>::new();
    let mut seen_periods = std::collections::HashSet::<String>::new();
    let mut all_period_options = Vec::<EarningsPeriodOption>::new();
    let mut seen_all_periods = std::collections::HashSet::<String>::new();
    let mut all_available_concepts = Vec::<AvailableXbrlConcept>::new();
    let mut all_mapping_warnings = Vec::<String>::new();
    let mut candidate_filings = Vec::<CandidateEarningsFiling>::new();
    let mut available_currency_set = std::collections::BTreeSet::<String>::new();

    for filing in filings {
        let package = match package::load_filing_package(client, &company.cik, &filing).await {
            Ok(package) => package,
            Err(error) => {
                skipped_reasons.push(error.to_string());
                continue;
            }
        };
        let Some(identity) = dei::extract_period_identity(&package.document, query.frequency)
        else {
            skipped_reasons.push(format!(
                "Skipped {} because DEI fiscal period metadata was unavailable.",
                filing.accession_number
            ));
            continue;
        };
        let available_currencies = statement_currency_codes(
            &package.document,
            &identity,
            query.frequency,
            &company.ticker,
            custom_mappings,
        );
        for currency in &available_currencies {
            available_currency_set.insert(currency.clone());
        }
        let dominant_currency = dominant_statement_currency(
            &package.document,
            &identity,
            query.frequency,
            &company.ticker,
            custom_mappings,
        );
        let option = EarningsPeriodOption {
            id: identity.key.token(),
            label: identity.display_label.clone(),
            frequency: query.frequency,
            fiscal_year: Some(identity.fiscal_year.clone()),
            fiscal_period: identity.fiscal_period.clone(),
            period_end: identity.period_end.clone(),
            sort_key: identity.key.sort_key(),
        };
        if seen_all_periods.insert(option.id.clone()) {
            all_period_options.push(option);
        }

        candidate_filings.push(CandidateEarningsFiling {
            filing,
            document: package.document,
            identity,
            available_currencies,
            dominant_currency,
        });
    }

    let available_currencies = available_currency_set.into_iter().collect::<Vec<_>>();
    let selected_currency =
        resolve_earnings_currency(query, company, &available_currencies, &candidate_filings)?;

    for candidate in candidate_filings {
        if selected_currency.as_deref().is_some_and(|currency| {
            !candidate
                .available_currencies
                .iter()
                .any(|value| value == currency)
        }) {
            skipped_reasons.push(format!(
                "Skipped {} because {} facts were unavailable for the statement window.",
                candidate.filing.accession_number,
                selected_currency.as_deref().unwrap_or_default()
            ));
            continue;
        }

        let Some((record, warnings)) = mapper::map_filing_to_record(
            &candidate.document,
            &candidate.filing,
            &candidate.identity,
            query.frequency,
            &company.ticker,
            custom_mappings,
            selected_currency.as_deref(),
        ) else {
            skipped_reasons.push(format!(
                "Skipped {} because no direct filing-native income statement facts matched the filing period.",
                candidate.filing.accession_number
            ));
            continue;
        };

        all_mapping_warnings.extend(warnings);

        if candidate.filing.form.starts_with("6-K") && !is_clearly_quarter_length(&record.period) {
            skipped_reasons.push(format!(
                "Skipped {} because the filing duration was not a clear fiscal quarter.",
                candidate.filing.accession_number
            ));
            continue;
        }

        if seen_periods.insert(record.option.id.clone()) {
            let filing_concepts =
                extract_available_concepts(&candidate.document, &record.option.id);
            all_available_concepts.extend(filing_concepts);

            records.push(record);
        }
    }

    if records.is_empty() {
        let detail = if skipped_reasons.is_empty() {
            format!(
                "No eligible {} filings were found for {}.",
                query.frequency.as_str(),
                company.ticker
            )
        } else {
            format!(
                "No eligible {} filings were found for {}. {}",
                query.frequency.as_str(),
                company.ticker,
                summarize_skips(&skipped_reasons).unwrap_or_default()
            )
        };
        return Err(EdgarLookupError::InvalidPeriodRange { detail });
    }

    records.sort_by(|left, right| right.option.sort_key.cmp(&left.option.sort_key));

    all_period_options.sort_by(|left, right| left.sort_key.cmp(&right.sort_key));
    let available_periods = all_period_options;

    let requested_range = query.range.as_ref().map(|range| {
        let start = range.start.token();
        let end = range.end.token();
        (start, end)
    });

    if let Some((start, end)) = requested_range.as_ref() {
        let has_start = available_periods.iter().any(|period| period.id == *start);
        let has_end = available_periods.iter().any(|period| period.id == *end);
        if !has_start || !has_end {
            let oldest = available_periods.first().map(|period| period.id.as_str());
            let newest = available_periods.last().map(|period| period.id.as_str());
            return Err(EdgarLookupError::InvalidPeriodRange {
                detail: match (oldest, newest) {
                    (Some(oldest), Some(newest)) => format!(
                        "Requested /em range is unavailable. Try a reported range between {oldest} and {newest}."
                    ),
                    _ => "Requested /em range is unavailable for this company.".to_string(),
                },
            });
        }
    }

    let mut selected = records
        .into_iter()
        .filter(|record| match requested_range.as_ref() {
            Some((start, end)) => record.option.id >= *start && record.option.id <= *end,
            None => true,
        })
        .collect::<Vec<_>>();

    if requested_range.is_none() {
        selected.truncate(default_earnings_period_limit(query.frequency));
    }

    apply_yoy_metrics(&mut selected, query.frequency);
    selected.reverse();

    let mut periods = Vec::with_capacity(selected.len());
    for record in &selected {
        let mut period = record.period.clone();
        all_mapping_warnings.extend(validate_earnings_consistency(
            &mut period,
            &record.formula_check_targets,
        ));
        periods.push(period);
    }
    let selected_period_start = requested_range
        .as_ref()
        .map(|(start, _)| start.clone())
        .or_else(|| selected.first().map(|record| record.option.id.clone()));
    let selected_period_end = requested_range
        .as_ref()
        .map(|(_, end)| end.clone())
        .or_else(|| selected.last().map(|record| record.option.id.clone()));
    let latest_filing = selected
        .last()
        .map(|record| record.filing.clone())
        .or_else(|| available_latest_filing(&available_periods, &selected));

    Ok(FilingNativeEarningsData {
        periods,
        available_periods,
        selected_period_start,
        selected_period_end,
        selected_currency,
        available_currencies,
        latest_filing,
        source_status: SourceStatus {
            companyfacts_used: false,
            degraded_reason: summarize_skips(&skipped_reasons),
        },
        available_concepts: all_available_concepts,
        mapping_warnings: all_mapping_warnings,
    })
}

pub(crate) fn build_panel_data(
    company: &ResolvedCompany,
    submissions: &CompanySubmissions,
    query: &EarningsQuery,
    data: FilingNativeEarningsData,
) -> EarningsPanelData {
    EarningsPanelData {
        symbol: company.ticker.clone(),
        company_name: company_name(company, Some(&submissions.name), None),
        cik: company.cik.clone(),
        frequency: query.frequency,
        periods: data.periods,
        available_periods: data.available_periods,
        selected_period_start: data.selected_period_start,
        selected_period_end: data.selected_period_end,
        selected_currency: data.selected_currency,
        available_currencies: data.available_currencies,
        latest_filing: data.latest_filing,
        source_status: data.source_status,
        available_concepts: data.available_concepts,
        mapping_warnings: data.mapping_warnings,
    }
}

fn resolve_earnings_currency(
    query: &EarningsQuery,
    company: &ResolvedCompany,
    available_currencies: &[String],
    candidate_filings: &[CandidateEarningsFiling],
) -> Result<Option<String>, EdgarLookupError> {
    if available_currencies.is_empty() {
        if let Some(requested_currency) = query.requested_currency.as_deref() {
            return Err(EdgarLookupError::InvalidCurrencySelection {
                detail: format!(
                    "Currency {requested_currency} is unavailable for /em {} {}. Available currencies: none.",
                    company.ticker,
                    query.frequency.as_str()
                ),
            });
        }
        return Ok(None);
    }

    if let Some(requested_currency) = query.requested_currency.as_deref() {
        if available_currencies
            .iter()
            .any(|currency| currency == requested_currency)
        {
            return Ok(Some(requested_currency.to_string()));
        }

        return Err(EdgarLookupError::InvalidCurrencySelection {
            detail: format!(
                "Currency {requested_currency} is unavailable for /em {} {}. Available currencies: {}.",
                company.ticker,
                query.frequency.as_str(),
                available_currencies.join(", ")
            ),
        });
    }

    if available_currencies.len() == 1 {
        return Ok(available_currencies.first().cloned());
    }

    Ok(candidate_filings
        .iter()
        .find_map(|candidate| candidate.dominant_currency.clone())
        .or_else(|| available_currencies.first().cloned()))
}

fn apply_yoy_metrics(records: &mut [FilingEarningsRecord], frequency: Frequency) {
    let offset = match frequency {
        Frequency::Annual => 1,
        Frequency::Quarterly => 4,
    };

    for index in 0..records.len() {
        let Some(previous) = records
            .get(index + offset)
            .map(|record| (record.period.total_revenues, record.period.diluted_eps))
        else {
            continue;
        };
        let Some(current) = records.get_mut(index).map(|record| &mut record.period) else {
            continue;
        };

        current.total_revenues_yoy_change_percent =
            calculate_change_percent(current.total_revenues, previous.0);
        current.revenue_yoy_change_percent = current.total_revenues_yoy_change_percent;
        current.diluted_eps_yoy_change_percent =
            calculate_change_percent(current.diluted_eps, previous.1);
    }
}

fn calculate_change_percent(current: Option<f64>, previous: Option<f64>) -> Option<f64> {
    let current = current?;
    let previous = previous?;
    (previous.abs() > f64::EPSILON).then_some(((current - previous) / previous.abs()) * 100.0)
}

fn summarize_skips(skipped_reasons: &[String]) -> Option<String> {
    if skipped_reasons.is_empty() {
        return None;
    }

    let preview = skipped_reasons
        .iter()
        .take(2)
        .cloned()
        .collect::<Vec<_>>()
        .join(" ");
    let suffix = if skipped_reasons.len() > 2 {
        format!(
            " {} additional filing(s) were skipped.",
            skipped_reasons.len() - 2
        )
    } else {
        String::new()
    };

    Some(format!(
        "Filing-native earnings skipped {} filing(s). {preview}{suffix}",
        skipped_reasons.len()
    ))
}

fn available_latest_filing(
    _available_periods: &[EarningsPeriodOption],
    selected: &[FilingEarningsRecord],
) -> Option<FilingRef> {
    selected.first().map(|record| record.filing.clone())
}

fn validate_earnings_consistency(
    period: &mut EarningsPeriod,
    formula_check_targets: &FormulaCheckTargets,
) -> Vec<String> {
    let mut warnings = Vec::new();

    if !formula_check_targets.contains(&TargetField::GrossProfit) {
        push_sum_warning(
            &mut warnings,
            &period.display_label,
            "Gross Profit",
            period.gross_profit,
            &[
                ("Revenue", period.total_revenues),
                ("Cost of Sales", period.cost_of_sales.map(|v| -v)),
            ],
        );
    }

    if !formula_check_targets.contains(&TargetField::OperatingProfit) {
        push_sum_warning(
            &mut warnings,
            &period.display_label,
            "Operating Profit",
            period.operating_profit,
            &[
                ("Gross Profit", period.gross_profit),
                (
                    "SG&A Expenses",
                    period
                        .selling_general_and_administrative_expenses
                        .map(|v| -v),
                ),
                (
                    "R&D Expenses",
                    period.research_and_development_expenses.map(|v| -v),
                ),
                (
                    "Other Operating Expenses",
                    period.other_operating_expenses.map(|v| -v),
                ),
            ],
        );
    }

    if !formula_check_targets.contains(&TargetField::IncomeBeforeProvisionForIncomeTaxes) {
        push_sum_warning_relative(
            &mut warnings,
            &period.display_label,
            "Income Before Tax",
            period.income_before_provision_for_income_taxes,
            &[
                ("Operating Profit", period.operating_profit),
                (
                    "Total Non-Operating Income",
                    period.total_non_operating_income,
                ),
            ],
            0.10,
        );
    }

    if !formula_check_targets.contains(&TargetField::NetIncome) {
        push_sum_warning_if_no_nci(&mut warnings, &period.display_label, period);
    }

    warnings
}

fn push_sum_warning_if_no_nci(
    warnings: &mut Vec<String>,
    period_label: &str,
    period: &EarningsPeriod,
) {
    let Some(consolidated_ni) = period.consolidated_net_income else {
        return;
    };
    let Some(income_before_tax) = period.income_before_provision_for_income_taxes else {
        return;
    };
    let Some(provision) = period.provision_for_income_taxes else {
        return;
    };

    let expected = income_before_tax - provision;

    if let Some(ni_to_common) = period.net_income_attributable_to_common_shareholders {
        let nci_gap = (consolidated_ni - ni_to_common).abs();
        let ibt_ni_gap = (consolidated_ni - expected).abs();

        if nci_gap > ibt_ni_gap * 0.1 {
            return;
        }
    }

    if !amounts_match(consolidated_ni, expected) {
        warnings.push(format!(
            "{}: Consolidated Net Income ({}) does not equal component sum ({}).",
            period_label,
            format_accounting_amount(consolidated_ni),
            format_accounting_amount(expected),
        ));
    }
}

fn push_sum_warning(
    warnings: &mut Vec<String>,
    period_label: &str,
    total_label: &str,
    total_value: Option<f64>,
    components: &[(&str, Option<f64>)],
) {
    push_sum_warning_impl(
        warnings,
        period_label,
        total_label,
        total_value,
        components,
        amounts_match,
    );
}

fn push_sum_warning_relative(
    warnings: &mut Vec<String>,
    period_label: &str,
    total_label: &str,
    total_value: Option<f64>,
    components: &[(&str, Option<f64>)],
    relative_tolerance: f64,
) {
    let matcher = move |actual: f64, expected: f64| {
        let max_abs = actual.abs().max(expected.abs());
        let tolerance = max_abs.mul_add(relative_tolerance, 1.0);
        (actual - expected).abs() <= tolerance
    };
    push_sum_warning_impl(
        warnings,
        period_label,
        total_label,
        total_value,
        components,
        matcher,
    );
}

fn push_sum_warning_impl(
    warnings: &mut Vec<String>,
    period_label: &str,
    total_label: &str,
    total_value: Option<f64>,
    components: &[(&str, Option<f64>)],
    matcher: impl Fn(f64, f64) -> bool,
) {
    let Some(total_value) = total_value else {
        return;
    };

    let mut expected_total = 0.0;
    for (_, value) in components {
        let Some(value) = value else {
            return;
        };
        expected_total += value;
    }

    if !matcher(total_value, expected_total) {
        warnings.push(format!(
            "{}: {} ({}) does not equal component sum ({}).",
            period_label,
            total_label,
            format_accounting_amount(total_value),
            format_accounting_amount(expected_total),
        ));
    }
}

fn amounts_match(actual: f64, expected: f64) -> bool {
    let tolerance = actual.abs().max(expected.abs()).mul_add(0.000_001, 1.0);
    (actual - expected).abs() <= tolerance
}

fn format_accounting_amount(value: f64) -> String {
    format!("{value:.2}")
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn calculate_change_percent_should_only_use_direct_values() {
        assert_eq!(
            calculate_change_percent(Some(120.0), Some(100.0)),
            Some(20.0)
        );
        assert_eq!(calculate_change_percent(Some(120.0), None), None);
    }

    #[test]
    fn validate_earnings_consistency_no_warnings_when_sums_match() {
        let mut period = test_earnings_period();
        period.total_revenues = Some(1000.0);
        period.cost_of_sales = Some(400.0);
        period.gross_profit = Some(600.0);
        period.selling_general_and_administrative_expenses = Some(100.0);
        period.research_and_development_expenses = Some(50.0);
        period.other_operating_expenses = Some(0.0);
        period.operating_profit = Some(450.0);
        period.total_non_operating_income = Some(30.0);
        period.income_before_provision_for_income_taxes = Some(480.0);
        period.provision_for_income_taxes = Some(80.0);
        period.consolidated_net_income = Some(400.0);

        let warnings = validate_earnings_consistency(&mut period, &FormulaCheckTargets::default());
        assert!(warnings.is_empty());
    }

    #[test]
    fn validate_earnings_consistency_flags_gross_profit_mismatch() {
        let mut period = test_earnings_period();
        period.total_revenues = Some(1000.0);
        period.cost_of_sales = Some(400.0);
        period.gross_profit = Some(500.0);

        let warnings = validate_earnings_consistency(&mut period, &FormulaCheckTargets::default());
        assert!(warnings
            .iter()
            .any(|w| w.contains("Gross Profit") && w.contains("does not equal component sum")));
    }

    #[test]
    fn validate_earnings_consistency_skips_formula_defined_gross_profit_check() {
        let mut period = test_earnings_period();
        period.total_revenues = Some(1000.0);
        period.cost_of_sales = Some(400.0);
        period.gross_profit = Some(500.0);
        let formula_targets = FormulaCheckTargets::from([TargetField::GrossProfit]);

        let warnings = validate_earnings_consistency(&mut period, &formula_targets);
        assert!(warnings.iter().all(|w| !w.contains("Gross Profit")));
    }

    #[test]
    fn validate_earnings_consistency_flags_operating_profit_mismatch() {
        let mut period = test_earnings_period();
        period.gross_profit = Some(600.0);
        period.selling_general_and_administrative_expenses = Some(100.0);
        period.research_and_development_expenses = Some(50.0);
        period.other_operating_expenses = Some(0.0);
        period.operating_profit = Some(999.0);

        let warnings = validate_earnings_consistency(&mut period, &FormulaCheckTargets::default());
        assert!(warnings.iter().any(|w| w.contains("Operating Profit")));
    }

    #[test]
    fn validate_earnings_consistency_skips_formula_defined_operating_profit_check() {
        let mut period = test_earnings_period();
        period.gross_profit = Some(600.0);
        period.selling_general_and_administrative_expenses = Some(100.0);
        period.research_and_development_expenses = Some(50.0);
        period.other_operating_expenses = Some(0.0);
        period.operating_profit = Some(999.0);
        let formula_targets = FormulaCheckTargets::from([TargetField::OperatingProfit]);

        let warnings = validate_earnings_consistency(&mut period, &formula_targets);
        assert!(warnings.iter().all(|w| !w.contains("Operating Profit")));
    }

    #[test]
    fn validate_earnings_consistency_keeps_parent_check_for_formula_defined_component() {
        let mut period = test_earnings_period();
        period.gross_profit = Some(600.0);
        period.selling_general_and_administrative_expenses = Some(100.0);
        period.research_and_development_expenses = Some(50.0);
        period.other_operating_expenses = Some(0.0);
        period.operating_profit = Some(999.0);
        let formula_targets = FormulaCheckTargets::from([TargetField::OtherOperatingExpenses]);

        let warnings = validate_earnings_consistency(&mut period, &formula_targets);
        assert!(warnings.iter().any(|w| w.contains("Operating Profit")));
    }

    #[test]
    fn validate_earnings_consistency_skips_check_when_component_missing() {
        let mut period = test_earnings_period();
        period.total_revenues = Some(1000.0);
        period.cost_of_sales = None;
        period.gross_profit = Some(500.0);

        let warnings = validate_earnings_consistency(&mut period, &FormulaCheckTargets::default());
        assert!(warnings.iter().all(|w| !w.contains("Gross Profit")));
    }

    #[test]
    fn validate_earnings_consistency_skips_net_income_check_when_nci_present() {
        let mut period = test_earnings_period();
        period.operating_profit = Some(-14.04);
        period.total_non_operating_income = Some(0.45);
        period.income_before_provision_for_income_taxes = Some(-14.82);
        period.provision_for_income_taxes = Some(0.12);
        period.consolidated_net_income = Some(-2.21);
        period.net_income_attributable_to_common_shareholders = Some(-15.57);

        let warnings = validate_earnings_consistency(&mut period, &FormulaCheckTargets::default());
        assert!(warnings
            .iter()
            .all(|w| !w.contains("Consolidated Net Income")));
        assert!(warnings.iter().all(|w| !w.contains("Income Before Tax")));
    }

    #[test]
    fn validate_earnings_consistency_flags_ibt_when_gap_is_large() {
        let mut period = test_earnings_period();
        period.operating_profit = Some(100.0);
        period.total_non_operating_income = Some(10.0);
        period.income_before_provision_for_income_taxes = Some(50.0);

        let warnings = validate_earnings_consistency(&mut period, &FormulaCheckTargets::default());
        assert!(warnings.iter().any(|w| w.contains("Income Before Tax")));
    }

    #[test]
    fn validate_earnings_consistency_flags_net_income_when_no_nci() {
        let mut period = test_earnings_period();
        period.income_before_provision_for_income_taxes = Some(480.0);
        period.provision_for_income_taxes = Some(80.0);
        period.consolidated_net_income = Some(350.0);

        let warnings = validate_earnings_consistency(&mut period, &FormulaCheckTargets::default());
        assert!(warnings
            .iter()
            .any(|w| w.contains("Consolidated Net Income")));
    }

    fn test_earnings_period() -> EarningsPeriod {
        EarningsPeriod {
            label: "FY2024".to_string(),
            display_label: "FY 2024".to_string(),
            fiscal_year: Some("2024".to_string()),
            fiscal_period: Some("FY".to_string()),
            period_start: Some("2024-01-01".to_string()),
            period_end: "2024-12-31".to_string(),
            filed_date: "2025-02-28".to_string(),
            form: "10-K".to_string(),
            total_revenues: None,
            total_revenues_yoy_change_percent: None,
            cost_of_sales: None,
            gross_profit: None,
            gross_profit_margin: None,
            selling_general_and_administrative_expenses: None,
            research_and_development_expenses: None,
            other_operating_expenses: None,
            operating_profit: None,
            operating_margin: None,
            non_operating_income: None,
            total_non_operating_income: None,
            income_before_provision_for_income_taxes: None,
            provision_for_income_taxes: None,
            consolidated_net_income: None,
            net_income_attributable_to_common_shareholders: None,
            total_shares_outstanding: None,
            basic_weighted_average_shares_outstanding: None,
            diluted_weighted_average_shares_outstanding: None,
            ebitda: None,
            effective_tax_rate: None,
            revenue: None,
            net_income: None,
            basic_eps: None,
            diluted_eps: None,
            diluted_weighted_average_shares: None,
            revenue_yoy_change_percent: None,
            diluted_eps_yoy_change_percent: None,
        }
    }
}
