//! Configuration parsing for CLI scenarios.
//!
//! This module defines the JSON schema for scenario configuration files
//! and provides utilities for loading and validating them.

use serde::{Deserialize, Serialize};
use std::collections::HashMap;

use crate::error::{CliError, CliResult};

/// Root configuration for a simulation scenario.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ScenarioConfig {
    /// Scenario name.
    #[serde(default)]
    pub name: Option<String>,
    /// Fluid name (e.g., "R134a", "R410A", "R744").
    pub fluid: String,
    /// Circuit configurations.
    #[serde(default)]
    pub circuits: Vec<CircuitConfig>,
    /// Thermal couplings between circuits.
    #[serde(default)]
    pub thermal_couplings: Vec<ThermalCouplingConfig>,
    /// Solver configuration.
    #[serde(default)]
    pub solver: SolverConfig,
    /// Optional metadata.
    #[serde(default)]
    pub metadata: Option<HashMap<String, serde_json::Value>>,
}

/// Thermal coupling configuration between two circuits.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ThermalCouplingConfig {
    /// Hot circuit ID.
    pub hot_circuit: usize,
    /// Cold circuit ID.
    pub cold_circuit: usize,
    /// Thermal conductance in W/K.
    pub ua: f64,
    /// Heat exchanger efficiency (0.0 to 1.0).
    #[serde(default = "default_efficiency")]
    pub efficiency: f64,
}

fn default_efficiency() -> f64 {
    0.95
}

/// Configuration for a single circuit.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CircuitConfig {
    /// Circuit ID (default: 0).
    #[serde(default)]
    pub id: usize,
    /// Components in this circuit.
    pub components: Vec<ComponentConfig>,
    /// Edge connections between components.
    #[serde(default)]
    pub edges: Vec<EdgeConfig>,
    /// Initial state for edges.
    #[serde(default)]
    pub initial_state: Option<InitialStateConfig>,
}

/// Configuration for a component.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ComponentConfig {
    /// Component type (e.g., "Compressor", "Condenser", "Evaporator", "ExpansionValve", "HeatExchanger").
    #[serde(rename = "type")]
    pub component_type: String,
    /// Component name for referencing in edges.
    pub name: String,
    /// Component-specific parameters.
    #[serde(flatten)]
    pub params: HashMap<String, serde_json::Value>,
}

/// Side conditions for a heat exchanger (hot or cold fluid).
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SideConditionsConfig {
    /// Fluid name (e.g., "R134a", "Water", "Air").
    pub fluid: String,
    /// Inlet temperature in °C.
    pub t_inlet_c: f64,
    /// Pressure in bar.
    #[serde(default = "default_pressure")]
    pub pressure_bar: f64,
    /// Mass flow rate in kg/s.
    #[serde(default = "default_mass_flow")]
    pub mass_flow_kg_s: f64,
}

fn default_pressure() -> f64 {
    1.0
}

fn default_mass_flow() -> f64 {
    0.1
}

/// Compressor AHRI 540 coefficients configuration.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Ahri540Config {
    /// Flow coefficient M1.
    pub m1: f64,
    /// Pressure ratio exponent M2.
    pub m2: f64,
    /// Power coefficients M3-M6 (cooling) and M7-M10 (heating).
    #[serde(default)]
    pub m3: Option<f64>,
    #[serde(default)]
    pub m4: Option<f64>,
    #[serde(default)]
    pub m5: Option<f64>,
    #[serde(default)]
    pub m6: Option<f64>,
    #[serde(default)]
    pub m7: Option<f64>,
    #[serde(default)]
    pub m8: Option<f64>,
    #[serde(default)]
    pub m9: Option<f64>,
    #[serde(default)]
    pub m10: Option<f64>,
}

/// Configuration for an edge between components.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EdgeConfig {
    /// Source component and port (e.g., "comp1:outlet").
    pub from: String,
    /// Target component and port (e.g., "cond1:inlet").
    pub to: String,
}

/// Initial state configuration.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct InitialStateConfig {
    /// Initial pressure in bar.
    pub pressure_bar: Option<f64>,
    /// Initial enthalpy in kJ/kg.
    pub enthalpy_kj_kg: Option<f64>,
    /// Initial temperature in Kelvin.
    pub temperature_k: Option<f64>,
}

/// Solver configuration.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SolverConfig {
    /// Solver strategy: "newton", "picard", or "fallback".
    #[serde(default = "default_solver_strategy")]
    pub strategy: String,
    /// Maximum iterations.
    #[serde(default = "default_max_iterations")]
    pub max_iterations: usize,
    /// Convergence tolerance.
    #[serde(default = "default_tolerance")]
    pub tolerance: f64,
    /// Timeout in milliseconds (0 = no timeout).
    #[serde(default)]
    pub timeout_ms: u64,
    /// Enable verbose output.
    #[serde(default)]
    pub verbose: bool,
}

fn default_solver_strategy() -> String {
    "fallback".to_string()
}

fn default_max_iterations() -> usize {
    100
}

fn default_tolerance() -> f64 {
    1e-6
}

impl Default for SolverConfig {
    fn default() -> Self {
        Self {
            strategy: default_solver_strategy(),
            max_iterations: default_max_iterations(),
            tolerance: default_tolerance(),
            timeout_ms: 0,
            verbose: false,
        }
    }
}

impl ScenarioConfig {
    /// Load a scenario configuration from a file.
    pub fn from_file(path: &std::path::Path) -> CliResult<Self> {
        let content = std::fs::read_to_string(path).map_err(|e| {
            if e.kind() == std::io::ErrorKind::NotFound {
                CliError::ConfigNotFound(path.to_path_buf())
            } else {
                CliError::Io(e)
            }
        })?;

        let config: Self = serde_json::from_str(&content).map_err(CliError::InvalidConfig)?;

        config.validate()?;

        Ok(config)
    }

    /// Load a scenario configuration from a JSON string.
    pub fn from_json(json: &str) -> CliResult<Self> {
        let config: Self = serde_json::from_str(json).map_err(CliError::InvalidConfig)?;
        config.validate()?;
        Ok(config)
    }

    /// Validate the configuration.
    pub fn validate(&self) -> CliResult<()> {
        if self.fluid.is_empty() {
            return Err(CliError::Config("fluid field is required".to_string()));
        }

        for (i, circuit) in self.circuits.iter().enumerate() {
            if circuit.components.is_empty() {
                return Err(CliError::Config(format!("circuit {} has no components", i)));
            }

            let component_names: std::collections::HashSet<&str> =
                circuit.components.iter().map(|c| c.name.as_str()).collect();

            for edge in &circuit.edges {
                let from_parts: Vec<&str> = edge.from.split(':').collect();
                let to_parts: Vec<&str> = edge.to.split(':').collect();

                if from_parts.len() != 2 || to_parts.len() != 2 {
                    return Err(CliError::Config(format!(
                        "invalid edge format '{} -> {}'. Expected 'component:port'",
                        edge.from, edge.to
                    )));
                }

                let from_component = from_parts[0];
                let to_component = to_parts[0];

                if !component_names.contains(from_component) {
                    return Err(CliError::Config(format!(
                        "edge references unknown component '{}' (in '{}'). Available: {}",
                        from_component,
                        edge.from,
                        component_names
                            .iter()
                            .cloned()
                            .collect::<Vec<_>>()
                            .join(", ")
                    )));
                }

                if !component_names.contains(to_component) {
                    return Err(CliError::Config(format!(
                        "edge references unknown component '{}' (in '{}'). Available: {}",
                        to_component,
                        edge.to,
                        component_names
                            .iter()
                            .cloned()
                            .collect::<Vec<_>>()
                            .join(", ")
                    )));
                }
            }
        }

        Ok(())
    }
}

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

    #[test]
    fn test_parse_minimal_config() {
        let json = r#"{ "fluid": "R134a" }"#;
        let config = ScenarioConfig::from_json(json).unwrap();
        assert_eq!(config.fluid, "R134a");
        assert!(config.circuits.is_empty());
    }

    #[test]
    fn test_parse_full_config() {
        let json = r#"
        {
            "fluid": "R410A",
            "circuits": [
                {
                    "id": 0,
                    "components": [
                        { "type": "Compressor", "name": "comp1", "ua": 5000.0 },
                        { "type": "Condenser", "name": "cond1", "ua": 5000.0 }
                    ],
                    "edges": [
                        { "from": "comp1:outlet", "to": "cond1:inlet" }
                    ],
                    "initial_state": {
                        "pressure_bar": 10.0,
                        "enthalpy_kj_kg": 400.0
                    }
                }
            ],
            "solver": {
                "strategy": "newton",
                "max_iterations": 50,
                "tolerance": 1e-8
            }
        }"#;
        let config = ScenarioConfig::from_json(json).unwrap();
        assert_eq!(config.fluid, "R410A");
        assert_eq!(config.circuits.len(), 1);
        assert_eq!(config.circuits[0].components.len(), 2);
        assert_eq!(config.solver.strategy, "newton");
    }

    #[test]
    fn test_validate_missing_fluid() {
        let json = r#"{ "fluid": "" }"#;
        let result = ScenarioConfig::from_json(json);
        assert!(result.is_err());
    }

    #[test]
    fn test_validate_invalid_edge_format() {
        let json = r#"
        {
            "fluid": "R134a",
            "circuits": [{
                "id": 0,
                "components": [{ "type": "Compressor", "name": "comp1", "ua": 5000.0 }],
                "edges": [{ "from": "invalid", "to": "also_invalid" }]
            }]
        }"#;
        let result = ScenarioConfig::from_json(json);
        assert!(result.is_err());
    }
}
