use std::path::Path;

use serde::{Deserialize, Serialize};

use crate::{RuntimeState, RuntimeSummary};

pub const SNAPSHOT_FORMAT_VERSION: u32 = 1;

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
pub struct RuntimeSnapshotSource {
    #[serde(default)]
    pub source_fixture_id: Option<String>,
    #[serde(default)]
    pub description: Option<String>,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct RuntimeSnapshotDocument {
    pub format_version: u32,
    pub snapshot_id: String,
    #[serde(default)]
    pub source: RuntimeSnapshotSource,
    pub state: RuntimeState,
}

impl RuntimeSnapshotDocument {
    pub fn summary(&self) -> RuntimeSummary {
        RuntimeSummary::from_state(&self.state)
    }
}

pub fn validate_runtime_snapshot_document(
    document: &RuntimeSnapshotDocument,
) -> Result<(), String> {
    if document.format_version != SNAPSHOT_FORMAT_VERSION {
        return Err(format!(
            "unsupported snapshot format_version {} (expected {})",
            document.format_version, SNAPSHOT_FORMAT_VERSION
        ));
    }
    if document.snapshot_id.trim().is_empty() {
        return Err("snapshot_id must not be empty".to_string());
    }
    document.state.validate()
}

pub fn load_runtime_snapshot_document(
    path: &Path,
) -> Result<RuntimeSnapshotDocument, Box<dyn std::error::Error>> {
    let text = std::fs::read_to_string(path)?;
    Ok(serde_json::from_str(&text)?)
}

pub fn save_runtime_snapshot_document(
    path: &Path,
    document: &RuntimeSnapshotDocument,
) -> Result<(), Box<dyn std::error::Error>> {
    validate_runtime_snapshot_document(document)
        .map_err(|err| format!("invalid runtime snapshot document: {err}"))?;
    let bytes = serde_json::to_vec_pretty(document)?;
    if let Some(parent) = path.parent() {
        std::fs::create_dir_all(parent)?;
    }
    std::fs::write(path, bytes)?;
    Ok(())
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::{
        CalendarPoint, RuntimeSaveProfileState, RuntimeServiceState, RuntimeWorldRestoreState,
    };
    use std::collections::BTreeMap;

    fn snapshot() -> RuntimeSnapshotDocument {
        RuntimeSnapshotDocument {
            format_version: SNAPSHOT_FORMAT_VERSION,
            snapshot_id: "snapshot-smoke".to_string(),
            source: RuntimeSnapshotSource {
                source_fixture_id: Some("fixture-smoke".to_string()),
                description: Some("test snapshot".to_string()),
            },
            state: RuntimeState {
                calendar: CalendarPoint {
                    year: 1830,
                    month_slot: 0,
                    phase_slot: 0,
                    tick_slot: 0,
                },
                world_flags: BTreeMap::new(),
                save_profile: RuntimeSaveProfileState::default(),
                world_restore: RuntimeWorldRestoreState::default(),
                metadata: BTreeMap::new(),
                companies: Vec::new(),
                selected_company_id: None,
                players: Vec::new(),
                selected_player_id: None,
                chairman_profiles: Vec::new(),
                selected_chairman_profile_id: None,
                trains: Vec::new(),
                locomotive_catalog: Vec::new(),
                cargo_catalog: Vec::new(),
                territories: Vec::new(),
                company_territory_track_piece_counts: Vec::new(),
                company_territory_access: Vec::new(),
                packed_event_collection: None,
                event_runtime_records: Vec::new(),
                candidate_availability: BTreeMap::new(),
                named_locomotive_availability: BTreeMap::new(),
                named_locomotive_cost: BTreeMap::new(),
                all_cargo_price_override: None,
                named_cargo_price_overrides: BTreeMap::new(),
                all_cargo_production_override: None,
                factory_cargo_production_override: None,
                farm_mine_cargo_production_override: None,
                named_cargo_production_overrides: BTreeMap::new(),
                cargo_production_overrides: BTreeMap::new(),
                world_runtime_variables: BTreeMap::new(),
                company_runtime_variables: BTreeMap::new(),
                player_runtime_variables: BTreeMap::new(),
                territory_runtime_variables: BTreeMap::new(),
                world_scalar_overrides: BTreeMap::new(),
                special_conditions: BTreeMap::new(),
                service_state: RuntimeServiceState::default(),
            },
        }
    }

    #[test]
    fn validates_snapshot_document() {
        let document = snapshot();
        assert!(validate_runtime_snapshot_document(&document).is_ok());
    }

    #[test]
    fn roundtrips_snapshot_json() {
        let document = snapshot();
        let value = serde_json::to_string_pretty(&document).expect("snapshot should serialize");
        let reparsed: RuntimeSnapshotDocument =
            serde_json::from_str(&value).expect("snapshot should deserialize");
        assert_eq!(document, reparsed);
    }
}
