use super::*;

#[test]
fn overlay_replaces_base_company_and_chairman_context_from_save_slice() {
    let base_state = RuntimeState {
        companies: vec![crate::state::RuntimeCompany {
            company_id: 42,
            current_cash: 5,
            debt: 1,
            credit_rating_score: None,
            prime_rate: None,
            active: true,
            available_track_laying_capacity: Some(1),
            controller_kind: RuntimeCompanyControllerKind::Unknown,
            linked_chairman_profile_id: None,
            book_value_per_share: 10,
            investor_confidence: 0,
            management_attitude: 0,
            takeover_cooldown_year: None,
            merger_cooldown_year: None,
            track_piece_counts: RuntimeTrackPieceCounts::default(),
        }],
        selected_company_id: Some(42),
        chairman_profiles: vec![crate::state::RuntimeChairmanProfile {
            profile_id: 9,
            name: "Base Chairman".to_string(),
            active: true,
            current_cash: 10,
            linked_company_id: None,
            company_holdings: BTreeMap::new(),
            holdings_value_total: 0,
            net_worth_total: 0,
            purchasing_power_total: 0,
        }],
        selected_chairman_profile_id: Some(9),
        territories: vec![crate::state::RuntimeTerritory {
            territory_id: 7,
            name: Some("Base Territory".to_string()),
            track_piece_counts: RuntimeTrackPieceCounts::default(),
        }],
        ..state()
    };
    let save_slice = SmpLoadedSaveSlice {
        file_extension_hint: Some("gms".to_string()),
        container_profile_family: Some("rt3-classic-save-container-v1".to_string()),
        mechanism_family: "classic-save-rehydrate-v1".to_string(),
        mechanism_confidence: "grounded".to_string(),
        trailer_family: None,
        bridge_family: None,
        profile: None,
        candidate_availability_table: None,
        named_locomotive_availability_table: None,
        locomotive_catalog: None,
        cargo_catalog: None,
        world_issue_37_state: None,
        world_economic_tuning_state: None,
        world_finance_neighborhood_state: None,
        world_locomotive_policy_state: None,
        company_roster: Some(save_company_roster()),
        chairman_profile_table: Some(save_chairman_profile_table()),
        region_collection: None,
        region_fixed_row_run_summary: None,
        placed_structure_collection: None,
        placed_structure_dynamic_side_buffer_summary: None,
        special_conditions_table: None,
        event_runtime_collection: None,
        notes: vec![],
    };

    let input = build_runtime_state_input_from_overlay(
        &base_state,
        &save_slice,
        "overlay-save-native-company-chairman",
        None,
    )
    .expect("overlay input should project");

    assert_eq!(input.state.companies.len(), 2);
    assert_eq!(input.state.selected_company_id, Some(1));
    assert_eq!(input.state.chairman_profiles.len(), 2);
    assert_eq!(input.state.selected_chairman_profile_id, Some(1));
    assert_eq!(input.state.territories, base_state.territories);
    assert_eq!(
        input
            .state
            .service_state
            .company_market_state
            .get(&2)
            .map(|state| state.linked_transit_latch),
        Some(true)
    );
}

#[test]
fn overlay_replaces_base_near_city_runtime_slice_from_save_collections() {
    let mut base_state = state();
    base_state.service_state.near_city_acquisition_regions =
        vec![crate::state::RuntimeNearCityAcquisitionRegion {
            region_id: 9,
            name: "Base Region".to_string(),
            profile_names: vec!["Base".to_string()],
            fixed_row_shape_family_signature: None,
            fixed_row_best_density_lane_relative_offset_hex: None,
        }];
    base_state.service_state.near_city_acquisition_sites =
        vec![crate::state::RuntimeNearCityAcquisitionSite {
            site_id: 9,
            primary_name: "BaseSite".to_string(),
            secondary_name: "BaseSet".to_string(),
            region_id: Some(9),
            owner_company_id: None,
            preferred_company_id: None,
            owner_company_id_provenance:
                crate::state::RuntimeNearCityAcquisitionValueProvenance::Grounded,
            self_id: 9,
            self_id_provenance: crate::state::RuntimeNearCityAcquisitionValueProvenance::Grounded,
            candidate_subtype_label: "industry".to_string(),
            candidate_subtype_provenance:
                crate::state::RuntimeNearCityAcquisitionValueProvenance::Grounded,
            cached_tri_lane_0: 1,
            cached_tri_lane_1: 2,
            cached_tri_lane_2: 3,
            cached_tri_lane_provenance:
                crate::state::RuntimeNearCityAcquisitionValueProvenance::Grounded,
            nontransport_source_label: "base".to_string(),
            tri_lane_source_label: "base".to_string(),
        }];

    let save_slice = SmpLoadedSaveSlice {
        file_extension_hint: Some("gms".to_string()),
        container_profile_family: Some("rt3-105-save-container-v1".to_string()),
        mechanism_family: "rt3-105-save-post-span-bridge-v1".to_string(),
        mechanism_confidence: "mixed".to_string(),
        trailer_family: Some("rt3-105-save-trailer-v1".to_string()),
        bridge_family: Some("rt3-105-save-post-span-bridge-v1".to_string()),
        profile: None,
        candidate_availability_table: None,
        named_locomotive_availability_table: None,
        locomotive_catalog: None,
        cargo_catalog: None,
        world_issue_37_state: None,
        world_economic_tuning_state: None,
        world_finance_neighborhood_state: None,
        world_locomotive_policy_state: None,
        company_roster: Some(save_company_roster()),
        chairman_profile_table: Some(save_chairman_profile_table()),
        region_collection: Some(save_region_collection()),
        region_fixed_row_run_summary: Some(save_region_fixed_row_run_summary()),
        placed_structure_collection: Some(save_placed_structure_collection()),
        placed_structure_dynamic_side_buffer_summary: Some(
            save_placed_structure_dynamic_side_buffer_summary(),
        ),
        special_conditions_table: None,
        event_runtime_collection: None,
        notes: vec![],
    };

    let input =
        build_runtime_state_input_from_overlay(&base_state, &save_slice, "overlay-near-city", None)
            .expect("overlay input should project near-city slice");

    assert_eq!(
        input
            .state
            .service_state
            .near_city_acquisition_regions
            .len(),
        2
    );
    assert_eq!(
        input.state.service_state.near_city_acquisition_sites.len(),
        2
    );
    assert_eq!(
        input.state.service_state.near_city_acquisition_sites[0].owner_company_id,
        Some(1)
    );
    assert_eq!(
        input.state.service_state.near_city_acquisition_sites[0].tri_lane_source_label,
        "matched_side_buffer_name_pair_plus_region_fixed_row_candidate"
    );
}

#[test]
fn overlay_applies_selection_only_company_and_chairman_context_from_save_slice() {
    let base_state = RuntimeState {
        companies: vec![
            crate::state::RuntimeCompany {
                company_id: 1,
                current_cash: 100,
                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(),
            },
            crate::state::RuntimeCompany {
                company_id: 42,
                current_cash: 200,
                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(9),
                book_value_per_share: 0,
                investor_confidence: 0,
                management_attitude: 0,
                takeover_cooldown_year: None,
                merger_cooldown_year: None,
                track_piece_counts: RuntimeTrackPieceCounts::default(),
            },
        ],
        selected_company_id: Some(42),
        chairman_profiles: vec![
            crate::state::RuntimeChairmanProfile {
                profile_id: 1,
                name: "Selected".to_string(),
                active: true,
                current_cash: 0,
                linked_company_id: Some(1),
                company_holdings: BTreeMap::new(),
                holdings_value_total: 0,
                net_worth_total: 0,
                purchasing_power_total: 0,
            },
            crate::state::RuntimeChairmanProfile {
                profile_id: 9,
                name: "Base".to_string(),
                active: true,
                current_cash: 0,
                linked_company_id: Some(42),
                company_holdings: BTreeMap::new(),
                holdings_value_total: 0,
                net_worth_total: 0,
                purchasing_power_total: 0,
            },
        ],
        selected_chairman_profile_id: Some(9),
        service_state: RuntimeServiceState {
            company_market_state: BTreeMap::from([(
                42,
                crate::state::RuntimeCompanyMarketState {
                    outstanding_shares: 30_000,
                    bond_count: 3,
                    live_bond_slots: Vec::new(),
                    largest_live_bond_principal: Some(750_000),
                    highest_coupon_live_bond_principal: Some(500_000),
                    mutable_support_scalar_raw_u32: 0x3f19999a,
                    young_company_support_scalar_raw_u32: 0x42580000,
                    support_progress_word: 8,
                    recent_per_share_cache_absolute_counter: 0,
                    recent_per_share_cached_value_bits: 0,
                    recent_per_share_subscore_raw_u32: 0x42000000,
                    cached_share_price_raw_u32: 0x42180000,
                    chairman_salary_baseline: 21,
                    chairman_salary_current: 24,
                    chairman_bonus_year: 1836,
                    chairman_bonus_amount: 600,
                    founding_year: 1834,
                    last_bankruptcy_year: 0,
                    last_dividend_year: 1838,
                    current_issue_calendar_word: 4,
                    current_issue_calendar_word_2: 5,
                    prior_issue_calendar_word: 3,
                    prior_issue_calendar_word_2: 4,
                    city_connection_latch: false,
                    linked_transit_latch: true,
                    linked_transit_route_anchor_entry_id: Some(91),
                    linked_transit_route_anchor_fallback_counts: vec![2, 4, 6],
                    stat_band_root_0cfb_candidates: Vec::new(),
                    stat_band_root_0d7f_candidates: Vec::new(),
                    stat_band_root_1c47_candidates: Vec::new(),
                    year_stat_family_qword_bits: Vec::new(),
                    special_stat_family_232a_qword_bits: Vec::new(),
                    issue_opinion_terms_raw_i32: Vec::new(),
                    direct_control_transfer_float_fields_raw_u32: BTreeMap::new(),
                    direct_control_transfer_int_fields_raw_u32: BTreeMap::new(),
                },
            )]),
            world_issue_opinion_base_terms_raw_i32: Vec::new(),
            chairman_issue_opinion_terms_raw_i32: BTreeMap::new(),
            ..RuntimeServiceState::default()
        },
        ..state()
    };
    let save_slice = SmpLoadedSaveSlice {
        file_extension_hint: Some("gms".to_string()),
        container_profile_family: Some("rt3-105-save-container-v1".to_string()),
        mechanism_family: "rt3-105-save-post-span-bridge-v1".to_string(),
        mechanism_confidence: "mixed".to_string(),
        trailer_family: Some("rt3-105-save-trailer-v1".to_string()),
        bridge_family: Some("rt3-105-save-post-span-bridge-v1".to_string()),
        profile: None,
        candidate_availability_table: None,
        named_locomotive_availability_table: None,
        locomotive_catalog: None,
        cargo_catalog: None,
        world_issue_37_state: None,
        world_economic_tuning_state: None,
        world_finance_neighborhood_state: None,
        world_locomotive_policy_state: None,
        company_roster: Some(SmpLoadedCompanyRoster {
            source_kind: "save-direct-world-block-company-selection-only".to_string(),
            semantic_family: "scenario-selected-company-context".to_string(),
            observed_entry_count: 0,
            selected_company_id: Some(1),
            entries: Vec::new(),
        }),
        chairman_profile_table: Some(SmpLoadedChairmanProfileTable {
            source_kind: "save-direct-world-block-chairman-selection-only".to_string(),
            semantic_family: "scenario-selected-chairman-context".to_string(),
            observed_entry_count: 0,
            selected_chairman_profile_id: Some(1),
            entries: Vec::new(),
        }),
        region_collection: None,
        region_fixed_row_run_summary: None,
        placed_structure_collection: None,
        placed_structure_dynamic_side_buffer_summary: None,
        special_conditions_table: None,
        event_runtime_collection: None,
        notes: vec![],
    };

    let input = build_runtime_state_input_from_overlay(
        &base_state,
        &save_slice,
        "overlay-save-selection-only-context",
        None,
    )
    .expect("overlay input should project");

    let mut expected_companies = base_state.companies.clone();
    expected_companies[1].investor_confidence = 38;
    assert_eq!(input.state.companies, expected_companies);
    assert_eq!(input.state.selected_company_id, Some(1));
    assert_eq!(input.state.chairman_profiles, base_state.chairman_profiles);
    assert_eq!(input.state.selected_chairman_profile_id, Some(1));
    assert_eq!(
        input.state.service_state.company_market_state,
        base_state.service_state.company_market_state
    );
}

#[test]
fn overlays_save_slice_events_onto_base_company_context() {
    let base_state = RuntimeState {
        calendar: CalendarPoint {
            year: 1845,
            month_slot: 2,
            phase_slot: 1,
            tick_slot: 3,
        },
        world_flags: BTreeMap::from([("base.only".to_string(), true)]),
        save_profile: RuntimeSaveProfileState::default(),
        world_restore: RuntimeWorldRestoreState::default(),
        metadata: BTreeMap::from([("base.note".to_string(), "kept".to_string())]),
        companies: vec![crate::state::RuntimeCompany {
            company_id: 42,
            controller_kind: RuntimeCompanyControllerKind::Human,
            current_cash: 500,
            debt: 20,
            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(42),
        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,
        event_runtime_records: vec![RuntimeEventRecord {
            record_id: 1,
            trigger_kind: 1,
            active: true,
            service_count: 0,
            marks_collection_dirty: false,
            one_shot: false,
            has_fired: false,
            conditions: Vec::new(),
            effects: vec![],
        }],
        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(),
        service_state: RuntimeServiceState {
            periodic_boundary_calls: 9,
            annual_finance_service_calls: 0,
            periodic_route_preference_override_apply_count: 0,
            periodic_route_preference_override_restore_count: 0,
            trigger_dispatch_counts: BTreeMap::new(),
            total_event_record_services: 4,
            dirty_rerun_count: 2,
            world_issue_opinion_base_terms_raw_i32: Vec::new(),
            company_market_state: BTreeMap::new(),
            company_periodic_side_latch_state: BTreeMap::new(),
            active_periodic_route_preference_override: None,
            last_periodic_route_preference_override: None,
            annual_finance_last_actions: BTreeMap::new(),
            annual_finance_action_counts: BTreeMap::new(),
            annual_dividend_adjustment_commit_count: 0,
            annual_bond_last_retired_principal_total: 0,
            annual_bond_last_issued_principal_total: 0,
            annual_stock_repurchase_last_share_count: 0,
            annual_stock_issue_last_share_count: 0,
            annual_finance_last_news_family_candidates: BTreeMap::new(),
            annual_finance_last_news_events: Vec::new(),
            ..RuntimeServiceState::default()
        },
    };
    let save_slice = SmpLoadedSaveSlice {
        file_extension_hint: Some("gms".to_string()),
        container_profile_family: Some("rt3-classic-save-container-v1".to_string()),
        mechanism_family: "classic-save-rehydrate-v1".to_string(),
        mechanism_confidence: "grounded".to_string(),
        trailer_family: None,
        bridge_family: None,
        profile: None,
        candidate_availability_table: None,
        named_locomotive_availability_table: None,
        locomotive_catalog: None,
        cargo_catalog: None,
        world_issue_37_state: None,
        world_economic_tuning_state: None,
        world_finance_neighborhood_state: None,
        world_locomotive_policy_state: None,
        company_roster: None,
        chairman_profile_table: None,
        region_collection: None,
        region_fixed_row_run_summary: None,
        placed_structure_collection: None,
        placed_structure_dynamic_side_buffer_summary: None,
        special_conditions_table: None,
        event_runtime_collection: Some(SmpLoadedEventRuntimeCollectionSummary {
            source_kind: "packed-event-runtime-collection".to_string(),
            mechanism_family: "classic-save-rehydrate-v1".to_string(),
            mechanism_confidence: "grounded".to_string(),
            container_profile_family: Some("rt3-classic-save-container-v1".to_string()),
            metadata_tag_offset: 0x7100,
            records_tag_offset: 0x7200,
            close_tag_offset: 0x7600,
            packed_state_version: 0x3e9,
            packed_state_version_hex: "0x000003e9".to_string(),
            live_id_bound: 42,
            live_record_count: 1,
            live_entry_ids: vec![7],
            decoded_record_count: 1,
            imported_runtime_record_count: 0,
            records_with_trigger_kind: 0,
            records_missing_trigger_kind: 0,
            nondirect_compact_record_count: 0,
            nondirect_compact_records_missing_trigger_kind: 0,
            trigger_kinds_present: vec![],
            control_lane_notes: vec![],
            add_building_dispatch_strip_record_indexes: vec![],
            add_building_dispatch_strip_descriptor_labels: vec![],
            add_building_dispatch_strip_records_with_trigger_kind: 0,
            add_building_dispatch_strip_records_missing_trigger_kind: 0,
            add_building_dispatch_strip_row_shape_families: vec![],
            add_building_dispatch_strip_signature_families: vec![],
            add_building_dispatch_strip_condition_tuple_families: vec![],
            add_building_dispatch_strip_signature_condition_clusters: vec![],
            records: vec![SmpLoadedPackedEventRecordSummary {
                record_index: 0,
                live_entry_id: 7,
                payload_offset: Some(0x7202),
                payload_len: Some(48),
                decode_status: "parity_only".to_string(),
                payload_family: "synthetic_harness".to_string(),
                trigger_kind: Some(7),
                active: Some(true),
                marks_collection_dirty: Some(false),
                one_shot: Some(false),
                compact_control: None,
                text_bands: packed_text_bands(),
                standalone_condition_row_count: 0,
                standalone_condition_rows: vec![],
                negative_sentinel_scope: None,
                grouped_effect_row_counts: vec![0, 0, 0, 0],
                grouped_effect_rows: vec![],
                decoded_conditions: Vec::new(),
                decoded_actions: vec![RuntimeEffect::AdjustCompanyCash {
                    target: crate::event::targets::RuntimeCompanyTarget::Ids { ids: vec![42] },
                    delta: 50,
                }],
                executable_import_ready: false,
                notes: vec!["needs company context".to_string()],
            }],
        }),
        notes: vec![],
    };

    let mut input = build_runtime_state_input_from_overlay(
        &base_state,
        &save_slice,
        "overlay-smoke",
        Some("overlay test".to_string()),
    )
    .expect("overlay input should project");

    assert_eq!(input.state.calendar, base_state.calendar);
    assert_eq!(input.state.companies, base_state.companies);
    assert_eq!(input.state.service_state, base_state.service_state);
    assert_eq!(input.state.event_runtime_records.len(), 1);
    assert_eq!(
        input
            .state
            .packed_event_collection
            .as_ref()
            .map(|summary| summary.imported_runtime_record_count),
        Some(1)
    );
    assert_eq!(
        input
            .state
            .packed_event_collection
            .as_ref()
            .and_then(|summary| summary.records[0].import_outcome.as_deref()),
        Some("imported")
    );
    assert_eq!(
        input
            .state
            .metadata
            .get("save_slice.import_projection")
            .map(String::as_str),
        Some("overlay-runtime-restore-v1")
    );
    assert_eq!(
        input.state.metadata.get("base.note").map(String::as_str),
        Some("kept")
    );
    assert_eq!(input.state.world_flags.get("base.only"), Some(&true));

    execute_step_command(
        &mut input.state,
        &StepCommand::ServiceTriggerKind { trigger_kind: 7 },
    )
    .expect("overlay-imported company-targeted record should run");

    assert_eq!(input.state.companies[0].current_cash, 550);
}
