use serde::{Deserialize, Serialize};

#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
pub enum GrowthSetting {
    #[default]
    Neutral,
    ExpansionBias,
    DividendSuppressed,
}

#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
pub struct BondPosition {
    pub principal: i64,
    pub coupon_rate: f64,
    pub years_remaining: u8,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum BankruptcyReason {
    EarlyStress,
    DeepDistress,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum AnnualReportMetric {
    NetProfits,
    RevenueAggregate,
    FuelCost,
    RevenuePerShare,
    EarningsPerShare,
    DividendPerShare,
    BookValuePerShare,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum DebtNewsOutcome {
    RefinanceOnly,
    RefinanceAndBorrow,
    RefinanceAndPayDown,
    DebtPayoffOnly,
    NewBorrowingOnly,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
pub struct DebtRestructureSummary {
    pub retired_principal: i64,
    pub issued_principal: i64,
}

impl DebtRestructureSummary {
    pub fn classify(self) -> Option<DebtNewsOutcome> {
        match (self.retired_principal > 0, self.issued_principal > 0) {
            (false, false) => None,
            (false, true) => Some(DebtNewsOutcome::NewBorrowingOnly),
            (true, false) => Some(DebtNewsOutcome::DebtPayoffOnly),
            (true, true) if self.retired_principal == self.issued_principal => {
                Some(DebtNewsOutcome::RefinanceOnly)
            }
            (true, true) if self.issued_principal > self.retired_principal => {
                Some(DebtNewsOutcome::RefinanceAndBorrow)
            }
            (true, true) => Some(DebtNewsOutcome::RefinanceAndPayDown),
        }
    }
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum AnnualFinanceDecision {
    NoAction,
    DeclareBankruptcy {
        reason: BankruptcyReason,
    },
    IssueBond {
        count: u32,
        principal_per_bond: i64,
        term_years: u8,
    },
    RepurchasePublicShares {
        share_count: u32,
        price_per_share: f64,
    },
    IssuePublicShares {
        share_count_per_tranche: u32,
        tranche_count: u32,
        price_per_share: f64,
    },
    AdjustDividend {
        old_rate: f64,
        new_rate: f64,
    },
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct AnnualFinanceEvaluation {
    pub decision: AnnualFinanceDecision,
    pub debt_restructure: DebtRestructureSummary,
    pub debt_news: Option<DebtNewsOutcome>,
    pub repurchased_share_count: u32,
    pub issued_share_count: u32,
}

impl AnnualFinanceEvaluation {
    pub fn no_action() -> Self {
        Self {
            decision: AnnualFinanceDecision::NoAction,
            debt_restructure: DebtRestructureSummary::default(),
            debt_news: None,
            repurchased_share_count: 0,
            issued_share_count: 0,
        }
    }
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct AnnualFinancePolicy {
    pub annual_mode: u8,
    pub build_103_plus: bool,
    pub bankruptcy_allowed: bool,
    pub bond_issuance_allowed: bool,
    pub stock_actions_allowed: bool,
    pub dividends_allowed: bool,
    pub growth_setting: GrowthSetting,
    pub stock_issue_cash_buffer: i64,
}

impl Default for AnnualFinancePolicy {
    fn default() -> Self {
        Self {
            annual_mode: 0x0c,
            build_103_plus: true,
            bankruptcy_allowed: true,
            bond_issuance_allowed: true,
            stock_actions_allowed: true,
            dividends_allowed: true,
            growth_setting: GrowthSetting::Neutral,
            stock_issue_cash_buffer: 30_000,
        }
    }
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct CompanyFinanceState {
    pub active: bool,
    pub years_since_founding: u8,
    pub years_since_last_bankruptcy: u8,
    pub current_cash: i64,
    pub current_company_value: i64,
    pub support_adjusted_share_price: f64,
    pub book_value_per_share: f64,
    pub current_fuel_cost: i64,
    pub current_dividend_per_share: f64,
    pub board_dividend_ceiling: f64,
    pub outstanding_share_count: u32,
    pub unassigned_share_count: u32,
    pub city_connection_bonus_latch: bool,
    pub linked_transit_service_latch: bool,
    pub chairman_buyback_factor: Option<f64>,
    pub recent_net_profits: [i64; 3],
    pub recent_revenue_totals: [i64; 3],
    pub recent_revenue_per_share: [f64; 3],
    pub recent_earnings_per_share: [f64; 3],
    pub recent_dividend_per_share: [f64; 3],
    pub bonds: Vec<BondPosition>,
}

impl Default for CompanyFinanceState {
    fn default() -> Self {
        Self {
            active: true,
            years_since_founding: 5,
            years_since_last_bankruptcy: 20,
            current_cash: 0,
            current_company_value: 1_000_000,
            support_adjusted_share_price: 25.0,
            book_value_per_share: 20.0,
            current_fuel_cost: 0,
            current_dividend_per_share: 0.0,
            board_dividend_ceiling: 2.0,
            outstanding_share_count: 20_000,
            unassigned_share_count: 10_000,
            city_connection_bonus_latch: false,
            linked_transit_service_latch: false,
            chairman_buyback_factor: None,
            recent_net_profits: [10_000, 10_000, 10_000],
            recent_revenue_totals: [200_000, 180_000, 160_000],
            recent_revenue_per_share: [1.2, 1.1, 1.0],
            recent_earnings_per_share: [1.0, 0.9, 0.8],
            recent_dividend_per_share: [0.2, 0.2, 0.1],
            bonds: Vec::new(),
        }
    }
}

impl CompanyFinanceState {
    pub const BOND_PRINCIPAL: i64 = 500_000;
    pub const BOND_TERM_YEARS: u8 = 30;
    pub const SHARE_LOT: u32 = 1_000;

    pub fn total_debt_principal(&self) -> i64 {
        self.bonds.iter().map(|bond| bond.principal.max(0)).sum()
    }

    pub fn highest_coupon_bond(&self) -> Option<BondPosition> {
        self.bonds
            .iter()
            .copied()
            .max_by(|left, right| left.coupon_rate.total_cmp(&right.coupon_rate))
    }

    pub fn simulate_cash_after_full_bond_repayment(&self) -> i64 {
        self.current_cash - self.total_debt_principal()
    }

    pub fn declare_bankruptcy(&mut self) {
        for bond in &mut self.bonds {
            bond.principal /= 2;
        }
        self.current_company_value /= 2;
        self.years_since_last_bankruptcy = 0;
    }

    pub fn issue_bond(&mut self, coupon_rate: f64, count: u32) {
        for _ in 0..count {
            self.bonds.push(BondPosition {
                principal: Self::BOND_PRINCIPAL,
                coupon_rate,
                years_remaining: Self::BOND_TERM_YEARS,
            });
            self.current_cash += Self::BOND_PRINCIPAL;
        }
    }

    pub fn repurchase_public_shares(&mut self, share_count: u32, price_per_share: f64) {
        let repurchased = share_count.min(self.unassigned_share_count);
        self.unassigned_share_count -= repurchased;
        self.outstanding_share_count = self.outstanding_share_count.saturating_sub(repurchased);
        self.current_cash -= (repurchased as f64 * price_per_share).round() as i64;
    }

    pub fn issue_public_shares(&mut self, share_count: u32, price_per_share: f64) {
        self.outstanding_share_count = self.outstanding_share_count.saturating_add(share_count);
        self.unassigned_share_count = self.unassigned_share_count.saturating_add(share_count);
        self.current_cash += (share_count as f64 * price_per_share).round() as i64;
    }

    pub fn set_dividend_rate(&mut self, new_rate: f64) {
        self.current_dividend_per_share = new_rate.clamp(0.0, self.board_dividend_ceiling);
    }

    pub fn read_recent_metric(&self, metric: AnnualReportMetric, years_ago: usize) -> Option<f64> {
        match metric {
            AnnualReportMetric::FuelCost if years_ago == 0 => Some(self.current_fuel_cost as f64),
            AnnualReportMetric::BookValuePerShare if years_ago == 0 => {
                Some(self.book_value_per_share)
            }
            AnnualReportMetric::NetProfits => self
                .recent_net_profits
                .get(years_ago)
                .copied()
                .map(|value| value as f64),
            AnnualReportMetric::RevenueAggregate => self
                .recent_revenue_totals
                .get(years_ago)
                .copied()
                .map(|value| value as f64),
            AnnualReportMetric::RevenuePerShare => {
                self.recent_revenue_per_share.get(years_ago).copied()
            }
            AnnualReportMetric::EarningsPerShare => {
                self.recent_earnings_per_share.get(years_ago).copied()
            }
            AnnualReportMetric::DividendPerShare => {
                self.recent_dividend_per_share.get(years_ago).copied()
            }
            _ => None,
        }
    }

    pub fn read_recent_metric_window(&self, metric: AnnualReportMetric, years: usize) -> Vec<f64> {
        (0..years)
            .filter_map(|years_ago| self.read_recent_metric(metric, years_ago))
            .collect()
    }

    pub fn weighted_recent_metric(
        &self,
        metric: AnnualReportMetric,
        weights: &[f64],
    ) -> Option<f64> {
        let mut numerator = 0.0;
        let mut denominator = 0.0;
        for (years_ago, weight) in weights.iter().copied().enumerate() {
            let value = self.read_recent_metric(metric, years_ago)?;
            numerator += value * weight;
            denominator += weight;
        }

        (denominator > 0.0).then_some(numerator / denominator)
    }

    pub fn apply_annual_decision(&mut self, decision: &AnnualFinanceDecision) {
        match *decision {
            AnnualFinanceDecision::NoAction => {}
            AnnualFinanceDecision::DeclareBankruptcy { .. } => self.declare_bankruptcy(),
            AnnualFinanceDecision::IssueBond {
                count,
                principal_per_bond: _,
                term_years: _,
            } => {
                let coupon = self
                    .highest_coupon_bond()
                    .map(|bond| bond.coupon_rate)
                    .unwrap_or(0.10);
                self.issue_bond(coupon, count);
            }
            AnnualFinanceDecision::RepurchasePublicShares {
                share_count,
                price_per_share,
            } => self.repurchase_public_shares(share_count, price_per_share),
            AnnualFinanceDecision::IssuePublicShares {
                share_count_per_tranche,
                tranche_count,
                price_per_share,
            } => self.issue_public_shares(
                share_count_per_tranche.saturating_mul(tranche_count),
                price_per_share,
            ),
            AnnualFinanceDecision::AdjustDividend { new_rate, .. } => {
                self.set_dividend_rate(new_rate);
            }
        }
    }
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct FinanceSnapshot {
    pub policy: AnnualFinancePolicy,
    pub company: CompanyFinanceState,
}

impl FinanceSnapshot {
    pub fn evaluate(&self) -> FinanceOutcome {
        let evaluation = evaluate_annual_finance_policy_detailed(&self.policy, &self.company);
        let mut post_company = self.company.clone();
        post_company.apply_annual_decision(&evaluation.decision);

        FinanceOutcome {
            evaluation,
            post_company,
        }
    }
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct FinanceOutcome {
    pub evaluation: AnnualFinanceEvaluation,
    pub post_company: CompanyFinanceState,
}

pub fn evaluate_annual_finance_policy(
    policy: &AnnualFinancePolicy,
    company: &CompanyFinanceState,
) -> AnnualFinanceDecision {
    evaluate_annual_finance_policy_detailed(policy, company).decision
}

pub fn evaluate_annual_finance_policy_detailed(
    policy: &AnnualFinancePolicy,
    company: &CompanyFinanceState,
) -> AnnualFinanceEvaluation {
    if !company.active {
        return AnnualFinanceEvaluation::no_action();
    }

    if should_bankrupt_early(policy, company) {
        return AnnualFinanceEvaluation {
            decision: AnnualFinanceDecision::DeclareBankruptcy {
                reason: BankruptcyReason::EarlyStress,
            },
            ..AnnualFinanceEvaluation::no_action()
        };
    }

    if let Some(evaluation) = issue_bond_evaluation(policy, company) {
        return evaluation;
    }

    if let Some(evaluation) = repurchase_evaluation(policy, company) {
        return evaluation;
    }

    if should_bankrupt_deep_distress(policy, company) {
        return AnnualFinanceEvaluation {
            decision: AnnualFinanceDecision::DeclareBankruptcy {
                reason: BankruptcyReason::DeepDistress,
            },
            ..AnnualFinanceEvaluation::no_action()
        };
    }

    if let Some(evaluation) = issue_stock_evaluation(policy, company) {
        return evaluation;
    }

    if let Some(evaluation) = dividend_evaluation(policy, company) {
        return evaluation;
    }

    AnnualFinanceEvaluation::no_action()
}

fn should_bankrupt_early(policy: &AnnualFinancePolicy, company: &CompanyFinanceState) -> bool {
    if policy.annual_mode != 0x0c || !policy.bankruptcy_allowed {
        return false;
    }
    if company.years_since_last_bankruptcy < 13 || company.years_since_founding < 4 {
        return false;
    }

    let current_revenue = company.recent_revenue_totals[0];
    let stress_ladder: i64 = if current_revenue < 120_000 {
        -600_000
    } else if current_revenue < 230_000 {
        -1_100_000
    } else if current_revenue < 340_000 {
        -1_600_000
    } else {
        -2_000_000
    };

    let failed_profit_years = company
        .recent_net_profits
        .iter()
        .filter(|profit| **profit <= 0)
        .count();
    let net_profit_sum: i64 = company.recent_net_profits.iter().sum();
    let share_price_floor = if failed_profit_years == 3 { 20.0 } else { 15.0 };
    let fuel_gate = (stress_ladder.abs() as f64 * 0.08).round() as i64;

    failed_profit_years >= 2
        && net_profit_sum <= -60_000
        && company.support_adjusted_share_price >= share_price_floor
        && company.current_fuel_cost >= fuel_gate
}

fn should_bankrupt_deep_distress(
    policy: &AnnualFinancePolicy,
    company: &CompanyFinanceState,
) -> bool {
    policy.bankruptcy_allowed
        && company.current_cash < -300_000
        && company.years_since_founding >= 3
        && company.years_since_last_bankruptcy >= 5
        && company
            .recent_net_profits
            .iter()
            .all(|profit| *profit <= -20_000)
}

fn issue_bond_evaluation(
    policy: &AnnualFinancePolicy,
    company: &CompanyFinanceState,
) -> Option<AnnualFinanceEvaluation> {
    if !policy.bond_issuance_allowed {
        return None;
    }

    let simulated_cash = company.simulate_cash_after_full_bond_repayment();
    let target_floor = if company.linked_transit_service_latch {
        -30_000
    } else {
        -250_000
    };
    if simulated_cash >= target_floor {
        return None;
    }

    let shortfall = (target_floor - simulated_cash).max(0) as u64;
    let count = shortfall.div_ceil(CompanyFinanceState::BOND_PRINCIPAL as u64) as u32;
    let issued_principal = count.max(1) as i64 * CompanyFinanceState::BOND_PRINCIPAL;
    let debt_restructure = DebtRestructureSummary {
        retired_principal: 0,
        issued_principal,
    };

    Some(AnnualFinanceEvaluation {
        decision: AnnualFinanceDecision::IssueBond {
            count: count.max(1),
            principal_per_bond: CompanyFinanceState::BOND_PRINCIPAL,
            term_years: CompanyFinanceState::BOND_TERM_YEARS,
        },
        debt_news: debt_restructure.classify(),
        debt_restructure,
        ..AnnualFinanceEvaluation::no_action()
    })
}

fn repurchase_evaluation(
    policy: &AnnualFinancePolicy,
    company: &CompanyFinanceState,
) -> Option<AnnualFinanceEvaluation> {
    if !policy.stock_actions_allowed
        || !company.city_connection_bonus_latch
        || matches!(policy.growth_setting, GrowthSetting::DividendSuppressed)
        || company.unassigned_share_count < CompanyFinanceState::SHARE_LOT
        || company.current_company_value < 800_000
    {
        return None;
    }

    let mut factor = company.chairman_buyback_factor.unwrap_or(1.0);
    if matches!(policy.growth_setting, GrowthSetting::ExpansionBias) {
        factor *= 1.6;
    }

    let batch = CompanyFinanceState::SHARE_LOT;
    let affordability_gate = company.support_adjusted_share_price * factor * batch as f64 * 1.2;
    if company.current_cash < affordability_gate.round() as i64 {
        return None;
    }

    Some(AnnualFinanceEvaluation {
        decision: AnnualFinanceDecision::RepurchasePublicShares {
            share_count: batch,
            price_per_share: company.support_adjusted_share_price,
        },
        repurchased_share_count: batch,
        ..AnnualFinanceEvaluation::no_action()
    })
}

fn issue_stock_evaluation(
    policy: &AnnualFinancePolicy,
    company: &CompanyFinanceState,
) -> Option<AnnualFinanceEvaluation> {
    if !policy.build_103_plus
        || !policy.stock_actions_allowed
        || !policy.bond_issuance_allowed
        || company.bonds.len() < 2
        || company.years_since_founding < 1
        || company.support_adjusted_share_price < 22.0
        || company.book_value_per_share <= 0.0
    {
        return None;
    }

    let highest_coupon = company.highest_coupon_bond()?;
    if company.current_cash >= highest_coupon.principal + policy.stock_issue_cash_buffer {
        return None;
    }

    let mut tranche = ((company.outstanding_share_count / 10) / CompanyFinanceState::SHARE_LOT)
        * CompanyFinanceState::SHARE_LOT;
    tranche = tranche.max(2_000);
    while tranche >= CompanyFinanceState::SHARE_LOT
        && company.support_adjusted_share_price * tranche as f64 > 55_000.0
    {
        tranche -= CompanyFinanceState::SHARE_LOT;
    }
    if tranche < CompanyFinanceState::SHARE_LOT {
        return None;
    }

    let price_to_book = company.support_adjusted_share_price / company.book_value_per_share;
    if price_to_book < required_price_to_book_ratio(highest_coupon.coupon_rate) {
        return None;
    }

    Some(AnnualFinanceEvaluation {
        decision: AnnualFinanceDecision::IssuePublicShares {
            share_count_per_tranche: tranche,
            tranche_count: 2,
            price_per_share: company.support_adjusted_share_price,
        },
        issued_share_count: tranche * 2,
        ..AnnualFinanceEvaluation::no_action()
    })
}

fn dividend_evaluation(
    policy: &AnnualFinancePolicy,
    company: &CompanyFinanceState,
) -> Option<AnnualFinanceEvaluation> {
    if !policy.dividends_allowed || company.years_since_founding < 2 {
        return None;
    }

    let weighted_target = company
        .weighted_recent_metric(AnnualReportMetric::EarningsPerShare, &[3.0, 2.0, 1.0])
        .unwrap_or(0.0);
    let mut target = weighted_target.max(0.0);

    if company.unassigned_share_count < CompanyFinanceState::SHARE_LOT
        && company.outstanding_share_count > 0
        && company.current_cash > 0
    {
        target += company.current_cash as f64 / company.outstanding_share_count as f64;
    }

    target = match policy.growth_setting {
        GrowthSetting::Neutral => target,
        GrowthSetting::ExpansionBias => target * 0.66,
        GrowthSetting::DividendSuppressed => 0.0,
    };
    target = quantize_tenths(target.clamp(0.0, company.board_dividend_ceiling));

    if (target - company.current_dividend_per_share).abs() <= 0.1 {
        return None;
    }

    Some(AnnualFinanceEvaluation {
        decision: AnnualFinanceDecision::AdjustDividend {
            old_rate: company.current_dividend_per_share,
            new_rate: target,
        },
        ..AnnualFinanceEvaluation::no_action()
    })
}

fn required_price_to_book_ratio(coupon_rate: f64) -> f64 {
    if coupon_rate <= 0.07 {
        1.3
    } else if coupon_rate <= 0.08 {
        1.2
    } else if coupon_rate <= 0.09 {
        1.1
    } else if coupon_rate <= 0.10 {
        0.95
    } else if coupon_rate <= 0.11 {
        0.8
    } else if coupon_rate <= 0.12 {
        0.62
    } else if coupon_rate <= 0.13 {
        0.5
    } else {
        0.35
    }
}

fn quantize_tenths(value: f64) -> f64 {
    (value * 10.0).round() / 10.0
}

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

    fn base_policy() -> AnnualFinancePolicy {
        AnnualFinancePolicy::default()
    }

    fn base_company() -> CompanyFinanceState {
        CompanyFinanceState {
            bonds: vec![
                BondPosition {
                    principal: 400_000,
                    coupon_rate: 0.10,
                    years_remaining: 10,
                },
                BondPosition {
                    principal: 300_000,
                    coupon_rate: 0.12,
                    years_remaining: 12,
                },
            ],
            ..CompanyFinanceState::default()
        }
    }

    #[test]
    fn early_bankruptcy_precedes_other_actions() {
        let policy = base_policy();
        let company = CompanyFinanceState {
            current_fuel_cost: 90_000,
            support_adjusted_share_price: 21.0,
            recent_net_profits: [-30_000, -25_000, -20_000],
            recent_revenue_totals: [150_000, 140_000, 130_000],
            city_connection_bonus_latch: true,
            linked_transit_service_latch: true,
            ..base_company()
        };

        let decision = evaluate_annual_finance_policy(&policy, &company);
        assert_eq!(
            decision,
            AnnualFinanceDecision::DeclareBankruptcy {
                reason: BankruptcyReason::EarlyStress,
            }
        );
    }

    #[test]
    fn bond_issue_precedes_stock_issue_when_cash_window_fails() {
        let policy = base_policy();
        let company = CompanyFinanceState {
            current_cash: -900_000,
            support_adjusted_share_price: 30.0,
            book_value_per_share: 20.0,
            linked_transit_service_latch: true,
            recent_net_profits: [20_000, 10_000, 5_000],
            ..base_company()
        };

        let decision = evaluate_annual_finance_policy(&policy, &company);
        assert_eq!(
            decision,
            AnnualFinanceDecision::IssueBond {
                count: 4,
                principal_per_bond: CompanyFinanceState::BOND_PRINCIPAL,
                term_years: CompanyFinanceState::BOND_TERM_YEARS,
            }
        );
    }

    #[test]
    fn stock_issue_checks_liquidity_before_valuation() {
        let policy = AnnualFinancePolicy {
            bond_issuance_allowed: false,
            dividends_allowed: false,
            ..base_policy()
        };
        let company = CompanyFinanceState {
            current_cash: 500_000,
            support_adjusted_share_price: 30.0,
            book_value_per_share: 20.0,
            recent_net_profits: [40_000, 30_000, 20_000],
            recent_revenue_totals: [250_000, 240_000, 230_000],
            ..base_company()
        };

        let decision = evaluate_annual_finance_policy(&policy, &company);
        assert_eq!(decision, AnnualFinanceDecision::NoAction);
    }

    #[test]
    fn stock_issue_emits_two_tranches_when_gates_pass() {
        let policy = AnnualFinancePolicy {
            dividends_allowed: false,
            ..base_policy()
        };
        let company = CompanyFinanceState {
            current_cash: 100_000,
            support_adjusted_share_price: 27.5,
            book_value_per_share: 20.0,
            outstanding_share_count: 60_000,
            recent_net_profits: [40_000, 30_000, 20_000],
            recent_revenue_totals: [250_000, 240_000, 230_000],
            bonds: vec![
                BondPosition {
                    principal: 150_000,
                    coupon_rate: 0.12,
                    years_remaining: 12,
                },
                BondPosition {
                    principal: 10_000,
                    coupon_rate: 0.10,
                    years_remaining: 10,
                },
            ],
            ..base_company()
        };

        let decision = evaluate_annual_finance_policy(&policy, &company);
        assert_eq!(
            decision,
            AnnualFinanceDecision::IssuePublicShares {
                share_count_per_tranche: 2_000,
                tranche_count: 2,
                price_per_share: 27.5,
            }
        );
    }

    #[test]
    fn recent_metric_reader_exposes_report_lanes() {
        let company = CompanyFinanceState {
            current_fuel_cost: 12_345,
            recent_net_profits: [11_000, 22_000, 33_000],
            recent_revenue_totals: [101_000, 102_000, 103_000],
            recent_revenue_per_share: [1.6, 1.5, 1.4],
            recent_earnings_per_share: [1.3, 1.2, 1.1],
            recent_dividend_per_share: [0.4, 0.3, 0.2],
            book_value_per_share: 19.5,
            ..base_company()
        };

        assert_eq!(
            company.read_recent_metric(AnnualReportMetric::NetProfits, 1),
            Some(22_000.0)
        );
        assert_eq!(
            company.read_recent_metric(AnnualReportMetric::RevenuePerShare, 2),
            Some(1.4)
        );
        assert_eq!(
            company.read_recent_metric(AnnualReportMetric::FuelCost, 0),
            Some(12_345.0)
        );
        assert_eq!(
            company.read_recent_metric(AnnualReportMetric::BookValuePerShare, 0),
            Some(19.5)
        );
        assert_eq!(
            company.weighted_recent_metric(AnnualReportMetric::EarningsPerShare, &[3.0, 2.0, 1.0]),
            Some(1.2333333333333334)
        );
    }

    #[test]
    fn debt_news_classifies_borrow_and_paydown_paths() {
        assert_eq!(
            DebtRestructureSummary {
                retired_principal: 500_000,
                issued_principal: 500_000,
            }
            .classify(),
            Some(DebtNewsOutcome::RefinanceOnly)
        );
        assert_eq!(
            DebtRestructureSummary {
                retired_principal: 300_000,
                issued_principal: 500_000,
            }
            .classify(),
            Some(DebtNewsOutcome::RefinanceAndBorrow)
        );
        assert_eq!(
            DebtRestructureSummary {
                retired_principal: 500_000,
                issued_principal: 300_000,
            }
            .classify(),
            Some(DebtNewsOutcome::RefinanceAndPayDown)
        );
    }

    #[test]
    fn detailed_evaluation_carries_share_and_debt_side_effects() {
        let policy = base_policy();
        let company = CompanyFinanceState {
            current_cash: -900_000,
            support_adjusted_share_price: 30.0,
            book_value_per_share: 20.0,
            linked_transit_service_latch: true,
            recent_net_profits: [20_000, 10_000, 5_000],
            ..base_company()
        };

        let evaluation = evaluate_annual_finance_policy_detailed(&policy, &company);
        assert_eq!(
            evaluation.decision,
            AnnualFinanceDecision::IssueBond {
                count: 4,
                principal_per_bond: CompanyFinanceState::BOND_PRINCIPAL,
                term_years: CompanyFinanceState::BOND_TERM_YEARS,
            }
        );
        assert_eq!(
            evaluation.debt_news,
            Some(DebtNewsOutcome::NewBorrowingOnly)
        );
        assert_eq!(evaluation.debt_restructure.issued_principal, 2_000_000);
        assert_eq!(evaluation.repurchased_share_count, 0);
        assert_eq!(evaluation.issued_share_count, 0);
    }

    #[test]
    fn snapshot_evaluation_applies_post_state_transition() {
        let snapshot = FinanceSnapshot {
            policy: AnnualFinancePolicy {
                dividends_allowed: false,
                ..base_policy()
            },
            company: CompanyFinanceState {
                current_cash: 100_000,
                support_adjusted_share_price: 27.5,
                book_value_per_share: 20.0,
                outstanding_share_count: 60_000,
                recent_net_profits: [40_000, 30_000, 20_000],
                recent_revenue_totals: [250_000, 240_000, 230_000],
                bonds: vec![
                    BondPosition {
                        principal: 150_000,
                        coupon_rate: 0.12,
                        years_remaining: 12,
                    },
                    BondPosition {
                        principal: 10_000,
                        coupon_rate: 0.10,
                        years_remaining: 10,
                    },
                ],
                ..base_company()
            },
        };

        let outcome = snapshot.evaluate();
        assert_eq!(
            outcome.evaluation.decision,
            AnnualFinanceDecision::IssuePublicShares {
                share_count_per_tranche: 2_000,
                tranche_count: 2,
                price_per_share: 27.5,
            }
        );
        assert_eq!(outcome.evaluation.issued_share_count, 4_000);
        assert_eq!(outcome.post_company.outstanding_share_count, 64_000);
        assert_eq!(outcome.post_company.unassigned_share_count, 14_000);
        assert_eq!(outcome.post_company.current_cash, 210_000);
    }

    #[test]
    fn dividend_target_is_quantized_and_clamped() {
        let policy = AnnualFinancePolicy {
            bond_issuance_allowed: false,
            ..base_policy()
        };
        let company = CompanyFinanceState {
            current_cash: 20_000,
            board_dividend_ceiling: 0.9,
            current_dividend_per_share: 0.2,
            unassigned_share_count: 500,
            outstanding_share_count: 10_000,
            recent_earnings_per_share: [1.4, 1.1, 0.9],
            ..base_company()
        };

        let decision = evaluate_annual_finance_policy(&policy, &company);
        assert_eq!(
            decision,
            AnnualFinanceDecision::AdjustDividend {
                old_rate: 0.2,
                new_rate: 0.9,
            }
        );
    }

    #[test]
    fn bankruptcy_mutator_halves_bond_principal() {
        let mut company = base_company();
        company.current_company_value = 900_000;
        company.years_since_last_bankruptcy = 25;

        company.apply_annual_decision(&AnnualFinanceDecision::DeclareBankruptcy {
            reason: BankruptcyReason::DeepDistress,
        });

        assert_eq!(company.bonds[0].principal, 200_000);
        assert_eq!(company.bonds[1].principal, 150_000);
        assert_eq!(company.current_company_value, 450_000);
        assert_eq!(company.years_since_last_bankruptcy, 0);
    }
}
