use std::collections::BTreeSet;

use super::context::ValidationContext;
use crate::state::RuntimeState;

pub(super) fn validate_catalogs(
    state: &RuntimeState,
    context: &mut ValidationContext,
) -> Result<(), String> {
    let mut seen_territory_names = BTreeSet::new();
    for territory in &state.territories {
        if !context.seen_territory_ids.insert(territory.territory_id) {
            return Err(format!("duplicate territory_id {}", territory.territory_id));
        }
        if let Some(name) = territory.name.as_deref() {
            if name.trim().is_empty() {
                return Err(format!(
                    "territory_id {} has an empty name",
                    territory.territory_id
                ));
            }
            if !seen_territory_names.insert(name.to_string()) {
                return Err(format!("duplicate territory name {name:?}"));
            }
        }
    }

    let mut seen_train_ids = BTreeSet::new();
    for train in &state.trains {
        if !seen_train_ids.insert(train.train_id) {
            return Err(format!("duplicate train_id {}", train.train_id));
        }
        if !context.seen_company_ids.contains(&train.owner_company_id) {
            return Err(format!(
                "train_id {} references unknown owner_company_id {}",
                train.train_id, train.owner_company_id
            ));
        }
        if let Some(territory_id) = train.territory_id {
            if !context.seen_territory_ids.contains(&territory_id) {
                return Err(format!(
                    "train_id {} references unknown territory_id {}",
                    train.train_id, territory_id
                ));
            }
        }
        if train.retired && train.active {
            return Err(format!(
                "train_id {} cannot be active and retired at the same time",
                train.train_id
            ));
        }
        if train
            .locomotive_name
            .as_deref()
            .is_some_and(|value| value.trim().is_empty())
        {
            return Err(format!(
                "train_id {} has an empty locomotive_name",
                train.train_id
            ));
        }
    }

    let mut seen_locomotive_ids = BTreeSet::new();
    let mut seen_locomotive_names = BTreeSet::new();
    for entry in &state.locomotive_catalog {
        if !seen_locomotive_ids.insert(entry.locomotive_id) {
            return Err(format!(
                "duplicate locomotive_catalog.locomotive_id {}",
                entry.locomotive_id
            ));
        }
        if entry.name.trim().is_empty() {
            return Err(format!(
                "locomotive_catalog entry {} has an empty name",
                entry.locomotive_id
            ));
        }
        if !seen_locomotive_names.insert(entry.name.clone()) {
            return Err(format!(
                "duplicate locomotive_catalog.name {:?}",
                entry.name
            ));
        }
    }

    let mut seen_cargo_slots = BTreeSet::new();
    let mut seen_cargo_labels = BTreeSet::new();
    for entry in &state.cargo_catalog {
        if !(1..=11).contains(&entry.slot_id) {
            return Err(format!(
                "cargo_catalog entry has out-of-range slot_id {}",
                entry.slot_id
            ));
        }
        if !seen_cargo_slots.insert(entry.slot_id) {
            return Err(format!("duplicate cargo_catalog.slot_id {}", entry.slot_id));
        }
        if entry.label.trim().is_empty() {
            return Err(format!(
                "cargo_catalog entry {} has an empty label",
                entry.slot_id
            ));
        }
        if !seen_cargo_labels.insert(entry.label.clone()) {
            return Err(format!("duplicate cargo_catalog.label {:?}", entry.label));
        }
    }

    for entry in &state.company_territory_track_piece_counts {
        if !context.seen_company_ids.contains(&entry.company_id) {
            return Err(format!(
                "company_territory_track_piece_counts references unknown company_id {}",
                entry.company_id
            ));
        }
        if !context.seen_territory_ids.contains(&entry.territory_id) {
            return Err(format!(
                "company_territory_track_piece_counts references unknown territory_id {}",
                entry.territory_id
            ));
        }
    }

    let mut seen_company_territory_access = BTreeSet::new();
    for entry in &state.company_territory_access {
        if !context.seen_company_ids.contains(&entry.company_id) {
            return Err(format!(
                "company_territory_access references unknown company_id {}",
                entry.company_id
            ));
        }
        if !context.seen_territory_ids.contains(&entry.territory_id) {
            return Err(format!(
                "company_territory_access references unknown territory_id {}",
                entry.territory_id
            ));
        }
        if !seen_company_territory_access.insert((entry.company_id, entry.territory_id)) {
            return Err(format!(
                "duplicate company_territory_access pair ({}, {})",
                entry.company_id, entry.territory_id
            ));
        }
    }

    Ok(())
}
