use std::collections::BTreeMap;

use crate::derived::{
    runtime_company_live_bond_coupon_burden_total, runtime_company_trailing_full_year_stat_series,
    runtime_decode_packed_calendar_tuple, runtime_pack_packed_calendar_tuple_to_absolute_counter,
    runtime_world_absolute_counter, runtime_world_partial_year_weight_numerator,
};
use crate::event::metrics::RuntimeCompanyMarketMetric;
use crate::state::{
    RuntimeCompanyAnnualFinanceState, RuntimeCompanyPeriodicServiceState, RuntimeState,
    RuntimeWorldRoutePreferenceOverrideState,
};

pub fn runtime_company_unassigned_share_pool(state: &RuntimeState, company_id: u32) -> Option<u32> {
    let outstanding_shares = state
        .service_state
        .company_market_state
        .get(&company_id)?
        .outstanding_shares;
    let assigned_shares = runtime_company_assigned_share_pool(state, company_id)?;
    Some(outstanding_shares.saturating_sub(assigned_shares))
}

pub fn runtime_company_assigned_share_pool(state: &RuntimeState, company_id: u32) -> Option<u32> {
    state.service_state.company_market_state.get(&company_id)?;
    Some(
        state
            .chairman_profiles
            .iter()
            .filter_map(|profile| profile.company_holdings.get(&company_id).copied())
            .sum::<u32>(),
    )
}

pub fn runtime_company_annual_finance_state(
    state: &RuntimeState,
    company_id: u32,
) -> Option<RuntimeCompanyAnnualFinanceState> {
    let market_state = state.service_state.company_market_state.get(&company_id)?;
    let periodic_service_state = runtime_company_periodic_service_state(state, company_id);
    let assigned_share_pool = runtime_company_assigned_share_pool(state, company_id)?;
    let unassigned_share_pool = runtime_company_unassigned_share_pool(state, company_id)?;
    let years_since_founding =
        derive_runtime_company_elapsed_years(state.calendar.year, market_state.founding_year);
    let years_since_last_bankruptcy = derive_runtime_company_elapsed_years(
        state.calendar.year,
        market_state.last_bankruptcy_year,
    );
    let years_since_last_dividend =
        derive_runtime_company_elapsed_years(state.calendar.year, market_state.last_dividend_year);
    let current_issue_absolute_counter = runtime_pack_packed_calendar_tuple_to_absolute_counter(
        runtime_decode_packed_calendar_tuple(
            market_state.current_issue_calendar_word,
            market_state.current_issue_calendar_word_2,
        ),
    );
    let prior_issue_absolute_counter = runtime_pack_packed_calendar_tuple_to_absolute_counter(
        runtime_decode_packed_calendar_tuple(
            market_state.prior_issue_calendar_word,
            market_state.prior_issue_calendar_word_2,
        ),
    );
    let current_issue_age_absolute_counter_delta = match (
        runtime_world_absolute_counter(state),
        current_issue_absolute_counter,
    ) {
        (Some(world_counter), Some(issue_counter)) if world_counter >= issue_counter => {
            Some(i64::from(world_counter - issue_counter))
        }
        _ => None,
    };
    let (trailing_full_year_year_words, trailing_full_year_net_profits) =
        runtime_company_trailing_full_year_stat_series(state, company_id, 0x2b, 4)
            .unwrap_or_default();
    let (_, trailing_full_year_revenues) =
        runtime_company_trailing_full_year_stat_series(state, company_id, 0x2c, 4)
            .unwrap_or_default();
    let (_, trailing_full_year_fuel_costs) =
        runtime_company_trailing_full_year_stat_series(state, company_id, 0x09, 4)
            .unwrap_or_default();
    Some(RuntimeCompanyAnnualFinanceState {
        company_id,
        outstanding_shares: market_state.outstanding_shares,
        bond_count: market_state.bond_count,
        largest_live_bond_principal: market_state.largest_live_bond_principal,
        highest_coupon_live_bond_principal: market_state.highest_coupon_live_bond_principal,
        live_bond_coupon_burden_total: runtime_company_live_bond_coupon_burden_total(
            state, company_id,
        ),
        assigned_share_pool,
        unassigned_share_pool,
        cached_share_price: rounded_cached_share_price_i64(market_state.cached_share_price_raw_u32),
        chairman_salary_baseline: market_state.chairman_salary_baseline,
        chairman_salary_current: market_state.chairman_salary_current,
        chairman_bonus_year: market_state.chairman_bonus_year,
        chairman_bonus_amount: market_state.chairman_bonus_amount,
        founding_year: market_state.founding_year,
        last_bankruptcy_year: market_state.last_bankruptcy_year,
        last_dividend_year: market_state.last_dividend_year,
        years_since_founding,
        years_since_last_bankruptcy,
        years_since_last_dividend,
        current_partial_year_weight_numerator: runtime_world_partial_year_weight_numerator(state),
        trailing_full_year_year_words,
        trailing_full_year_net_profits,
        trailing_full_year_revenues,
        trailing_full_year_fuel_costs,
        current_issue_absolute_counter,
        prior_issue_absolute_counter,
        current_issue_age_absolute_counter_delta,
        current_issue_calendar_word: market_state.current_issue_calendar_word,
        current_issue_calendar_word_2: market_state.current_issue_calendar_word_2,
        prior_issue_calendar_word: market_state.prior_issue_calendar_word,
        prior_issue_calendar_word_2: market_state.prior_issue_calendar_word_2,
        preferred_locomotive_engine_type_raw_u8: periodic_service_state
            .as_ref()
            .and_then(|service_state| service_state.preferred_locomotive_engine_type_raw_u8),
        city_connection_latch: periodic_service_state
            .as_ref()
            .map(|service_state| service_state.city_connection_latch)
            .unwrap_or(market_state.city_connection_latch),
        linked_transit_latch: periodic_service_state
            .as_ref()
            .map(|service_state| service_state.linked_transit_latch)
            .unwrap_or(market_state.linked_transit_latch),
    })
}

pub fn runtime_company_periodic_service_state(
    state: &RuntimeState,
    company_id: u32,
) -> Option<RuntimeCompanyPeriodicServiceState> {
    const DEFAULT_ROUTE_QUALITY_MULTIPLIER_BASIS_POINTS: i64 = 140;
    const ELECTRIC_ROUTE_QUALITY_MULTIPLIER_BASIS_POINTS: i64 = 180;
    const ELECTRIC_ENGINE_TYPE_RAW_U8: u8 = 2;

    let market_state = state.service_state.company_market_state.get(&company_id)?;
    let periodic_side_latch_state = state
        .service_state
        .company_periodic_side_latch_state
        .get(&company_id);
    let preferred_locomotive_engine_type_raw_u8 = periodic_side_latch_state
        .and_then(|latch_state| latch_state.preferred_locomotive_engine_type_raw_u8);
    let base_route_preference_raw_u8 = state.world_restore.auto_show_grade_during_track_lay_raw_u8;
    let electric_route_preference_override_active =
        preferred_locomotive_engine_type_raw_u8 == Some(ELECTRIC_ENGINE_TYPE_RAW_U8);
    let effective_route_preference_raw_u8 = if electric_route_preference_override_active {
        Some(ELECTRIC_ENGINE_TYPE_RAW_U8)
    } else {
        base_route_preference_raw_u8
    };
    Some(RuntimeCompanyPeriodicServiceState {
        company_id,
        preferred_locomotive_engine_type_raw_u8,
        city_connection_latch: periodic_side_latch_state
            .map(|latch_state| latch_state.city_connection_latch)
            .unwrap_or(market_state.city_connection_latch),
        linked_transit_latch: periodic_side_latch_state
            .map(|latch_state| latch_state.linked_transit_latch)
            .unwrap_or(market_state.linked_transit_latch),
        base_route_preference_raw_u8,
        effective_route_preference_raw_u8,
        electric_route_preference_override_active,
        effective_route_quality_multiplier_basis_points:
            if electric_route_preference_override_active {
                ELECTRIC_ROUTE_QUALITY_MULTIPLIER_BASIS_POINTS
            } else {
                DEFAULT_ROUTE_QUALITY_MULTIPLIER_BASIS_POINTS
            },
    })
}

pub fn runtime_apply_company_periodic_route_preference_override(
    state: &mut RuntimeState,
    company_id: u32,
) -> Option<RuntimeWorldRoutePreferenceOverrideState> {
    let periodic_service_state = runtime_company_periodic_service_state(state, company_id)?;
    if !periodic_service_state.electric_route_preference_override_active {
        return None;
    }
    let override_state = RuntimeWorldRoutePreferenceOverrideState {
        company_id,
        base_route_preference_raw_u8: periodic_service_state.base_route_preference_raw_u8,
        effective_route_preference_raw_u8: periodic_service_state.effective_route_preference_raw_u8,
        electric_route_preference_override_active: true,
    };
    state.world_restore.auto_show_grade_during_track_lay_raw_u8 =
        override_state.effective_route_preference_raw_u8;
    state
        .service_state
        .active_periodic_route_preference_override = Some(override_state.clone());
    state.service_state.last_periodic_route_preference_override = Some(override_state.clone());
    state
        .service_state
        .periodic_route_preference_override_apply_count += 1;
    Some(override_state)
}

pub fn runtime_restore_company_periodic_route_preference_override(
    state: &mut RuntimeState,
) -> Option<RuntimeWorldRoutePreferenceOverrideState> {
    let override_state = state
        .service_state
        .active_periodic_route_preference_override
        .take()?;
    state.world_restore.auto_show_grade_during_track_lay_raw_u8 =
        override_state.base_route_preference_raw_u8;
    state
        .service_state
        .periodic_route_preference_override_restore_count += 1;
    Some(override_state)
}

pub fn runtime_company_market_metric_value(
    state: &RuntimeState,
    company_id: u32,
    metric: RuntimeCompanyMarketMetric,
) -> Option<i64> {
    let annual_finance_state = runtime_company_annual_finance_state(state, company_id)?;
    match metric {
        RuntimeCompanyMarketMetric::OutstandingShares => {
            Some(annual_finance_state.outstanding_shares as i64)
        }
        RuntimeCompanyMarketMetric::BondCount => Some(annual_finance_state.bond_count as i64),
        RuntimeCompanyMarketMetric::LargestLiveBondPrincipal => annual_finance_state
            .largest_live_bond_principal
            .map(|value| value as i64),
        RuntimeCompanyMarketMetric::HighestCouponLiveBondPrincipal => annual_finance_state
            .highest_coupon_live_bond_principal
            .map(|value| value as i64),
        RuntimeCompanyMarketMetric::AssignedSharePool => {
            Some(annual_finance_state.assigned_share_pool as i64)
        }
        RuntimeCompanyMarketMetric::UnassignedSharePool => {
            Some(annual_finance_state.unassigned_share_pool as i64)
        }
        RuntimeCompanyMarketMetric::CachedSharePrice => annual_finance_state.cached_share_price,
        RuntimeCompanyMarketMetric::ChairmanSalaryBaseline => {
            Some(annual_finance_state.chairman_salary_baseline as i64)
        }
        RuntimeCompanyMarketMetric::ChairmanSalaryCurrent => {
            Some(annual_finance_state.chairman_salary_current as i64)
        }
        RuntimeCompanyMarketMetric::ChairmanBonusAmount => {
            Some(annual_finance_state.chairman_bonus_amount as i64)
        }
        RuntimeCompanyMarketMetric::CurrentIssueAbsoluteCounter => annual_finance_state
            .current_issue_absolute_counter
            .map(i64::from),
        RuntimeCompanyMarketMetric::PriorIssueAbsoluteCounter => annual_finance_state
            .prior_issue_absolute_counter
            .map(i64::from),
        RuntimeCompanyMarketMetric::CurrentIssueAgeAbsoluteCounterDelta => {
            annual_finance_state.current_issue_age_absolute_counter_delta
        }
        RuntimeCompanyMarketMetric::CurrentIssueCalendarWord => {
            Some(annual_finance_state.current_issue_calendar_word as i64)
        }
        RuntimeCompanyMarketMetric::CurrentIssueCalendarWord2 => {
            Some(annual_finance_state.current_issue_calendar_word_2 as i64)
        }
        RuntimeCompanyMarketMetric::PriorIssueCalendarWord => {
            Some(annual_finance_state.prior_issue_calendar_word as i64)
        }
        RuntimeCompanyMarketMetric::PriorIssueCalendarWord2 => {
            Some(annual_finance_state.prior_issue_calendar_word_2 as i64)
        }
    }
}

pub(crate) fn rounded_cached_share_price_i64(raw_u32: u32) -> Option<i64> {
    let value = f32::from_bits(raw_u32);
    if !value.is_finite() {
        return None;
    }
    if value < i64::MIN as f32 || value > i64::MAX as f32 {
        return None;
    }
    Some(value.round() as i64)
}

pub(in crate::derived) fn runtime_decode_saved_f64_bits(bits: u64) -> Option<f64> {
    let value = f64::from_bits(bits);
    if !value.is_finite() {
        return None;
    }
    Some(value)
}

pub(crate) fn runtime_round_f64_to_i64(value: f64) -> Option<i64> {
    if !value.is_finite() {
        return None;
    }
    if value < i64::MIN as f64 || value > i64::MAX as f64 {
        return None;
    }
    Some(value.round() as i64)
}

pub(in crate::derived) fn derive_runtime_company_elapsed_years(
    current_year: u32,
    prior_year: u32,
) -> Option<u32> {
    if prior_year == 0 || prior_year > current_year {
        return None;
    }
    Some(current_year - prior_year)
}

pub(crate) fn derive_runtime_chairman_holdings_share_price_total(
    holdings_by_company: &BTreeMap<u32, u32>,
    company_share_prices: &BTreeMap<u32, i64>,
) -> Option<i64> {
    let mut total = 0i64;
    for (company_id, units) in holdings_by_company {
        let share_price = *company_share_prices.get(company_id)?;
        total = total.checked_add((*units as i64).checked_mul(share_price)?)?;
    }
    Some(total)
}
