use std::collections::BTreeSet;

use crate::engine::conditions::ResolvedConditionContext;
use crate::engine::metrics::{
    apply_u64_delta, retire_matching_trains, set_company_territory_access_pairs,
};
use crate::engine::service::{
    service_clear_company_live_bonds, service_post_company_stat_delta,
    service_set_company_cached_share_price, service_set_company_credit_rating_target,
    service_set_company_direct_float_field, service_set_company_issue_opinion_total,
    service_set_company_prime_rate_target, service_zero_company_current_cash,
};
use crate::engine::targets::{resolve_company_target_ids, resolve_territory_target_ids};
use crate::event::metrics::{RUNTIME_COMPANY_STAT_SLOT_CURRENT_CASH, RuntimeCompanyMetric};
use crate::event::targets::{RuntimeCompanyTarget, RuntimeTerritoryTarget};
use crate::state::RuntimeState;

pub(super) fn apply_set_company_cash(
    state: &mut RuntimeState,
    target: &RuntimeCompanyTarget,
    value: i64,
    condition_context: &ResolvedConditionContext,
    mutated_company_ids: &mut BTreeSet<u32>,
) -> Result<(), String> {
    let company_ids = resolve_company_target_ids(state, target, condition_context)?;
    for company_id in company_ids {
        let prior_cash = state
            .companies
            .iter()
            .find(|company| company.company_id == company_id)
            .map(|company| company.current_cash)
            .ok_or_else(|| format!("missing company_id {company_id} while applying cash effect"))?;
        if !service_post_company_stat_delta(
            state,
            company_id,
            RUNTIME_COMPANY_STAT_SLOT_CURRENT_CASH,
            value.saturating_sub(prior_cash) as f64,
            false,
        ) {
            let company = state
                .companies
                .iter_mut()
                .find(|company| company.company_id == company_id)
                .ok_or_else(|| {
                    format!("missing company_id {company_id} while applying cash effect")
                })?;
            company.current_cash = value;
        }
        mutated_company_ids.insert(company_id);
    }
    Ok(())
}

pub(super) fn apply_set_company_governance_scalar(
    state: &mut RuntimeState,
    target: &RuntimeCompanyTarget,
    metric: RuntimeCompanyMetric,
    value: i64,
    condition_context: &ResolvedConditionContext,
    mutated_company_ids: &mut BTreeSet<u32>,
) -> Result<(), String> {
    let company_ids = resolve_company_target_ids(state, target, condition_context)?;
    for company_id in company_ids {
        let applied_through_owner_state = match metric {
            RuntimeCompanyMetric::CreditRating => {
                service_set_company_credit_rating_target(state, company_id, value)
            }
            RuntimeCompanyMetric::PrimeRate => {
                service_set_company_prime_rate_target(state, company_id, value)
            }
            RuntimeCompanyMetric::BookValuePerShare => {
                service_set_company_direct_float_field(state, company_id, 0x32f, value as f64)
            }
            RuntimeCompanyMetric::InvestorConfidence => {
                service_set_company_cached_share_price(state, company_id, value as f64)
            }
            RuntimeCompanyMetric::ManagementAttitude => service_set_company_issue_opinion_total(
                state,
                company_id,
                crate::event::metrics::RUNTIME_WORLD_ISSUE_MANAGEMENT_ATTITUDE,
                value,
            ),
            _ => {
                return Err(format!(
                    "unsupported governance metric {:?} in company governance effect",
                    metric
                ));
            }
        };
        let company = state
            .companies
            .iter_mut()
            .find(|company| company.company_id == company_id)
            .ok_or_else(|| {
                format!("missing company_id {company_id} while applying governance effect")
            })?;
        match metric {
            RuntimeCompanyMetric::CreditRating => {
                if !applied_through_owner_state {
                    company.credit_rating_score = Some(value);
                }
            }
            RuntimeCompanyMetric::PrimeRate => {
                if !applied_through_owner_state {
                    company.prime_rate = Some(value);
                }
            }
            RuntimeCompanyMetric::BookValuePerShare => {
                if !applied_through_owner_state {
                    company.book_value_per_share = value;
                }
            }
            RuntimeCompanyMetric::InvestorConfidence => {
                if !applied_through_owner_state {
                    company.investor_confidence = value;
                }
            }
            RuntimeCompanyMetric::ManagementAttitude => {
                if !applied_through_owner_state {
                    company.management_attitude = value;
                }
            }
            _ => unreachable!(),
        };
        mutated_company_ids.insert(company_id);
    }
    Ok(())
}

pub(super) fn apply_set_company_territory_access(
    state: &mut RuntimeState,
    target: &RuntimeCompanyTarget,
    territory: &RuntimeTerritoryTarget,
    value: bool,
    condition_context: &ResolvedConditionContext,
    mutated_company_ids: &mut BTreeSet<u32>,
) -> Result<(), String> {
    let company_ids = resolve_company_target_ids(state, target, condition_context)?;
    let territory_ids = resolve_territory_target_ids(state, territory)?;
    set_company_territory_access_pairs(
        &mut state.company_territory_access,
        &company_ids,
        &territory_ids,
        value,
    );
    mutated_company_ids.extend(company_ids);
    Ok(())
}

pub(super) fn apply_confiscate_company_assets(
    state: &mut RuntimeState,
    target: &RuntimeCompanyTarget,
    condition_context: &ResolvedConditionContext,
    mutated_company_ids: &mut BTreeSet<u32>,
) -> Result<(), String> {
    let company_ids = resolve_company_target_ids(state, target, condition_context)?;
    for company_id in company_ids.iter().copied() {
        let _ = service_zero_company_current_cash(state, company_id);
        let _ = service_clear_company_live_bonds(state, company_id);
        let company = state
            .companies
            .iter_mut()
            .find(|company| company.company_id == company_id)
            .ok_or_else(|| {
                format!("missing company_id {company_id} while applying confiscate effect")
            })?;
        company.current_cash = 0;
        company.debt = 0;
        company.active = false;
        mutated_company_ids.insert(company_id);
        if state.selected_company_id == Some(company_id) {
            state.selected_company_id = None;
        }
    }
    retire_matching_trains(&mut state.trains, Some(&company_ids), None, None);
    Ok(())
}

pub(super) fn apply_deactivate_company(
    state: &mut RuntimeState,
    target: &RuntimeCompanyTarget,
    condition_context: &ResolvedConditionContext,
    mutated_company_ids: &mut BTreeSet<u32>,
) -> Result<(), String> {
    let company_ids = resolve_company_target_ids(state, target, condition_context)?;
    for company_id in company_ids {
        let company = state
            .companies
            .iter_mut()
            .find(|company| company.company_id == company_id)
            .ok_or_else(|| {
                format!("missing company_id {company_id} while applying deactivate effect")
            })?;
        company.active = false;
        mutated_company_ids.insert(company_id);
        if state.selected_company_id == Some(company_id) {
            state.selected_company_id = None;
        }
    }
    Ok(())
}

pub(super) fn apply_set_company_track_laying_capacity(
    state: &mut RuntimeState,
    target: &RuntimeCompanyTarget,
    value: Option<u32>,
    condition_context: &ResolvedConditionContext,
    mutated_company_ids: &mut BTreeSet<u32>,
) -> Result<(), String> {
    let company_ids = resolve_company_target_ids(state, target, condition_context)?;
    for company_id in company_ids {
        let company = state
            .companies
            .iter_mut()
            .find(|company| company.company_id == company_id)
            .ok_or_else(|| {
                format!("missing company_id {company_id} while applying track capacity effect")
            })?;
        company.available_track_laying_capacity = value;
        mutated_company_ids.insert(company_id);
    }
    Ok(())
}

pub(super) fn apply_adjust_company_cash(
    state: &mut RuntimeState,
    target: &RuntimeCompanyTarget,
    delta: i64,
    condition_context: &ResolvedConditionContext,
    mutated_company_ids: &mut BTreeSet<u32>,
) -> Result<(), String> {
    let company_ids = resolve_company_target_ids(state, target, condition_context)?;
    for company_id in company_ids {
        let prior_cash = state
            .companies
            .iter()
            .find(|company| company.company_id == company_id)
            .map(|company| company.current_cash)
            .ok_or_else(|| format!("missing company_id {company_id} while applying cash effect"))?;
        let next_cash = prior_cash
            .checked_add(delta)
            .ok_or_else(|| format!("company_id {company_id} cash adjustment overflow"))?;
        if !service_post_company_stat_delta(
            state,
            company_id,
            RUNTIME_COMPANY_STAT_SLOT_CURRENT_CASH,
            delta as f64,
            false,
        ) {
            let company = state
                .companies
                .iter_mut()
                .find(|company| company.company_id == company_id)
                .ok_or_else(|| {
                    format!("missing company_id {company_id} while applying cash effect")
                })?;
            company.current_cash = next_cash;
        }
        mutated_company_ids.insert(company_id);
    }
    Ok(())
}

pub(super) fn apply_adjust_company_debt(
    state: &mut RuntimeState,
    target: &RuntimeCompanyTarget,
    delta: i64,
    condition_context: &ResolvedConditionContext,
    mutated_company_ids: &mut BTreeSet<u32>,
) -> Result<(), String> {
    let company_ids = resolve_company_target_ids(state, target, condition_context)?;
    for company_id in company_ids {
        let company = state
            .companies
            .iter_mut()
            .find(|company| company.company_id == company_id)
            .ok_or_else(|| format!("missing company_id {company_id} while applying debt effect"))?;
        company.debt = apply_u64_delta(company.debt, delta, company_id)?;
        mutated_company_ids.insert(company_id);
    }
    Ok(())
}

pub(super) fn apply_set_company_variable(
    state: &mut RuntimeState,
    target: &RuntimeCompanyTarget,
    index: u32,
    value: i64,
    condition_context: &ResolvedConditionContext,
    mutated_company_ids: &mut BTreeSet<u32>,
) -> Result<(), String> {
    let company_ids = resolve_company_target_ids(state, target, condition_context)?;
    for company_id in company_ids {
        state
            .company_runtime_variables
            .entry(company_id)
            .or_default()
            .insert(index, value);
        mutated_company_ids.insert(company_id);
    }
    Ok(())
}
