use crate::derived::{
    runtime_company_book_value_per_share, runtime_company_credit_rating,
    runtime_company_investor_confidence, runtime_company_management_attitude,
    runtime_company_prime_rate,
};
use crate::event::conditions::RuntimeConditionComparator;
use crate::event::metrics::{
    RuntimeChairmanMetric, RuntimeCompanyMetric, RuntimeTerritoryMetric, RuntimeTrackMetric,
};
use crate::event::targets::RuntimeCargoClass;
use crate::state::{
    RuntimeChairmanProfile, RuntimeCompany, RuntimeCompanyTerritoryAccess, RuntimeState,
    RuntimeTrackPieceCounts, RuntimeTrain,
};

pub(super) fn company_metric_value(
    state: &RuntimeState,
    company: &RuntimeCompany,
    metric: RuntimeCompanyMetric,
) -> i64 {
    match metric {
        RuntimeCompanyMetric::CurrentCash => company.current_cash,
        RuntimeCompanyMetric::TotalDebt => company.debt as i64,
        RuntimeCompanyMetric::CreditRating => {
            runtime_company_credit_rating(state, company.company_id).unwrap_or(0)
        }
        RuntimeCompanyMetric::PrimeRate => {
            runtime_company_prime_rate(state, company.company_id).unwrap_or(0)
        }
        RuntimeCompanyMetric::BookValuePerShare => {
            runtime_company_book_value_per_share(state, company.company_id).unwrap_or(0)
        }
        RuntimeCompanyMetric::InvestorConfidence => {
            runtime_company_investor_confidence(state, company.company_id).unwrap_or(0)
        }
        RuntimeCompanyMetric::ManagementAttitude => {
            runtime_company_management_attitude(state, company.company_id).unwrap_or(0)
        }
        RuntimeCompanyMetric::TrackPiecesTotal => i64::from(company.track_piece_counts.total),
        RuntimeCompanyMetric::TrackPiecesSingle => i64::from(company.track_piece_counts.single),
        RuntimeCompanyMetric::TrackPiecesDouble => i64::from(company.track_piece_counts.double),
        RuntimeCompanyMetric::TrackPiecesTransition => {
            i64::from(company.track_piece_counts.transition)
        }
        RuntimeCompanyMetric::TrackPiecesElectric => i64::from(company.track_piece_counts.electric),
        RuntimeCompanyMetric::TrackPiecesNonElectric => {
            i64::from(company.track_piece_counts.non_electric)
        }
    }
}

pub(super) fn chairman_metric_value(
    profile: &RuntimeChairmanProfile,
    metric: RuntimeChairmanMetric,
) -> i64 {
    match metric {
        RuntimeChairmanMetric::CurrentCash => profile.current_cash,
        RuntimeChairmanMetric::HoldingsValueTotal => profile.holdings_value_total,
        RuntimeChairmanMetric::NetWorthTotal => profile.net_worth_total,
        RuntimeChairmanMetric::PurchasingPowerTotal => profile.purchasing_power_total,
    }
}

pub(super) fn territory_metric_value(
    state: &RuntimeState,
    territory_ids: &[u32],
    metric: RuntimeTerritoryMetric,
) -> i64 {
    state
        .territories
        .iter()
        .filter(|territory| territory_ids.contains(&territory.territory_id))
        .map(|territory| {
            track_piece_metric_value(
                territory.track_piece_counts,
                territory_metric_to_track_metric(metric),
            )
        })
        .sum()
}

pub(super) fn company_territory_metric_value(
    state: &RuntimeState,
    company_id: u32,
    territory_ids: &[u32],
    metric: RuntimeTrackMetric,
) -> i64 {
    state
        .company_territory_track_piece_counts
        .iter()
        .filter(|entry| {
            entry.company_id == company_id && territory_ids.contains(&entry.territory_id)
        })
        .map(|entry| track_piece_metric_value(entry.track_piece_counts, metric))
        .sum()
}

pub(super) fn track_piece_metric_value(
    counts: RuntimeTrackPieceCounts,
    metric: RuntimeTrackMetric,
) -> i64 {
    match metric {
        RuntimeTrackMetric::Total => i64::from(counts.total),
        RuntimeTrackMetric::Single => i64::from(counts.single),
        RuntimeTrackMetric::Double => i64::from(counts.double),
        RuntimeTrackMetric::Transition => i64::from(counts.transition),
        RuntimeTrackMetric::Electric => i64::from(counts.electric),
        RuntimeTrackMetric::NonElectric => i64::from(counts.non_electric),
    }
}

pub(super) fn territory_metric_to_track_metric(
    metric: RuntimeTerritoryMetric,
) -> RuntimeTrackMetric {
    match metric {
        RuntimeTerritoryMetric::TrackPiecesTotal => RuntimeTrackMetric::Total,
        RuntimeTerritoryMetric::TrackPiecesSingle => RuntimeTrackMetric::Single,
        RuntimeTerritoryMetric::TrackPiecesDouble => RuntimeTrackMetric::Double,
        RuntimeTerritoryMetric::TrackPiecesTransition => RuntimeTrackMetric::Transition,
        RuntimeTerritoryMetric::TrackPiecesElectric => RuntimeTrackMetric::Electric,
        RuntimeTerritoryMetric::TrackPiecesNonElectric => RuntimeTrackMetric::NonElectric,
    }
}

pub(super) fn compare_condition_value(
    actual: i64,
    comparator: RuntimeConditionComparator,
    expected: i64,
) -> bool {
    match comparator {
        RuntimeConditionComparator::Ge => actual >= expected,
        RuntimeConditionComparator::Le => actual <= expected,
        RuntimeConditionComparator::Gt => actual > expected,
        RuntimeConditionComparator::Lt => actual < expected,
        RuntimeConditionComparator::Eq => actual == expected,
        RuntimeConditionComparator::Ne => actual != expected,
    }
}

pub(super) fn cargo_production_total_for_class(
    state: &RuntimeState,
    cargo_class: RuntimeCargoClass,
) -> i64 {
    state
        .cargo_catalog
        .iter()
        .filter(|entry| entry.cargo_class == cargo_class)
        .filter_map(|entry| state.cargo_production_overrides.get(&entry.slot_id))
        .copied()
        .map(i64::from)
        .sum()
}

pub(super) fn apply_u64_delta(current: u64, delta: i64, company_id: u32) -> Result<u64, String> {
    if delta >= 0 {
        current
            .checked_add(delta as u64)
            .ok_or_else(|| format!("company_id {company_id} debt adjustment overflow"))
    } else {
        current
            .checked_sub(delta.unsigned_abs())
            .ok_or_else(|| format!("company_id {company_id} debt adjustment underflow"))
    }
}

pub(super) fn retire_matching_trains(
    trains: &mut [RuntimeTrain],
    company_ids: Option<&Vec<u32>>,
    territory_ids: Option<&Vec<u32>>,
    locomotive_name: Option<&str>,
) {
    for train in trains.iter_mut() {
        if !train.active || train.retired {
            continue;
        }
        if company_ids.is_some_and(|company_ids| !company_ids.contains(&train.owner_company_id)) {
            continue;
        }
        if territory_ids.is_some_and(|territory_ids| {
            !train
                .territory_id
                .is_some_and(|territory_id| territory_ids.contains(&territory_id))
        }) {
            continue;
        }
        if locomotive_name.is_some_and(|name| train.locomotive_name.as_deref() != Some(name)) {
            continue;
        }
        train.active = false;
        train.retired = true;
    }
}

pub(super) fn set_company_territory_access_pairs(
    access_entries: &mut Vec<RuntimeCompanyTerritoryAccess>,
    company_ids: &[u32],
    territory_ids: &[u32],
    value: bool,
) {
    if value {
        for company_id in company_ids {
            for territory_id in territory_ids {
                if !access_entries.iter().any(|entry| {
                    entry.company_id == *company_id && entry.territory_id == *territory_id
                }) {
                    access_entries.push(RuntimeCompanyTerritoryAccess {
                        company_id: *company_id,
                        territory_id: *territory_id,
                    });
                }
            }
        }
    } else {
        access_entries.retain(|entry| {
            !(company_ids.contains(&entry.company_id)
                && territory_ids.contains(&entry.territory_id))
        });
    }
}
