use super::*;

pub(super) fn maybe_emit_finance_template_bundle() {
    if env::var_os("RRT_WRITE_FINANCE_TEMPLATE").is_none() {
        return;
    }
    if FINANCE_TEMPLATE_EMITTED.swap(true, Ordering::AcqRel) {
        return;
    }

    let base_dir = env::current_dir().unwrap_or_else(|_| PathBuf::from("."));
    let _ = write_finance_snapshot_bundle(&base_dir, "attach_template", &sample_finance_snapshot());
}

pub(super) fn maybe_start_finance_capture_thread() {
    if env::var_os("RRT_WRITE_FINANCE_CAPTURE").is_none() {
        return;
    }
    if FINANCE_CAPTURE_STARTED.swap(true, Ordering::AcqRel) {
        return;
    }

    append_log_message(FINANCE_CAPTURE_STARTED_MESSAGE);
    let base_dir = env::current_dir().unwrap_or_else(|_| PathBuf::from("."));
    let _ = thread::Builder::new()
        .name("rrt-finance-capture".to_string())
        .spawn(move || {
            for _ in 0..MAX_CAPTURE_POLL_ATTEMPTS {
                if !FINANCE_COLLECTION_PROBE_WRITTEN.load(Ordering::Acquire) {
                    if let Some(probe) = unsafe { capture_company_collection_probe() } {
                        if write_indexed_collection_probe(&base_dir, "attach_probe", &probe).is_ok()
                        {
                            FINANCE_COLLECTION_PROBE_WRITTEN.store(true, Ordering::Release);
                            append_log_message(FINANCE_CAPTURE_PROBE_DUMP_WRITTEN_MESSAGE);
                        }
                    }
                }
                if let Some(snapshot) = unsafe { try_capture_probe_snapshot() } {
                    append_log_message(FINANCE_CAPTURE_COMPANY_RESOLVED_MESSAGE);
                    if write_finance_snapshot_only(&base_dir, "attach_probe", &snapshot).is_ok() {
                        append_log_message(FINANCE_CAPTURE_PROBE_WRITTEN_MESSAGE);
                        return;
                    }
                }
                thread::sleep(CAPTURE_POLL_INTERVAL);
            }

            append_log_message(FINANCE_CAPTURE_TIMEOUT_MESSAGE);
        });
}

pub(super) fn maybe_start_cargo_capture_thread() {
    if env::var_os("RRT_WRITE_CARGO_CAPTURE").is_none() {
        return;
    }
    if CARGO_CAPTURE_STARTED.swap(true, Ordering::AcqRel) {
        return;
    }

    append_log_message(CARGO_CAPTURE_STARTED_MESSAGE);
    let base_dir = env::current_dir().unwrap_or_else(|_| PathBuf::from("."));
    let _ = thread::Builder::new()
        .name("rrt-cargo-capture".to_string())
        .spawn(move || {
            let mut last_probe: Option<CargoCollectionProbe> = None;
            for _ in 0..MAX_CAPTURE_POLL_ATTEMPTS {
                if let Some(probe) = unsafe { capture_cargo_collection_probe() } {
                    last_probe = Some(probe.clone());
                    if probe.live_entry_count > 0
                        && write_cargo_collection_probe(&base_dir, "attach_probe", &probe).is_ok()
                    {
                        append_log_message(CARGO_CAPTURE_WRITTEN_MESSAGE);
                        return;
                    }
                }
                thread::sleep(CAPTURE_POLL_INTERVAL);
            }

            if let Some(probe) = last_probe {
                let _ = write_cargo_collection_probe(&base_dir, "attach_probe_timeout", &probe);
            }
            append_log_message(CARGO_CAPTURE_TIMEOUT_MESSAGE);
        });
}

pub(super) unsafe fn try_capture_probe_snapshot() -> Option<FinanceSnapshot> {
    append_log_message(FINANCE_CAPTURE_SCAN_MESSAGE);
    let company = unsafe { resolve_first_active_company()? };
    Some(unsafe { capture_probe_snapshot_from_company(company) })
}

pub(super) unsafe fn resolve_first_active_company() -> Option<*mut u8> {
    let collection = COMPANY_COLLECTION_ADDR as *const u8;
    let id_bound = unsafe { read_i32(collection.add(INDEXED_COLLECTION_ID_BOUND_OFFSET)) };
    if id_bound <= 0 {
        return None;
    }

    for entry_id in 1..=id_bound as usize {
        if unsafe { indexed_collection_entry_id_is_live(collection, entry_id) } {
            let company =
                unsafe { indexed_collection_resolve_live_entry_by_id(collection, entry_id) };
            if !company.is_null() && unsafe { read_u8(company.add(COMPANY_ACTIVE_OFFSET)) != 0 } {
                return Some(company);
            }
        }
    }

    None
}

pub(super) unsafe fn capture_company_collection_probe() -> Option<IndexedCollectionProbe> {
    let collection = COMPANY_COLLECTION_ADDR as *const u8;
    let id_bound = unsafe { read_i32(collection.add(INDEXED_COLLECTION_ID_BOUND_OFFSET)) };
    if id_bound <= 0 {
        return Some(IndexedCollectionProbe {
            collection_addr: COMPANY_COLLECTION_ADDR,
            flat_payload: unsafe {
                read_u32(collection.add(INDEXED_COLLECTION_FLAT_FLAG_OFFSET)) != 0
            },
            stride: unsafe { read_u32(collection.add(INDEXED_COLLECTION_STRIDE_OFFSET)) },
            id_bound,
            payload_ptr: unsafe {
                read_ptr(collection.add(INDEXED_COLLECTION_PAYLOAD_OFFSET)) as usize
            },
            tombstone_ptr: unsafe {
                read_ptr(collection.add(INDEXED_COLLECTION_TOMBSTONE_BITSET_OFFSET)) as usize
            },
            first_rows: Vec::new(),
        });
    }

    let mut first_rows = Vec::new();
    let sample_bound = (id_bound as usize).min(8);
    for entry_id in 1..=sample_bound {
        let live = unsafe { indexed_collection_entry_id_is_live(collection, entry_id) };
        let resolved_ptr =
            unsafe { indexed_collection_resolve_live_entry_by_id(collection, entry_id) as usize };
        let active_flag = if resolved_ptr == 0 {
            None
        } else {
            Some(unsafe { read_u8((resolved_ptr as *const u8).add(COMPANY_ACTIVE_OFFSET)) })
        };
        first_rows.push(IndexedCollectionProbeRow {
            entry_id,
            live,
            resolved_ptr,
            active_flag,
        });
    }

    Some(IndexedCollectionProbe {
        collection_addr: COMPANY_COLLECTION_ADDR,
        flat_payload: unsafe { read_u32(collection.add(INDEXED_COLLECTION_FLAT_FLAG_OFFSET)) != 0 },
        stride: unsafe { read_u32(collection.add(INDEXED_COLLECTION_STRIDE_OFFSET)) },
        id_bound,
        payload_ptr: unsafe {
            read_ptr(collection.add(INDEXED_COLLECTION_PAYLOAD_OFFSET)) as usize
        },
        tombstone_ptr: unsafe {
            read_ptr(collection.add(INDEXED_COLLECTION_TOMBSTONE_BITSET_OFFSET)) as usize
        },
        first_rows,
    })
}

pub(super) unsafe fn capture_cargo_collection_probe() -> Option<CargoCollectionProbe> {
    let collection = CARGO_COLLECTION_ADDR as *const u8;
    let id_bound = unsafe { read_i32(collection.add(INDEXED_COLLECTION_ID_BOUND_OFFSET)) };
    if id_bound <= 0 {
        return Some(CargoCollectionProbe {
            collection_addr: CARGO_COLLECTION_ADDR,
            flat_payload: unsafe {
                read_u32(collection.add(INDEXED_COLLECTION_FLAT_FLAG_OFFSET)) != 0
            },
            stride: unsafe { read_u32(collection.add(INDEXED_COLLECTION_STRIDE_OFFSET)) },
            id_bound,
            payload_ptr: unsafe {
                read_ptr(collection.add(INDEXED_COLLECTION_PAYLOAD_OFFSET)) as usize
            },
            tombstone_ptr: unsafe {
                read_ptr(collection.add(INDEXED_COLLECTION_TOMBSTONE_BITSET_OFFSET)) as usize
            },
            live_entry_count: 0,
            rows: Vec::new(),
        });
    }

    let mut live_entry_count = 0_usize;
    let mut rows = Vec::with_capacity(id_bound as usize);
    for entry_id in 1..=id_bound as usize {
        let live = unsafe { indexed_collection_entry_id_is_live(collection, entry_id) };
        let resolved_ptr =
            unsafe { indexed_collection_resolve_live_entry_by_id(collection, entry_id) as usize };
        if live && resolved_ptr != 0 {
            live_entry_count += 1;
        }
        let stem = if resolved_ptr == 0 {
            None
        } else {
            Some(unsafe { read_c_string((resolved_ptr as *const u8).add(CARGO_STEM_OFFSET), 0x1e) })
        };
        let route_style_byte = if resolved_ptr == 0 {
            None
        } else {
            Some(unsafe { read_u8((resolved_ptr as *const u8).add(CARGO_ROUTE_STYLE_OFFSET)) })
        };
        let subtype_byte = if resolved_ptr == 0 {
            None
        } else {
            Some(unsafe { read_u8((resolved_ptr as *const u8).add(CARGO_SUBTYPE_OFFSET)) })
        };
        let class_marker = if live {
            Some(unsafe {
                read_u32(collection.add(CARGO_COLLECTION_CLASS_MARKER_BASE_OFFSET + entry_id * 4))
            })
        } else {
            None
        };
        rows.push(CargoCollectionProbeRow {
            entry_id,
            live,
            resolved_ptr,
            stem,
            route_style_byte,
            subtype_byte,
            class_marker,
        });
    }

    Some(CargoCollectionProbe {
        collection_addr: CARGO_COLLECTION_ADDR,
        flat_payload: unsafe { read_u32(collection.add(INDEXED_COLLECTION_FLAT_FLAG_OFFSET)) != 0 },
        stride: unsafe { read_u32(collection.add(INDEXED_COLLECTION_STRIDE_OFFSET)) },
        id_bound,
        payload_ptr: unsafe {
            read_ptr(collection.add(INDEXED_COLLECTION_PAYLOAD_OFFSET)) as usize
        },
        tombstone_ptr: unsafe {
            read_ptr(collection.add(INDEXED_COLLECTION_TOMBSTONE_BITSET_OFFSET)) as usize
        },
        live_entry_count,
        rows,
    })
}

pub(super) unsafe fn capture_probe_snapshot_from_company(company: *mut u8) -> FinanceSnapshot {
    let scenario = unsafe { read_ptr(ACTIVE_MODE_PTR_ADDR as *const u8) } as *const u8;
    let current_year = unsafe { read_u16(scenario.add(SCENARIO_CURRENT_YEAR_OFFSET)) };
    let founding_year = unsafe { read_u16(company.add(COMPANY_FOUNDING_YEAR_OFFSET)) };
    let last_bankruptcy_year =
        unsafe { read_u16(company.add(COMPANY_LAST_BANKRUPTCY_YEAR_OFFSET)) };
    let outstanding_share_count =
        unsafe { read_u32(company.add(COMPANY_OUTSTANDING_SHARES_OFFSET)) };
    let bonds = unsafe { capture_bonds(company, current_year) };
    let company_value = unsafe { read_u32(company.add(COMPANY_COMPANY_VALUE_OFFSET)) as i64 };
    let growth_setting = unsafe {
        growth_setting_from_raw(read_u8(
            scenario.add(SCENARIO_BUILDING_DENSITY_GROWTH_OFFSET),
        ))
    };

    FinanceSnapshot {
        policy: AnnualFinancePolicy {
            annual_mode: 0x0c,
            bankruptcy_allowed: unsafe {
                read_u8(scenario.add(SCENARIO_BANKRUPTCY_TOGGLE_OFFSET)) == 0
            },
            bond_issuance_allowed: unsafe {
                read_u8(scenario.add(SCENARIO_BOND_TOGGLE_OFFSET)) == 0
            },
            stock_actions_allowed: unsafe {
                read_u8(scenario.add(SCENARIO_STOCK_TOGGLE_OFFSET)) == 0
            },
            dividends_allowed: unsafe {
                read_u8(scenario.add(SCENARIO_DIVIDEND_TOGGLE_OFFSET)) == 0
            },
            growth_setting,
            ..AnnualFinancePolicy::default()
        },
        company: CompanyFinanceState {
            active: unsafe { read_u8(company.add(COMPANY_ACTIVE_OFFSET)) != 0 },
            years_since_founding: year_delta(current_year, founding_year),
            years_since_last_bankruptcy: year_delta(current_year, last_bankruptcy_year),
            current_company_value: company_value,
            outstanding_share_count,
            city_connection_bonus_latch: unsafe {
                read_u8(company.add(COMPANY_CITY_CONNECTION_LATCH_OFFSET)) != 0
            },
            linked_transit_service_latch: unsafe {
                read_u8(company.add(COMPANY_LINKED_TRANSIT_LATCH_OFFSET)) != 0
            },
            chairman_buyback_factor: None,
            bonds,
            ..CompanyFinanceState::default()
        },
    }
}

pub(super) unsafe fn capture_bonds(company: *mut u8, current_year: u16) -> Vec<BondPosition> {
    let bond_count = unsafe { read_u8(company.add(COMPANY_BOND_COUNT_OFFSET)) as usize };
    let table = unsafe { company.add(COMPANY_BOND_TABLE_OFFSET) };
    let mut bonds = Vec::with_capacity(bond_count);

    for index in 0..bond_count {
        let slot = unsafe { table.add(index * 12) };
        let principal = unsafe { read_i32(slot) } as i64;
        let maturity_year = unsafe { read_u32(slot.add(4)) };
        let coupon_rate = unsafe { read_f32(slot.add(8)) } as f64;

        bonds.push(BondPosition {
            principal,
            coupon_rate,
            years_remaining: maturity_year
                .saturating_sub(current_year as u32)
                .min(u8::MAX as u32) as u8,
        });
    }

    bonds
}

pub(super) fn growth_setting_from_raw(raw: u8) -> GrowthSetting {
    match raw {
        1 => GrowthSetting::ExpansionBias,
        2 => GrowthSetting::DividendSuppressed,
        _ => GrowthSetting::Neutral,
    }
}

pub(super) fn year_delta(current_year: u16, past_year: u16) -> u8 {
    current_year.saturating_sub(past_year).min(u8::MAX as u16) as u8
}
