//! System snapshot structures for JSON serialization/deserialization
//!
//! This module provides types for capturing complete system state including
//! topology, component parameters, fluid state, and backend information.

use crate::coupling::ThermalCoupling;
use entropyk_components::ComponentParams;
use entropyk_core::SystemState;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;

/// Snapshot of the complete system for serialization
///
/// Contains all information needed to reconstruct an identical system:
/// - Topology (components and their connections)
/// - Component parameters
/// - Fluid state (pressures and enthalpies)
/// - Fluid backend information
/// - Solver configuration
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct SystemSnapshot {
    /// Schema version for forward/backward compatibility
    pub version: String,
    /// System topology (components, edges, thermal couplings)
    pub topology: TopologySnapshot,
    /// Component-specific parameters indexed by component name
    #[serde(default)]
    pub parameters: HashMap<String, ComponentParams>,
    /// Fluid state (edge pressures and enthalpies)
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub fluid_state: Option<SystemState>,
    /// Fluid backend information
    pub fluid_backend: FluidBackendInfo,
    /// Solver configuration
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub solver_config: Option<SolverConfigSnapshot>,
    /// Component name → type mapping for stable reconstruction
    #[serde(default, skip_serializing_if = "HashMap::is_empty")]
    pub component_names: HashMap<String, String>,
    /// Component name → circuit ID mapping
    #[serde(default, skip_serializing_if = "HashMap::is_empty")]
    pub circuit_assignments: HashMap<String, u16>,
    /// Constraints for inverse control
    #[serde(default, skip_serializing_if = "Vec::is_empty")]
    pub constraints: Vec<ConstraintSnapshot>,
    /// Bounded control variables for inverse control
    #[serde(default, skip_serializing_if = "Vec::is_empty")]
    pub bounded_variables: Vec<BoundedVariableSnapshot>,
    /// Optional metadata
    #[serde(default, skip_serializing_if = "HashMap::is_empty")]
    pub metadata: HashMap<String, serde_json::Value>,
}

/// Snapshot of system topology
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct TopologySnapshot {
    /// Flow edges between components
    #[serde(default)]
    pub edges: Vec<EdgeSnapshot>,
    /// Thermal couplings between circuits
    #[serde(default, skip_serializing_if = "Vec::is_empty")]
    pub thermal_couplings: Vec<ThermalCoupling>,
}

/// Snapshot of a flow edge
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct EdgeSnapshot {
    /// Source component name
    pub source: String,
    /// Source port name
    #[serde(default)]
    pub source_port: String,
    /// Target component name
    pub target: String,
    /// Target port name
    #[serde(default)]
    pub target_port: String,
    /// Circuit ID
    pub circuit_id: u16,
}

/// Information about the fluid backend
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct FluidBackendInfo {
    /// Backend name (e.g., "CoolPropBackend", "TabularBackend")
    pub name: String,
    /// Backend version
    pub version: String,
    /// Backend hash for verification
    #[serde(skip_serializing_if = "Option::is_none")]
    pub hash: Option<String>,
}

/// Snapshot of solver configuration
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct SolverConfigSnapshot {
    /// Solver type ("NewtonRaphson", "SequentialSubstitution", etc.)
    pub solver_type: String,
    /// Maximum iterations
    pub max_iterations: usize,
    /// Convergence tolerance
    pub tolerance: f64,
    /// Divergence threshold
    pub divergence_threshold: f64,
}

impl Default for SolverConfigSnapshot {
    fn default() -> Self {
        Self {
            solver_type: "NewtonRaphson".to_string(),
            max_iterations: 100,
            tolerance: 1e-6,
            divergence_threshold: 1e10,
        }
    }
}

/// Snapshot of a constraint for inverse control
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct ConstraintSnapshot {
    /// Constraint identifier
    pub id: String,
    /// Component name the constraint targets
    pub component: String,
    /// Output type being constrained (e.g., "capacity", "superheat")
    pub output_type: String,
    /// Target value for the constraint
    pub target: f64,
}

/// Snapshot of a bounded control variable
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct BoundedVariableSnapshot {
    /// Variable identifier
    pub id: String,
    /// Component name the variable belongs to
    pub component: String,
    /// Variable name (e.g., "f_m", "f_power", "opening")
    pub variable_name: String,
    /// Lower bound
    pub lower_bound: f64,
    /// Upper bound
    pub upper_bound: f64,
    /// Initial value
    pub initial_value: f64,
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_system_snapshot_serialization() {
        let snapshot = SystemSnapshot {
            version: "1.0".to_string(),
            topology: TopologySnapshot {
                edges: vec![],
                thermal_couplings: vec![],
            },
            parameters: HashMap::new(),
            fluid_state: None,
            fluid_backend: FluidBackendInfo {
                name: "TestBackend".to_string(),
                version: "1.0.0".to_string(),
                hash: Some("abc123".to_string()),
            },
            solver_config: Some(SolverConfigSnapshot::default()),
            component_names: HashMap::new(),
            circuit_assignments: HashMap::new(),
            constraints: vec![],
            bounded_variables: vec![],
            metadata: HashMap::new(),
        };

        let json = serde_json::to_string_pretty(&snapshot).unwrap();
        let deserialized: SystemSnapshot = serde_json::from_str(&json).unwrap();

        assert_eq!(snapshot, deserialized);
    }

    #[test]
    fn test_solver_config_default() {
        let config = SolverConfigSnapshot::default();
        assert_eq!(config.solver_type, "NewtonRaphson");
        assert_eq!(config.max_iterations, 100);
        assert_eq!(config.tolerance, 1e-6);
    }

    #[test]
    fn test_camel_case_output() {
        let edge = EdgeSnapshot {
            source: "comp_a".to_string(),
            source_port: "outlet".to_string(),
            target: "comp_b".to_string(),
            target_port: "inlet".to_string(),
            circuit_id: 0,
        };
        let json = serde_json::to_string(&edge).unwrap();
        assert!(json.contains("\"sourcePort\""));
        assert!(json.contains("\"targetPort\""));
        assert!(json.contains("\"circuitId\""));
    }

    #[test]
    fn test_backward_compat_missing_fields() {
        // Old snapshot without new fields should deserialize with defaults
        let old_json = r#"{
            "version": "1.0",
            "topology": { "edges": [] },
            "parameters": {},
            "fluidBackend": { "name": "Test", "version": "1.0" }
        }"#;
        let snapshot: SystemSnapshot = serde_json::from_str(old_json).unwrap();
        assert!(snapshot.component_names.is_empty());
        assert!(snapshot.circuit_assignments.is_empty());
        assert!(snapshot.constraints.is_empty());
        assert!(snapshot.bounded_variables.is_empty());
    }
}
