use super::*;

#[test]
fn applies_company_effects_for_specific_targets() {
    let mut state = RuntimeState {
        companies: vec![
            RuntimeCompany {
                company_id: 1,
                controller_kind: RuntimeCompanyControllerKind::Unknown,
                current_cash: 10,
                debt: 5,
                credit_rating_score: None,
                prime_rate: None,
                track_piece_counts: RuntimeTrackPieceCounts::default(),
                active: true,
                available_track_laying_capacity: None,
                linked_chairman_profile_id: None,
                book_value_per_share: 0,
                investor_confidence: 0,
                management_attitude: 0,
                takeover_cooldown_year: None,
                merger_cooldown_year: None,
            },
            RuntimeCompany {
                company_id: 2,
                controller_kind: RuntimeCompanyControllerKind::Unknown,
                current_cash: 20,
                debt: 8,
                credit_rating_score: None,
                prime_rate: None,
                track_piece_counts: RuntimeTrackPieceCounts::default(),
                active: true,
                available_track_laying_capacity: None,
                linked_chairman_profile_id: None,
                book_value_per_share: 0,
                investor_confidence: 0,
                management_attitude: 0,
                takeover_cooldown_year: None,
                merger_cooldown_year: None,
            },
        ],
        event_runtime_records: vec![RuntimeEventRecord {
            record_id: 10,
            trigger_kind: 7,
            active: true,
            service_count: 0,
            marks_collection_dirty: false,
            one_shot: false,
            has_fired: false,
            conditions: Vec::new(),
            effects: vec![
                RuntimeEffect::AdjustCompanyCash {
                    target: RuntimeCompanyTarget::Ids { ids: vec![2] },
                    delta: 4,
                },
                RuntimeEffect::AdjustCompanyDebt {
                    target: RuntimeCompanyTarget::Ids { ids: vec![2] },
                    delta: -3,
                },
            ],
        }],
        ..state()
    };

    let result = execute_step_command(
        &mut state,
        &StepCommand::ServiceTriggerKind { trigger_kind: 7 },
    )
    .expect("targeted company effects should succeed");

    assert_eq!(state.companies[0].current_cash, 10);
    assert_eq!(state.companies[1].current_cash, 24);
    assert_eq!(state.companies[1].debt, 5);
    assert_eq!(result.service_events[0].applied_effect_count, 2);
    assert_eq!(result.service_events[0].mutated_company_ids, vec![2]);
}

#[test]
fn set_company_cash_updates_owner_state_backed_current_cash() {
    let mut year_stat_family_qword_bits = vec![
        0u64;
        ((RUNTIME_COMPANY_STAT_SLOT_COUNT + 2) * RUNTIME_COMPANY_YEAR_STAT_FAMILY_SPAN)
            as usize
    ];
    year_stat_family_qword_bits[(RUNTIME_COMPANY_STAT_SLOT_CURRENT_CASH
        * RUNTIME_COMPANY_YEAR_STAT_FAMILY_SPAN) as usize] = 100.0f64.to_bits();

    let mut state = RuntimeState {
        companies: vec![RuntimeCompany {
            company_id: 1,
            controller_kind: RuntimeCompanyControllerKind::Unknown,
            current_cash: 100,
            debt: 0,
            credit_rating_score: None,
            prime_rate: None,
            track_piece_counts: RuntimeTrackPieceCounts::default(),
            active: true,
            available_track_laying_capacity: None,
            linked_chairman_profile_id: None,
            book_value_per_share: 0,
            investor_confidence: 0,
            management_attitude: 0,
            takeover_cooldown_year: None,
            merger_cooldown_year: None,
        }],
        event_runtime_records: vec![RuntimeEventRecord {
            record_id: 10,
            trigger_kind: 7,
            active: true,
            service_count: 0,
            marks_collection_dirty: false,
            one_shot: false,
            has_fired: false,
            conditions: Vec::new(),
            effects: vec![RuntimeEffect::SetCompanyCash {
                target: RuntimeCompanyTarget::Ids { ids: vec![1] },
                value: 250,
            }],
        }],
        service_state: RuntimeServiceState {
            company_market_state: BTreeMap::from([(
                1,
                crate::state::RuntimeCompanyMarketState {
                    year_stat_family_qword_bits,
                    special_stat_family_232a_qword_bits: vec![0u64; 0x20],
                    ..crate::state::RuntimeCompanyMarketState::default()
                },
            )]),
            ..RuntimeServiceState::default()
        },
        calendar: crate::CalendarPoint {
            year: 1830,
            month_slot: 0,
            phase_slot: 0,
            tick_slot: 0,
        },
        world_flags: BTreeMap::new(),
        save_profile: crate::state::RuntimeSaveProfileState::default(),
        world_restore: crate::state::RuntimeWorldRestoreState::default(),
        metadata: BTreeMap::new(),
        selected_company_id: None,
        players: Vec::new(),
        selected_player_id: None,
        chairman_profiles: Vec::new(),
        selected_chairman_profile_id: None,
        trains: Vec::new(),
        locomotive_catalog: Vec::new(),
        cargo_catalog: Vec::new(),
        territories: Vec::new(),
        company_territory_track_piece_counts: Vec::new(),
        company_territory_access: Vec::new(),
        packed_event_collection: None,
        candidate_availability: BTreeMap::new(),
        named_locomotive_availability: BTreeMap::new(),
        named_locomotive_cost: BTreeMap::new(),
        all_cargo_price_override: None,
        named_cargo_price_overrides: BTreeMap::new(),
        all_cargo_production_override: None,
        factory_cargo_production_override: None,
        farm_mine_cargo_production_override: None,
        named_cargo_production_overrides: BTreeMap::new(),
        cargo_production_overrides: BTreeMap::new(),
        world_runtime_variables: BTreeMap::new(),
        company_runtime_variables: BTreeMap::new(),
        player_runtime_variables: BTreeMap::new(),
        territory_runtime_variables: BTreeMap::new(),
        world_scalar_overrides: BTreeMap::new(),
        special_conditions: BTreeMap::new(),
    };

    execute_step_command(
        &mut state,
        &StepCommand::ServiceTriggerKind { trigger_kind: 7 },
    )
    .expect("cash effect should apply through owner state");

    assert_eq!(state.companies[0].current_cash, 250);
    assert_eq!(
        crate::derived::runtime_company_stat_value(
            &state,
            1,
            RuntimeCompanyStatSelector {
                family_id: RUNTIME_COMPANY_STAT_FAMILY_CONTROL_TRANSFER,
                slot_id: RUNTIME_COMPANY_STAT_SLOT_CURRENT_CASH,
            },
        ),
        Some(250)
    );
}

#[test]
fn set_company_governance_scalar_updates_owner_state_backed_metrics() {
    let mut state = RuntimeState {
        companies: vec![RuntimeCompany {
            company_id: 1,
            controller_kind: RuntimeCompanyControllerKind::Unknown,
            current_cash: 100,
            debt: 0,
            credit_rating_score: None,
            prime_rate: None,
            track_piece_counts: RuntimeTrackPieceCounts::default(),
            active: true,
            available_track_laying_capacity: None,
            linked_chairman_profile_id: None,
            book_value_per_share: 0,
            investor_confidence: 0,
            management_attitude: 0,
            takeover_cooldown_year: None,
            merger_cooldown_year: None,
        }],
        event_runtime_records: vec![RuntimeEventRecord {
            record_id: 11,
            trigger_kind: 7,
            active: true,
            service_count: 0,
            marks_collection_dirty: false,
            one_shot: false,
            has_fired: false,
            conditions: Vec::new(),
            effects: vec![
                RuntimeEffect::SetCompanyGovernanceScalar {
                    target: RuntimeCompanyTarget::Ids { ids: vec![1] },
                    metric: RuntimeCompanyMetric::PrimeRate,
                    value: 6,
                },
                RuntimeEffect::SetCompanyGovernanceScalar {
                    target: RuntimeCompanyTarget::Ids { ids: vec![1] },
                    metric: RuntimeCompanyMetric::BookValuePerShare,
                    value: 2620,
                },
                RuntimeEffect::SetCompanyGovernanceScalar {
                    target: RuntimeCompanyTarget::Ids { ids: vec![1] },
                    metric: RuntimeCompanyMetric::InvestorConfidence,
                    value: 37,
                },
                RuntimeEffect::SetCompanyGovernanceScalar {
                    target: RuntimeCompanyTarget::Ids { ids: vec![1] },
                    metric: RuntimeCompanyMetric::ManagementAttitude,
                    value: 58,
                },
            ],
        }],
        service_state: RuntimeServiceState {
            world_issue_opinion_base_terms_raw_i32: {
                let mut terms = vec![0; 0x3b];
                terms[RUNTIME_WORLD_ISSUE_PRIME_RATE as usize] = 0;
                terms
            },
            company_market_state: BTreeMap::from([(
                1,
                crate::state::RuntimeCompanyMarketState {
                    issue_opinion_terms_raw_i32: vec![0; 0x3b],
                    ..crate::state::RuntimeCompanyMarketState::default()
                },
            )]),
            ..RuntimeServiceState::default()
        },
        calendar: crate::CalendarPoint {
            year: 1830,
            month_slot: 0,
            phase_slot: 0,
            tick_slot: 0,
        },
        world_flags: BTreeMap::new(),
        save_profile: crate::state::RuntimeSaveProfileState::default(),
        world_restore: crate::state::RuntimeWorldRestoreState {
            issue_37_value: Some(5.0f32.to_bits()),
            ..crate::state::RuntimeWorldRestoreState::default()
        },
        metadata: BTreeMap::new(),
        selected_company_id: None,
        players: Vec::new(),
        selected_player_id: None,
        chairman_profiles: Vec::new(),
        selected_chairman_profile_id: None,
        trains: Vec::new(),
        locomotive_catalog: Vec::new(),
        cargo_catalog: Vec::new(),
        territories: Vec::new(),
        company_territory_track_piece_counts: Vec::new(),
        company_territory_access: Vec::new(),
        packed_event_collection: None,
        candidate_availability: BTreeMap::new(),
        named_locomotive_availability: BTreeMap::new(),
        named_locomotive_cost: BTreeMap::new(),
        all_cargo_price_override: None,
        named_cargo_price_overrides: BTreeMap::new(),
        all_cargo_production_override: None,
        factory_cargo_production_override: None,
        farm_mine_cargo_production_override: None,
        named_cargo_production_overrides: BTreeMap::new(),
        cargo_production_overrides: BTreeMap::new(),
        world_runtime_variables: BTreeMap::new(),
        company_runtime_variables: BTreeMap::new(),
        player_runtime_variables: BTreeMap::new(),
        territory_runtime_variables: BTreeMap::new(),
        world_scalar_overrides: BTreeMap::new(),
        special_conditions: BTreeMap::new(),
    };

    execute_step_command(
        &mut state,
        &StepCommand::ServiceTriggerKind { trigger_kind: 7 },
    )
    .expect("governance effect should apply through owner state");

    assert_eq!(state.companies[0].prime_rate, Some(6));
    assert_eq!(state.companies[0].book_value_per_share, 2620);
    assert_eq!(state.companies[0].investor_confidence, 37);
    assert_eq!(state.companies[0].management_attitude, 58);
    assert_eq!(
        crate::derived::runtime_company_prime_rate(&state, 1),
        Some(6)
    );
    assert_eq!(
        crate::derived::runtime_company_book_value_per_share(&state, 1),
        Some(2620)
    );
    assert_eq!(
        crate::derived::runtime_company_investor_confidence(&state, 1),
        Some(37)
    );
    assert_eq!(
        crate::derived::runtime_company_management_attitude(&state, 1),
        Some(58)
    );
    assert_eq!(
        state.service_state.company_market_state[&1]
            .direct_control_transfer_float_fields_raw_u32
            .get(&0x32f)
            .copied(),
        Some(2620.0f32.to_bits())
    );
    assert_eq!(
        state.service_state.company_market_state[&1].cached_share_price_raw_u32,
        37.0f32.to_bits()
    );
    assert_eq!(
        state.service_state.company_market_state[&1].issue_opinion_terms_raw_i32
            [RUNTIME_WORLD_ISSUE_PRIME_RATE as usize],
        100
    );
    assert_eq!(
        state.service_state.company_market_state[&1].issue_opinion_terms_raw_i32
            [RUNTIME_WORLD_ISSUE_MANAGEMENT_ATTITUDE as usize],
        58
    );
}

#[test]
fn set_company_credit_rating_governance_scalar_updates_issue38_owner_state() {
    let mut year_stat_family_qword_bits = vec![
        0u64;
        (RUNTIME_COMPANY_STAT_SLOT_COUNT * RUNTIME_COMPANY_YEAR_STAT_FAMILY_SPAN)
            as usize
    ];
    year_stat_family_qword_bits[(0x12 * RUNTIME_COMPANY_YEAR_STAT_FAMILY_SPAN) as usize] =
        20.0f64.to_bits();
    year_stat_family_qword_bits[(0x01 * RUNTIME_COMPANY_YEAR_STAT_FAMILY_SPAN + 1) as usize] =
        100.0f64.to_bits();
    year_stat_family_qword_bits[(0x09 * RUNTIME_COMPANY_YEAR_STAT_FAMILY_SPAN + 1) as usize] =
        0.0f64.to_bits();

    let mut state = RuntimeState {
        companies: vec![RuntimeCompany {
            company_id: 1,
            controller_kind: RuntimeCompanyControllerKind::Human,
            current_cash: 100,
            debt: 0,
            credit_rating_score: None,
            prime_rate: None,
            track_piece_counts: RuntimeTrackPieceCounts::default(),
            active: true,
            available_track_laying_capacity: None,
            linked_chairman_profile_id: None,
            book_value_per_share: 0,
            investor_confidence: 0,
            management_attitude: 0,
            takeover_cooldown_year: None,
            merger_cooldown_year: None,
        }],
        world_restore: RuntimeWorldRestoreState {
            issue_37_value: Some(5.0f32.to_bits()),
            issue_38_value: Some(2),
            packed_year_word_raw_u16: Some(1835),
            ..RuntimeWorldRestoreState::default()
        },
        service_state: RuntimeServiceState {
            world_issue_opinion_base_terms_raw_i32: vec![0; 0x3b],
            company_market_state: BTreeMap::from([(
                1,
                crate::state::RuntimeCompanyMarketState {
                    outstanding_shares: 10_000,
                    founding_year: 1830,
                    last_bankruptcy_year: 1800,
                    year_stat_family_qword_bits,
                    issue_opinion_terms_raw_i32: vec![0; 0x3b],
                    live_bond_slots: vec![crate::state::RuntimeCompanyBondSlot {
                        slot_index: 0,
                        principal: 100_000,
                        maturity_year: 0,
                        coupon_rate_raw_u32: 0.05f32.to_bits(),
                    }],
                    ..crate::state::RuntimeCompanyMarketState::default()
                },
            )]),
            ..RuntimeServiceState::default()
        },
        event_runtime_records: vec![RuntimeEventRecord {
            record_id: 13,
            trigger_kind: 7,
            active: true,
            service_count: 0,
            marks_collection_dirty: false,
            one_shot: false,
            has_fired: false,
            conditions: Vec::new(),
            effects: vec![RuntimeEffect::SetCompanyGovernanceScalar {
                target: RuntimeCompanyTarget::Ids { ids: vec![1] },
                metric: RuntimeCompanyMetric::CreditRating,
                value: 7,
            }],
        }],
        calendar: crate::CalendarPoint {
            year: 1835,
            month_slot: 0,
            phase_slot: 0,
            tick_slot: 0,
        },
        world_flags: BTreeMap::new(),
        save_profile: crate::state::RuntimeSaveProfileState::default(),
        metadata: BTreeMap::new(),
        selected_company_id: None,
        players: Vec::new(),
        selected_player_id: None,
        chairman_profiles: Vec::new(),
        selected_chairman_profile_id: None,
        trains: Vec::new(),
        locomotive_catalog: Vec::new(),
        cargo_catalog: Vec::new(),
        territories: Vec::new(),
        company_territory_track_piece_counts: Vec::new(),
        company_territory_access: Vec::new(),
        packed_event_collection: None,
        candidate_availability: BTreeMap::new(),
        named_locomotive_availability: BTreeMap::new(),
        named_locomotive_cost: BTreeMap::new(),
        all_cargo_price_override: None,
        named_cargo_price_overrides: BTreeMap::new(),
        all_cargo_production_override: None,
        factory_cargo_production_override: None,
        farm_mine_cargo_production_override: None,
        named_cargo_production_overrides: BTreeMap::new(),
        cargo_production_overrides: BTreeMap::new(),
        world_runtime_variables: BTreeMap::new(),
        company_runtime_variables: BTreeMap::new(),
        player_runtime_variables: BTreeMap::new(),
        territory_runtime_variables: BTreeMap::new(),
        world_scalar_overrides: BTreeMap::new(),
        special_conditions: BTreeMap::new(),
    };

    execute_step_command(
        &mut state,
        &StepCommand::ServiceTriggerKind { trigger_kind: 7 },
    )
    .expect("credit-rating governance effect should apply through owner state");

    assert_eq!(state.companies[0].credit_rating_score, Some(7));
    assert_eq!(
        crate::derived::runtime_company_credit_rating(&state, 1),
        Some(7)
    );
    assert_eq!(
        state.service_state.company_market_state[&1].issue_opinion_terms_raw_i32
            [RUNTIME_WORLD_ISSUE_CREDIT_MARKET as usize],
        -3
    );
}

#[test]
fn adjust_company_cash_updates_owner_state_backed_current_cash() {
    let mut year_stat_family_qword_bits = vec![
        0u64;
        ((RUNTIME_COMPANY_STAT_SLOT_COUNT + 2) * RUNTIME_COMPANY_YEAR_STAT_FAMILY_SPAN)
            as usize
    ];
    year_stat_family_qword_bits[(RUNTIME_COMPANY_STAT_SLOT_CURRENT_CASH
        * RUNTIME_COMPANY_YEAR_STAT_FAMILY_SPAN) as usize] = 100.0f64.to_bits();

    let mut state = RuntimeState {
        companies: vec![RuntimeCompany {
            company_id: 1,
            controller_kind: RuntimeCompanyControllerKind::Unknown,
            current_cash: 100,
            debt: 0,
            credit_rating_score: None,
            prime_rate: None,
            track_piece_counts: RuntimeTrackPieceCounts::default(),
            active: true,
            available_track_laying_capacity: None,
            linked_chairman_profile_id: None,
            book_value_per_share: 0,
            investor_confidence: 0,
            management_attitude: 0,
            takeover_cooldown_year: None,
            merger_cooldown_year: None,
        }],
        event_runtime_records: vec![RuntimeEventRecord {
            record_id: 12,
            trigger_kind: 7,
            active: true,
            service_count: 0,
            marks_collection_dirty: false,
            one_shot: false,
            has_fired: false,
            conditions: Vec::new(),
            effects: vec![RuntimeEffect::AdjustCompanyCash {
                target: RuntimeCompanyTarget::Ids { ids: vec![1] },
                delta: 25,
            }],
        }],
        service_state: RuntimeServiceState {
            company_market_state: BTreeMap::from([(
                1,
                crate::state::RuntimeCompanyMarketState {
                    year_stat_family_qword_bits,
                    special_stat_family_232a_qword_bits: vec![0u64; 0x20],
                    ..crate::state::RuntimeCompanyMarketState::default()
                },
            )]),
            ..RuntimeServiceState::default()
        },
        calendar: crate::CalendarPoint {
            year: 1830,
            month_slot: 0,
            phase_slot: 0,
            tick_slot: 0,
        },
        world_flags: BTreeMap::new(),
        save_profile: crate::state::RuntimeSaveProfileState::default(),
        world_restore: crate::state::RuntimeWorldRestoreState::default(),
        metadata: BTreeMap::new(),
        selected_company_id: None,
        players: Vec::new(),
        selected_player_id: None,
        chairman_profiles: Vec::new(),
        selected_chairman_profile_id: None,
        trains: Vec::new(),
        locomotive_catalog: Vec::new(),
        cargo_catalog: Vec::new(),
        territories: Vec::new(),
        company_territory_track_piece_counts: Vec::new(),
        company_territory_access: Vec::new(),
        packed_event_collection: None,
        candidate_availability: BTreeMap::new(),
        named_locomotive_availability: BTreeMap::new(),
        named_locomotive_cost: BTreeMap::new(),
        all_cargo_price_override: None,
        named_cargo_price_overrides: BTreeMap::new(),
        all_cargo_production_override: None,
        factory_cargo_production_override: None,
        farm_mine_cargo_production_override: None,
        named_cargo_production_overrides: BTreeMap::new(),
        cargo_production_overrides: BTreeMap::new(),
        world_runtime_variables: BTreeMap::new(),
        company_runtime_variables: BTreeMap::new(),
        player_runtime_variables: BTreeMap::new(),
        territory_runtime_variables: BTreeMap::new(),
        world_scalar_overrides: BTreeMap::new(),
        special_conditions: BTreeMap::new(),
    };

    execute_step_command(
        &mut state,
        &StepCommand::ServiceTriggerKind { trigger_kind: 7 },
    )
    .expect("cash adjustment should apply through owner state");

    assert_eq!(state.companies[0].current_cash, 125);
    assert_eq!(
        crate::derived::runtime_company_stat_value(
            &state,
            1,
            RuntimeCompanyStatSelector {
                family_id: RUNTIME_COMPANY_STAT_FAMILY_CONTROL_TRANSFER,
                slot_id: RUNTIME_COMPANY_STAT_SLOT_CURRENT_CASH,
            },
        ),
        Some(125)
    );
}

#[test]
fn applies_named_locomotive_availability_effects() {
    let mut state = RuntimeState {
        event_runtime_records: vec![RuntimeEventRecord {
            record_id: 10,
            trigger_kind: 7,
            active: true,
            service_count: 0,
            marks_collection_dirty: false,
            one_shot: false,
            has_fired: false,
            conditions: Vec::new(),
            effects: vec![
                RuntimeEffect::SetNamedLocomotiveAvailability {
                    name: "Big Boy".to_string(),
                    value: false,
                },
                RuntimeEffect::SetNamedLocomotiveAvailability {
                    name: "GP7".to_string(),
                    value: true,
                },
            ],
        }],
        ..state()
    };

    let result = execute_step_command(
        &mut state,
        &StepCommand::ServiceTriggerKind { trigger_kind: 7 },
    )
    .expect("named locomotive availability effects should succeed");

    assert_eq!(state.named_locomotive_availability.get("Big Boy"), Some(&0));
    assert_eq!(state.named_locomotive_availability.get("GP7"), Some(&1));
    assert_eq!(result.service_events[0].applied_effect_count, 2);
}

#[test]
fn applies_named_locomotive_cost_effects() {
    let mut state = RuntimeState {
        event_runtime_records: vec![RuntimeEventRecord {
            record_id: 11,
            trigger_kind: 7,
            active: true,
            service_count: 0,
            marks_collection_dirty: false,
            one_shot: false,
            has_fired: false,
            conditions: Vec::new(),
            effects: vec![
                RuntimeEffect::SetNamedLocomotiveCost {
                    name: "Big Boy".to_string(),
                    value: 250000,
                },
                RuntimeEffect::SetNamedLocomotiveCost {
                    name: "GP7".to_string(),
                    value: 175000,
                },
            ],
        }],
        ..state()
    };

    let result = execute_step_command(
        &mut state,
        &StepCommand::ServiceTriggerKind { trigger_kind: 7 },
    )
    .expect("named locomotive cost effects should succeed");

    assert_eq!(state.named_locomotive_cost.get("Big Boy"), Some(&250000));
    assert_eq!(state.named_locomotive_cost.get("GP7"), Some(&175000));
    assert_eq!(result.service_events[0].applied_effect_count, 2);
}

#[test]
fn applies_scalar_named_locomotive_availability_effects() {
    let mut state = RuntimeState {
        event_runtime_records: vec![RuntimeEventRecord {
            record_id: 12,
            trigger_kind: 7,
            active: true,
            service_count: 0,
            marks_collection_dirty: false,
            one_shot: false,
            has_fired: false,
            conditions: Vec::new(),
            effects: vec![
                RuntimeEffect::SetNamedLocomotiveAvailabilityValue {
                    name: "Big Boy".to_string(),
                    value: 42,
                },
                RuntimeEffect::SetNamedLocomotiveAvailabilityValue {
                    name: "GP7".to_string(),
                    value: 7,
                },
            ],
        }],
        ..state()
    };

    let result = execute_step_command(
        &mut state,
        &StepCommand::ServiceTriggerKind { trigger_kind: 7 },
    )
    .expect("scalar named locomotive availability effects should succeed");

    assert_eq!(
        state.named_locomotive_availability.get("Big Boy"),
        Some(&42)
    );
    assert_eq!(state.named_locomotive_availability.get("GP7"), Some(&7));
    assert_eq!(result.service_events[0].applied_effect_count, 2);
}

#[test]
fn applies_world_scalar_override_effects() {
    let mut state = RuntimeState {
        event_runtime_records: vec![RuntimeEventRecord {
            record_id: 13,
            trigger_kind: 7,
            active: true,
            service_count: 0,
            marks_collection_dirty: false,
            one_shot: false,
            has_fired: false,
            conditions: Vec::new(),
            effects: vec![
                RuntimeEffect::SetWorldScalarOverride {
                    key: "world.build_stations_cost".to_string(),
                    value: 350000,
                },
                RuntimeEffect::SetCargoPriceOverride {
                    target: RuntimeCargoPriceTarget::All,
                    value: 180,
                },
                RuntimeEffect::SetCargoPriceOverride {
                    target: RuntimeCargoPriceTarget::Named {
                        name: "Coal".to_string(),
                    },
                    value: 95,
                },
                RuntimeEffect::SetCargoProductionOverride {
                    target: RuntimeCargoProductionTarget::Factory,
                    value: 225,
                },
                RuntimeEffect::SetCargoProductionOverride {
                    target: RuntimeCargoProductionTarget::Named {
                        name: "Corn".to_string(),
                    },
                    value: 140,
                },
                RuntimeEffect::SetCargoProductionSlot {
                    slot: 1,
                    value: 125,
                },
                RuntimeEffect::SetTerritoryAccessCost { value: 750000 },
            ],
        }],
        ..state()
    };

    let result = execute_step_command(
        &mut state,
        &StepCommand::ServiceTriggerKind { trigger_kind: 7 },
    )
    .expect("world scalar override effects should succeed");

    assert_eq!(
        state
            .world_scalar_overrides
            .get("world.build_stations_cost"),
        Some(&350000)
    );
    assert_eq!(state.all_cargo_price_override, Some(180));
    assert_eq!(state.named_cargo_price_overrides.get("Coal"), Some(&95));
    assert_eq!(state.factory_cargo_production_override, Some(225));
    assert_eq!(
        state.named_cargo_production_overrides.get("Corn"),
        Some(&140)
    );
    assert_eq!(state.cargo_production_overrides.get(&1), Some(&125));
    assert_eq!(state.world_restore.territory_access_cost, Some(750000));
    assert_eq!(result.service_events[0].applied_effect_count, 7);
}

#[test]
fn applies_locomotive_policy_world_flags_through_owner_state() {
    let mut state = RuntimeState {
        event_runtime_records: vec![RuntimeEventRecord {
            record_id: 213,
            trigger_kind: 7,
            active: true,
            service_count: 0,
            marks_collection_dirty: false,
            one_shot: false,
            has_fired: false,
            conditions: Vec::new(),
            effects: vec![
                RuntimeEffect::SetWorldFlag {
                    key: "world.all_steam_locos_available".to_string(),
                    value: true,
                },
                RuntimeEffect::SetWorldFlag {
                    key: "world.all_diesel_locos_available".to_string(),
                    value: false,
                },
                RuntimeEffect::SetWorldFlag {
                    key: "world.all_electric_locos_available".to_string(),
                    value: true,
                },
            ],
        }],
        ..state()
    };

    let result = execute_step_command(
        &mut state,
        &StepCommand::ServiceTriggerKind { trigger_kind: 7 },
    )
    .expect("locomotive policy world flags should succeed");

    assert_eq!(
        state.world_flags.get("world.all_steam_locos_available"),
        Some(&true)
    );
    assert_eq!(
        state.world_restore.all_steam_locomotives_available_raw_u8,
        Some(1)
    );
    assert_eq!(
        state.world_restore.all_diesel_locomotives_available_raw_u8,
        Some(0)
    );
    assert_eq!(
        state
            .world_restore
            .all_electric_locomotives_available_raw_u8,
        Some(1)
    );
    assert_eq!(result.service_events[0].applied_effect_count, 3);
}

#[test]
fn applies_runtime_variable_effects() {
    let mut state = RuntimeState {
        companies: vec![
            RuntimeCompany {
                company_id: 1,
                controller_kind: RuntimeCompanyControllerKind::Human,
                current_cash: 10,
                debt: 0,
                credit_rating_score: None,
                prime_rate: None,
                track_piece_counts: RuntimeTrackPieceCounts::default(),
                active: true,
                available_track_laying_capacity: None,
                linked_chairman_profile_id: None,
                book_value_per_share: 0,
                investor_confidence: 0,
                management_attitude: 0,
                takeover_cooldown_year: None,
                merger_cooldown_year: None,
            },
            RuntimeCompany {
                company_id: 2,
                controller_kind: RuntimeCompanyControllerKind::Ai,
                current_cash: 20,
                debt: 0,
                credit_rating_score: None,
                prime_rate: None,
                track_piece_counts: RuntimeTrackPieceCounts::default(),
                active: true,
                available_track_laying_capacity: None,
                linked_chairman_profile_id: None,
                book_value_per_share: 0,
                investor_confidence: 0,
                management_attitude: 0,
                takeover_cooldown_year: None,
                merger_cooldown_year: None,
            },
        ],
        players: vec![RuntimePlayer {
            player_id: 9,
            current_cash: 0,
            active: true,
            controller_kind: RuntimeCompanyControllerKind::Human,
        }],
        territories: vec![RuntimeTerritory {
            territory_id: 7,
            name: Some("North".to_string()),
            track_piece_counts: RuntimeTrackPieceCounts::default(),
        }],
        event_runtime_records: vec![RuntimeEventRecord {
            record_id: 14,
            trigger_kind: 7,
            active: true,
            service_count: 0,
            marks_collection_dirty: false,
            one_shot: false,
            has_fired: false,
            conditions: Vec::new(),
            effects: vec![
                RuntimeEffect::SetWorldVariable {
                    index: 1,
                    value: -5,
                },
                RuntimeEffect::SetCompanyVariable {
                    target: RuntimeCompanyTarget::AllActive,
                    index: 2,
                    value: 17,
                },
                RuntimeEffect::SetPlayerVariable {
                    target: RuntimePlayerTarget::Ids { ids: vec![9] },
                    index: 3,
                    value: 99,
                },
                RuntimeEffect::SetTerritoryVariable {
                    target: RuntimeTerritoryTarget::Ids { ids: vec![7] },
                    index: 4,
                    value: 1234,
                },
            ],
        }],
        ..state()
    };

    let result = execute_step_command(
        &mut state,
        &StepCommand::ServiceTriggerKind { trigger_kind: 7 },
    )
    .expect("runtime variable effects should succeed");

    assert_eq!(state.world_runtime_variables.get(&1), Some(&-5));
    assert_eq!(
        state
            .company_runtime_variables
            .get(&1)
            .and_then(|vars| vars.get(&2)),
        Some(&17)
    );
    assert_eq!(
        state
            .company_runtime_variables
            .get(&2)
            .and_then(|vars| vars.get(&2)),
        Some(&17)
    );
    assert_eq!(
        state
            .player_runtime_variables
            .get(&9)
            .and_then(|vars| vars.get(&3)),
        Some(&99)
    );
    assert_eq!(
        state
            .territory_runtime_variables
            .get(&7)
            .and_then(|vars| vars.get(&4)),
        Some(&1234)
    );
    assert_eq!(result.service_events[0].applied_effect_count, 4);
}

#[test]
fn applies_economic_status_code_effect() {
    let mut state = RuntimeState {
        event_runtime_records: vec![RuntimeEventRecord {
            record_id: 90,
            trigger_kind: 6,
            active: true,
            service_count: 0,
            marks_collection_dirty: false,
            one_shot: false,
            has_fired: false,
            conditions: Vec::new(),
            effects: vec![RuntimeEffect::SetEconomicStatusCode { value: 3 }],
        }],
        ..state()
    };

    execute_step_command(
        &mut state,
        &StepCommand::ServiceTriggerKind { trigger_kind: 6 },
    )
    .expect("economic-status effect should succeed");

    assert_eq!(state.world_restore.economic_status_code, Some(3));
}

#[test]
fn applies_limited_track_building_amount_effect() {
    let mut state = RuntimeState {
        event_runtime_records: vec![RuntimeEventRecord {
            record_id: 91,
            trigger_kind: 6,
            active: true,
            service_count: 0,
            marks_collection_dirty: false,
            one_shot: false,
            has_fired: false,
            conditions: Vec::new(),
            effects: vec![RuntimeEffect::SetLimitedTrackBuildingAmount { value: 18 }],
        }],
        ..state()
    };

    execute_step_command(
        &mut state,
        &StepCommand::ServiceTriggerKind { trigger_kind: 6 },
    )
    .expect("limited-track-building-amount effect should succeed");

    assert_eq!(state.world_restore.limited_track_building_amount, Some(18));
}

#[test]
fn confiscate_company_assets_zeros_company_and_retires_owned_trains() {
    let mut year_stat_family_qword_bits = vec![
        0u64;
        ((RUNTIME_COMPANY_STAT_SLOT_COUNT + 2) * RUNTIME_COMPANY_YEAR_STAT_FAMILY_SPAN)
            as usize
    ];
    year_stat_family_qword_bits[(RUNTIME_COMPANY_STAT_SLOT_CURRENT_CASH
        * RUNTIME_COMPANY_YEAR_STAT_FAMILY_SPAN) as usize] = 50.0f64.to_bits();

    let mut state = RuntimeState {
        companies: vec![
            RuntimeCompany {
                company_id: 1,
                controller_kind: RuntimeCompanyControllerKind::Human,
                current_cash: 50,
                debt: 7,
                credit_rating_score: None,
                prime_rate: None,
                track_piece_counts: RuntimeTrackPieceCounts::default(),
                active: true,
                available_track_laying_capacity: None,
                linked_chairman_profile_id: None,
                book_value_per_share: 0,
                investor_confidence: 0,
                management_attitude: 0,
                takeover_cooldown_year: None,
                merger_cooldown_year: None,
            },
            RuntimeCompany {
                company_id: 2,
                controller_kind: RuntimeCompanyControllerKind::Ai,
                current_cash: 80,
                debt: 9,
                credit_rating_score: None,
                prime_rate: None,
                track_piece_counts: RuntimeTrackPieceCounts::default(),
                active: true,
                available_track_laying_capacity: None,
                linked_chairman_profile_id: None,
                book_value_per_share: 0,
                investor_confidence: 0,
                management_attitude: 0,
                takeover_cooldown_year: None,
                merger_cooldown_year: None,
            },
        ],
        selected_company_id: Some(1),
        trains: vec![
            RuntimeTrain {
                train_id: 10,
                owner_company_id: 1,
                territory_id: None,
                locomotive_name: Some("Mikado".to_string()),
                active: true,
                retired: false,
            },
            RuntimeTrain {
                train_id: 11,
                owner_company_id: 2,
                territory_id: None,
                locomotive_name: Some("Orca".to_string()),
                active: true,
                retired: false,
            },
        ],
        event_runtime_records: vec![RuntimeEventRecord {
            record_id: 91,
            trigger_kind: 6,
            active: true,
            service_count: 0,
            marks_collection_dirty: false,
            one_shot: false,
            has_fired: false,
            conditions: Vec::new(),
            effects: vec![RuntimeEffect::ConfiscateCompanyAssets {
                target: RuntimeCompanyTarget::SelectedCompany,
            }],
        }],
        service_state: RuntimeServiceState {
            company_market_state: BTreeMap::from([(
                1,
                crate::state::RuntimeCompanyMarketState {
                    year_stat_family_qword_bits,
                    special_stat_family_232a_qword_bits: vec![0u64; 0x20],
                    live_bond_slots: vec![crate::state::RuntimeCompanyBondSlot {
                        slot_index: 0,
                        principal: 20_000,
                        maturity_year: 1845,
                        coupon_rate_raw_u32: 0.05f32.to_bits(),
                    }],
                    bond_count: 1,
                    largest_live_bond_principal: Some(20_000),
                    highest_coupon_live_bond_principal: Some(20_000),
                    ..crate::state::RuntimeCompanyMarketState::default()
                },
            )]),
            ..crate::state::RuntimeServiceState::default()
        },
        ..state()
    };

    execute_step_command(
        &mut state,
        &StepCommand::ServiceTriggerKind { trigger_kind: 6 },
    )
    .expect("confiscation effect should succeed");

    assert_eq!(state.companies[0].current_cash, 0);
    assert_eq!(state.companies[0].debt, 0);
    assert!(!state.companies[0].active);
    assert_eq!(state.selected_company_id, None);
    assert!(state.trains[0].retired);
    assert!(!state.trains[1].retired);
    assert_eq!(
        crate::derived::runtime_company_stat_value(
            &state,
            1,
            RuntimeCompanyStatSelector {
                family_id: RUNTIME_COMPANY_STAT_FAMILY_CONTROL_TRANSFER,
                slot_id: RUNTIME_COMPANY_STAT_SLOT_CURRENT_CASH,
            },
        ),
        Some(0)
    );
    assert!(
        state.service_state.company_market_state[&1]
            .live_bond_slots
            .is_empty()
    );
    assert_eq!(state.service_state.company_market_state[&1].bond_count, 0);
    assert_eq!(
        state.service_state.company_market_state[&1].largest_live_bond_principal,
        None
    );
    assert_eq!(
        state.service_state.company_market_state[&1].highest_coupon_live_bond_principal,
        None
    );
}

#[test]
fn retire_trains_respects_company_territory_and_locomotive_filters() {
    let mut state = RuntimeState {
        territories: vec![
            RuntimeTerritory {
                territory_id: 7,
                name: Some("Appalachia".to_string()),
                track_piece_counts: RuntimeTrackPieceCounts::default(),
            },
            RuntimeTerritory {
                territory_id: 8,
                name: Some("Great Plains".to_string()),
                track_piece_counts: RuntimeTrackPieceCounts::default(),
            },
        ],
        trains: vec![
            RuntimeTrain {
                train_id: 10,
                owner_company_id: 1,
                territory_id: Some(7),
                locomotive_name: Some("Mikado".to_string()),
                active: true,
                retired: false,
            },
            RuntimeTrain {
                train_id: 11,
                owner_company_id: 1,
                territory_id: Some(7),
                locomotive_name: Some("Orca".to_string()),
                active: true,
                retired: false,
            },
            RuntimeTrain {
                train_id: 12,
                owner_company_id: 1,
                territory_id: Some(8),
                locomotive_name: Some("Mikado".to_string()),
                active: true,
                retired: false,
            },
        ],
        event_runtime_records: vec![RuntimeEventRecord {
            record_id: 92,
            trigger_kind: 6,
            active: true,
            service_count: 0,
            marks_collection_dirty: false,
            one_shot: false,
            has_fired: false,
            conditions: Vec::new(),
            effects: vec![RuntimeEffect::RetireTrains {
                company_target: Some(RuntimeCompanyTarget::SelectedCompany),
                territory_target: Some(RuntimeTerritoryTarget::Ids { ids: vec![7] }),
                locomotive_name: Some("Mikado".to_string()),
            }],
        }],
        selected_company_id: Some(1),
        ..state()
    };

    execute_step_command(
        &mut state,
        &StepCommand::ServiceTriggerKind { trigger_kind: 6 },
    )
    .expect("retire-trains effect should succeed");

    assert!(state.trains[0].retired);
    assert!(!state.trains[1].retired);
    assert!(!state.trains[2].retired);
}

#[test]
fn set_chairman_cash_supports_all_active_target() {
    let mut state = RuntimeState {
        companies: vec![
            RuntimeCompany {
                company_id: 1,
                current_cash: 0,
                debt: 0,
                credit_rating_score: None,
                prime_rate: None,
                active: true,
                available_track_laying_capacity: None,
                controller_kind: RuntimeCompanyControllerKind::Human,
                linked_chairman_profile_id: Some(1),
                book_value_per_share: 0,
                investor_confidence: 0,
                management_attitude: 0,
                takeover_cooldown_year: None,
                merger_cooldown_year: None,
                track_piece_counts: RuntimeTrackPieceCounts::default(),
            },
            RuntimeCompany {
                company_id: 2,
                current_cash: 0,
                debt: 0,
                credit_rating_score: None,
                prime_rate: None,
                active: true,
                available_track_laying_capacity: None,
                controller_kind: RuntimeCompanyControllerKind::Human,
                linked_chairman_profile_id: Some(2),
                book_value_per_share: 0,
                investor_confidence: 0,
                management_attitude: 0,
                takeover_cooldown_year: None,
                merger_cooldown_year: None,
                track_piece_counts: RuntimeTrackPieceCounts::default(),
            },
        ],
        chairman_profiles: vec![
            RuntimeChairmanProfile {
                profile_id: 1,
                name: "Chairman One".to_string(),
                active: true,
                current_cash: 10,
                linked_company_id: Some(1),
                company_holdings: BTreeMap::from([(1, 2)]),
                holdings_value_total: 20,
                net_worth_total: 30,
                purchasing_power_total: 70,
            },
            RuntimeChairmanProfile {
                profile_id: 2,
                name: "Chairman Two".to_string(),
                active: true,
                current_cash: 20,
                linked_company_id: Some(2),
                company_holdings: BTreeMap::from([(2, 3)]),
                holdings_value_total: 60,
                net_worth_total: 80,
                purchasing_power_total: 110,
            },
            RuntimeChairmanProfile {
                profile_id: 3,
                name: "Chairman Three".to_string(),
                active: false,
                current_cash: 30,
                linked_company_id: None,
                company_holdings: BTreeMap::new(),
                holdings_value_total: 0,
                net_worth_total: 30,
                purchasing_power_total: 30,
            },
        ],
        event_runtime_records: vec![RuntimeEventRecord {
            record_id: 93,
            trigger_kind: 6,
            active: true,
            service_count: 0,
            marks_collection_dirty: false,
            one_shot: false,
            has_fired: false,
            conditions: Vec::new(),
            effects: vec![RuntimeEffect::SetChairmanCash {
                target: RuntimeChairmanTarget::AllActive,
                value: 77,
            }],
        }],
        service_state: RuntimeServiceState {
            company_market_state: BTreeMap::from([
                (
                    1,
                    crate::state::RuntimeCompanyMarketState {
                        cached_share_price_raw_u32: 0x41200000,
                        ..crate::state::RuntimeCompanyMarketState::default()
                    },
                ),
                (
                    2,
                    crate::state::RuntimeCompanyMarketState {
                        cached_share_price_raw_u32: 0x41a00000,
                        ..crate::state::RuntimeCompanyMarketState::default()
                    },
                ),
            ]),
            ..RuntimeServiceState::default()
        },
        ..state()
    };

    execute_step_command(
        &mut state,
        &StepCommand::ServiceTriggerKind { trigger_kind: 6 },
    )
    .expect("all-active chairman cash effect should succeed");

    assert_eq!(state.chairman_profiles[0].current_cash, 77);
    assert_eq!(state.chairman_profiles[1].current_cash, 77);
    assert_eq!(state.chairman_profiles[2].current_cash, 30);
    assert_eq!(state.chairman_profiles[0].net_worth_total, 97);
    assert_eq!(state.chairman_profiles[0].purchasing_power_total, 137);
    assert_eq!(state.chairman_profiles[1].net_worth_total, 137);
    assert_eq!(state.chairman_profiles[1].purchasing_power_total, 167);
}

#[test]
fn deactivate_chairman_clears_selected_and_company_links_for_ids_target() {
    let mut state = RuntimeState {
        companies: vec![
            RuntimeCompany {
                company_id: 1,
                controller_kind: RuntimeCompanyControllerKind::Human,
                current_cash: 100,
                debt: 0,
                credit_rating_score: None,
                prime_rate: None,
                track_piece_counts: RuntimeTrackPieceCounts::default(),
                active: true,
                available_track_laying_capacity: None,
                linked_chairman_profile_id: Some(1),
                book_value_per_share: 0,
                investor_confidence: 0,
                management_attitude: 0,
                takeover_cooldown_year: None,
                merger_cooldown_year: None,
            },
            RuntimeCompany {
                company_id: 2,
                controller_kind: RuntimeCompanyControllerKind::Ai,
                current_cash: 80,
                debt: 0,
                credit_rating_score: None,
                prime_rate: None,
                track_piece_counts: RuntimeTrackPieceCounts::default(),
                active: true,
                available_track_laying_capacity: None,
                linked_chairman_profile_id: Some(2),
                book_value_per_share: 0,
                investor_confidence: 0,
                management_attitude: 0,
                takeover_cooldown_year: None,
                merger_cooldown_year: None,
            },
        ],
        chairman_profiles: vec![
            RuntimeChairmanProfile {
                profile_id: 1,
                name: "Chairman One".to_string(),
                active: true,
                current_cash: 10,
                linked_company_id: Some(1),
                company_holdings: BTreeMap::new(),
                holdings_value_total: 0,
                net_worth_total: 0,
                purchasing_power_total: 0,
            },
            RuntimeChairmanProfile {
                profile_id: 2,
                name: "Chairman Two".to_string(),
                active: true,
                current_cash: 20,
                linked_company_id: Some(2),
                company_holdings: BTreeMap::new(),
                holdings_value_total: 0,
                net_worth_total: 0,
                purchasing_power_total: 0,
            },
        ],
        selected_chairman_profile_id: Some(2),
        event_runtime_records: vec![RuntimeEventRecord {
            record_id: 94,
            trigger_kind: 6,
            active: true,
            service_count: 0,
            marks_collection_dirty: false,
            one_shot: false,
            has_fired: false,
            conditions: Vec::new(),
            effects: vec![RuntimeEffect::DeactivateChairman {
                target: RuntimeChairmanTarget::Ids { ids: vec![2] },
            }],
        }],
        ..state()
    };

    execute_step_command(
        &mut state,
        &StepCommand::ServiceTriggerKind { trigger_kind: 6 },
    )
    .expect("ids-target chairman deactivation should succeed");

    assert!(state.chairman_profiles[0].active);
    assert!(!state.chairman_profiles[1].active);
    assert_eq!(state.chairman_profiles[1].linked_company_id, None);
    assert_eq!(state.selected_chairman_profile_id, None);
    assert_eq!(state.companies[0].linked_chairman_profile_id, Some(1));
    assert_eq!(state.companies[1].linked_chairman_profile_id, None);
}
