use crate::derived::{
    runtime_company_annual_finance_state, runtime_company_control_transfer_stat_value_f64,
    runtime_company_credit_rating, runtime_company_matured_live_bond_count,
    runtime_company_matured_live_bond_principal_total,
    runtime_company_next_live_bond_maturity_year, runtime_company_prime_rate,
    runtime_company_total_live_bond_principal, runtime_round_f64_to_i64,
    runtime_world_annual_finance_mode_active, runtime_world_bond_issue_and_repayment_allowed,
};
use crate::event::metrics::RUNTIME_COMPANY_STAT_SLOT_CURRENT_CASH;
use crate::state::{RuntimeCompanyAnnualBondPolicyState, RuntimeState};

pub fn runtime_company_average_live_bond_coupon(
    state: &RuntimeState,
    company_id: u32,
) -> Option<f64> {
    let market_state = state.service_state.company_market_state.get(&company_id)?;
    if market_state.live_bond_slots.is_empty() {
        return Some(0.0);
    }
    let mut weighted_coupon_sum = 0.0f64;
    let mut total_principal = 0u64;
    for slot in &market_state.live_bond_slots {
        let coupon_rate = f32::from_bits(slot.coupon_rate_raw_u32) as f64;
        if !coupon_rate.is_finite() {
            continue;
        }
        weighted_coupon_sum += coupon_rate * (slot.principal as f64);
        total_principal = total_principal.checked_add(slot.principal as u64)?;
    }
    if total_principal == 0 {
        return Some(0.0);
    }
    Some(weighted_coupon_sum / total_principal as f64)
}

pub fn runtime_company_live_bond_coupon_burden_total(
    state: &RuntimeState,
    company_id: u32,
) -> Option<i64> {
    let market_state = state.service_state.company_market_state.get(&company_id)?;
    let mut total = 0i64;
    for slot in &market_state.live_bond_slots {
        let coupon_rate = f32::from_bits(slot.coupon_rate_raw_u32) as f64;
        if !coupon_rate.is_finite() {
            continue;
        }
        let coupon_burden =
            runtime_round_f64_to_i64((slot.principal as f64) * coupon_rate).unwrap_or(0);
        total = total.checked_add(coupon_burden)?;
    }
    Some(total)
}

pub fn runtime_company_bond_interest_rate_quote(
    state: &RuntimeState,
    company_id: u32,
    _principal: u32,
    _years_to_maturity: u32,
) -> Option<f64> {
    let credit_rating = runtime_company_credit_rating(state, company_id)? as f64;
    let prime_rate_percent = runtime_company_prime_rate(state, company_id)? as f64;
    let quote = (prime_rate_percent + (10.0 - credit_rating)) / 100.0;
    quote.is_finite().then_some(quote)
}

pub fn runtime_company_annual_bond_policy_state(
    state: &RuntimeState,
    company_id: u32,
) -> Option<RuntimeCompanyAnnualBondPolicyState> {
    const STANDARD_CASH_FLOOR: i64 = -250_000;
    const LINKED_TRANSIT_CASH_FLOOR: i64 = -30_000;
    const ISSUE_PRINCIPAL_STEP: u32 = 500_000;
    const ISSUE_YEARS_TO_MATURITY: u32 = 30;

    let annual_finance_state = runtime_company_annual_finance_state(state, company_id)?;
    let current_year_word = state
        .world_restore
        .packed_year_word_raw_u16
        .map(u32::from)
        .unwrap_or(state.calendar.year);
    let current_cash = runtime_company_control_transfer_stat_value_f64(
        state,
        company_id,
        RUNTIME_COMPANY_STAT_SLOT_CURRENT_CASH,
    )
    .and_then(runtime_round_f64_to_i64);
    let live_bond_principal_total = runtime_company_total_live_bond_principal(state, company_id);
    let matured_live_bond_count =
        runtime_company_matured_live_bond_count(state, company_id, current_year_word);
    let matured_live_bond_principal_total =
        runtime_company_matured_live_bond_principal_total(state, company_id, current_year_word);
    let next_live_bond_maturity_year =
        runtime_company_next_live_bond_maturity_year(state, company_id);
    let cash_after_full_repayment = current_cash
        .zip(live_bond_principal_total)
        .map(|(cash, principal)| cash - i64::from(principal));
    let issue_cash_floor = Some(if annual_finance_state.linked_transit_latch {
        LINKED_TRANSIT_CASH_FLOOR
    } else {
        STANDARD_CASH_FLOOR
    });
    let proposed_issue_bond_count = cash_after_full_repayment.zip(issue_cash_floor).map(
        |(cash_after_repayment, cash_floor)| {
            if cash_after_repayment >= cash_floor {
                0
            } else {
                let deficit = (cash_floor - cash_after_repayment) as u64;
                deficit.div_ceil(u64::from(ISSUE_PRINCIPAL_STEP)) as u32
            }
        },
    );
    let proposed_issue_total_principal =
        proposed_issue_bond_count.and_then(|count| count.checked_mul(ISSUE_PRINCIPAL_STEP));
    let eligible_for_bond_issue_branch = runtime_world_annual_finance_mode_active(state)
        == Some(true)
        && runtime_world_bond_issue_and_repayment_allowed(state) == Some(true)
        && (matured_live_bond_principal_total.is_some_and(|principal| principal > 0)
            || proposed_issue_bond_count.is_some_and(|count| count > 0));
    Some(RuntimeCompanyAnnualBondPolicyState {
        company_id,
        annual_mode_active: runtime_world_annual_finance_mode_active(state),
        bond_issue_and_repayment_allowed: runtime_world_bond_issue_and_repayment_allowed(state),
        linked_transit_latch: annual_finance_state.linked_transit_latch,
        live_bond_count: Some(annual_finance_state.bond_count),
        live_bond_principal_total,
        matured_live_bond_count,
        matured_live_bond_principal_total,
        next_live_bond_maturity_year,
        live_bond_coupon_burden_total: annual_finance_state.live_bond_coupon_burden_total,
        current_cash,
        cash_after_full_repayment,
        issue_cash_floor,
        issue_principal_step: Some(ISSUE_PRINCIPAL_STEP),
        proposed_issue_bond_count,
        proposed_issue_total_principal,
        proposed_issue_years_to_maturity: Some(ISSUE_YEARS_TO_MATURITY),
        eligible_for_bond_issue_branch,
    })
}

pub fn runtime_annual_bond_principal_flow_relation_label(
    retired_principal_total: u64,
    issued_principal_total: u64,
) -> Option<&'static str> {
    match retired_principal_total.cmp(&issued_principal_total) {
        std::cmp::Ordering::Equal => {
            if retired_principal_total == 0 {
                None
            } else {
                Some("retired_equals_issued")
            }
        }
        std::cmp::Ordering::Greater => {
            if issued_principal_total == 0 {
                Some("retired_only")
            } else {
                Some("retired_exceeds_issued")
            }
        }
        std::cmp::Ordering::Less => {
            if retired_principal_total == 0 {
                Some("issued_only")
            } else {
                Some("issued_exceeds_retired")
            }
        }
    }
}
