use super::*;

#[test]
fn services_periodic_trigger_order_and_dirty_rerun() {
    let mut state = RuntimeState {
        event_runtime_records: vec![
            RuntimeEventRecord {
                record_id: 1,
                trigger_kind: 1,
                active: true,
                service_count: 0,
                marks_collection_dirty: true,
                one_shot: false,
                has_fired: false,
                conditions: Vec::new(),
                effects: vec![RuntimeEffect::SetWorldFlag {
                    key: "runtime.effect_fired".to_string(),
                    value: true,
                }],
            },
            RuntimeEventRecord {
                record_id: 2,
                trigger_kind: 4,
                active: true,
                service_count: 0,
                marks_collection_dirty: false,
                one_shot: false,
                has_fired: false,
                conditions: Vec::new(),
                effects: vec![RuntimeEffect::AdjustCompanyCash {
                    target: RuntimeCompanyTarget::AllActive,
                    delta: 5,
                }],
            },
            RuntimeEventRecord {
                record_id: 3,
                trigger_kind: 0x0a,
                active: true,
                service_count: 0,
                marks_collection_dirty: false,
                one_shot: false,
                has_fired: false,
                conditions: Vec::new(),
                effects: vec![RuntimeEffect::SetSpecialCondition {
                    label: "Dirty rerun fired".to_string(),
                    value: 1,
                }],
            },
        ],
        ..state()
    };

    let result = execute_step_command(&mut state, &StepCommand::ServicePeriodicBoundary)
        .expect("periodic boundary service should succeed");

    assert_eq!(result.steps_executed, 0);
    assert_eq!(state.service_state.periodic_boundary_calls, 1);
    assert_eq!(state.service_state.total_event_record_services, 3);
    assert_eq!(state.service_state.dirty_rerun_count, 1);
    assert_eq!(state.event_runtime_records[0].service_count, 1);
    assert_eq!(state.event_runtime_records[1].service_count, 1);
    assert_eq!(state.event_runtime_records[2].service_count, 1);
    assert_eq!(state.world_flags.get("runtime.effect_fired"), Some(&true));
    assert_eq!(state.companies[0].current_cash, 15);
    assert_eq!(state.special_conditions.get("Dirty rerun fired"), Some(&1));
    assert_eq!(
        state.service_state.trigger_dispatch_counts.get(&1),
        Some(&1)
    );
    assert_eq!(
        state.service_state.trigger_dispatch_counts.get(&4),
        Some(&1)
    );
    assert_eq!(
        state.service_state.trigger_dispatch_counts.get(&0x0a),
        Some(&1)
    );
    assert_eq!(result.service_events.len(), 9);
    assert_eq!(result.service_events[0].applied_effect_count, 1);
    assert_eq!(
        result
            .service_events
            .iter()
            .find(|event| event.trigger_kind == Some(4))
            .expect("trigger kind 4 event should be present")
            .applied_effect_count,
        1
    );
    assert_eq!(
        result
            .service_events
            .iter()
            .find(|event| event.trigger_kind == Some(0x0a))
            .expect("trigger kind 0x0a event should be present")
            .applied_effect_count,
        1
    );
    assert_eq!(
        result
            .service_events
            .iter()
            .find(|event| event.trigger_kind == Some(4))
            .expect("trigger kind 4 event should be present")
            .mutated_company_ids,
        vec![1]
    );
    assert!(
        result
            .service_events
            .iter()
            .any(|event| event.kind == "near_city_acquisition")
    );
    assert!(
        result
            .service_events
            .iter()
            .any(|event| event.kind == "annual_finance_policy")
    );
}

#[test]
fn periodic_boundary_applies_near_city_acquisition_and_emits_news() {
    let mut state = state();
    state.service_state.company_market_state = BTreeMap::from([(
        1,
        crate::state::RuntimeCompanyMarketState {
            city_connection_latch: true,
            linked_transit_latch: false,
            linked_transit_route_anchor_entry_id: Some(77),
            ..crate::state::RuntimeCompanyMarketState::default()
        },
    )]);
    state.service_state.near_city_acquisition_regions =
        vec![crate::state::RuntimeNearCityAcquisitionRegion {
            region_id: 0,
            name: "Marker09".to_string(),
            profile_names: vec!["Farm Corn".to_string()],
            fixed_row_shape_family_signature: Some("family-a".to_string()),
            fixed_row_best_density_lane_relative_offset_hex: Some("0x24".to_string()),
        }];
    state.service_state.near_city_acquisition_sites =
        vec![crate::state::RuntimeNearCityAcquisitionSite {
            site_id: 0,
            primary_name: "FarmCorn".to_string(),
            secondary_name: "FarmSet".to_string(),
            region_id: Some(0),
            owner_company_id: None,
            preferred_company_id: Some(1),
            owner_company_id_provenance:
                crate::state::RuntimeNearCityAcquisitionValueProvenance::BestEffortGuess,
            self_id: 0,
            self_id_provenance: crate::state::RuntimeNearCityAcquisitionValueProvenance::Grounded,
            candidate_subtype_label: "farm".to_string(),
            candidate_subtype_provenance:
                crate::state::RuntimeNearCityAcquisitionValueProvenance::Grounded,
            cached_tri_lane_0: 0xff0000ff,
            cached_tri_lane_1: 1,
            cached_tri_lane_2: 0x24,
            cached_tri_lane_provenance:
                crate::state::RuntimeNearCityAcquisitionValueProvenance::Grounded,
            nontransport_source_label: "best_effort_company_market_latch_projection".to_string(),
            tri_lane_source_label: "matched_side_buffer_name_pair_plus_region_fixed_row_candidate"
                .to_string(),
        }];

    let result = execute_step_command(&mut state, &StepCommand::ServicePeriodicBoundary)
        .expect("periodic boundary should apply near-city acquisition");

    assert_eq!(state.service_state.near_city_acquisition_service_calls, 1);
    assert_eq!(
        state.service_state.near_city_acquisition_sites[0].owner_company_id,
        Some(1)
    );
    assert_eq!(
        state
            .service_state
            .near_city_acquisition_last_news_events
            .len(),
        1
    );
    assert_eq!(
        state.service_state.near_city_acquisition_last_news_events[0].selector_label,
        "near_city_farm_acquisition"
    );
    assert!(result.service_events.iter().any(|event| {
        event.kind == "near_city_acquisition"
            && event.applied_effect_count == 1
            && event.mutated_company_ids == vec![1]
            && event.near_city_acquisition_news_family_candidates.get(&0)
                == Some(&"near_city_farm_acquisition".to_string())
            && event.near_city_acquisition_news_events.len() == 1
    }));
}

#[test]
fn periodic_boundary_clears_transient_preferred_locomotive_side_latch() {
    let mut state = state();
    state.service_state.company_periodic_side_latch_state = BTreeMap::from([(
        1,
        RuntimeCompanyPeriodicSideLatchState {
            preferred_locomotive_engine_type_raw_u8: Some(2),
            city_connection_latch: true,
            linked_transit_latch: false,
        },
    )]);

    execute_step_command(&mut state, &StepCommand::ServicePeriodicBoundary)
        .expect("periodic boundary should refresh periodic side latches");

    assert_eq!(
        state
            .service_state
            .company_periodic_side_latch_state
            .get(&1),
        Some(&RuntimeCompanyPeriodicSideLatchState {
            preferred_locomotive_engine_type_raw_u8: None,
            city_connection_latch: true,
            linked_transit_latch: false,
        })
    );
}

#[test]
fn periodic_boundary_reseeds_finance_side_latches_from_market_state() {
    let mut state = state();
    state.service_state.company_market_state = BTreeMap::from([(
        1,
        crate::state::RuntimeCompanyMarketState {
            city_connection_latch: false,
            linked_transit_latch: true,
            ..crate::state::RuntimeCompanyMarketState::default()
        },
    )]);
    state.service_state.company_periodic_side_latch_state = BTreeMap::from([(
        1,
        RuntimeCompanyPeriodicSideLatchState {
            preferred_locomotive_engine_type_raw_u8: Some(2),
            city_connection_latch: true,
            linked_transit_latch: false,
        },
    )]);

    execute_step_command(&mut state, &StepCommand::ServicePeriodicBoundary)
        .expect("periodic boundary should reseed finance latches from market state");

    assert_eq!(
        state
            .service_state
            .company_periodic_side_latch_state
            .get(&1),
        Some(&RuntimeCompanyPeriodicSideLatchState {
            preferred_locomotive_engine_type_raw_u8: None,
            city_connection_latch: false,
            linked_transit_latch: true,
        })
    );
}
