//! Standalone heat exchanger tests — Modelica-style 4-port pattern.
//!
//! Each test wires a SINGLE heat exchanger with Source/Sink boundary components
//! on BOTH sides. No full chiller cycle, no thermal_couplings, no ThermalLoad.
//!
//! Pattern (Modelica `BoundaryNode.Source → HX → Sink`):
//!   Hot side:  HotSource(P, T, ṁ) → HX:hot_inlet → HX:hot_outlet → HotSink(P_back)
//!   Cold side: ColdSource(P, T, ṁ) → HX:cold_inlet → HX:cold_outlet → ColdSink(P_back)
//!
//! Test matrix:
//!   1. Air-Water HX       (HeatExchanger, hot=Water, cold=Air)
//!   2. Water-Water HX      (HeatExchanger, hot=Water, cold=Water)
//!   3. Air/Ref Evaporator  (HeatExchanger, hot=Air, cold=R134a liquid→vapor)
//!   4. Water/Ref Evaporator (HeatExchanger, hot=Water, cold=R134a liquid→vapor)
//!   5. Air/Ref Condenser   (HeatExchanger, hot=R134a vapor→liquid, cold=Air)
//!   6. Water/Ref Condenser  (HeatExchanger, hot=R134a vapor→liquid, cold=Water)

use entropyk_cli::run::{run_simulation, SimulationStatus};
use tempfile::tempdir;

fn run_config(json: &str) -> SimulationResult {
    let dir = tempdir().unwrap();
    let path = dir.path().join("hx_test.json");
    std::fs::write(&path, json).unwrap();
    run_simulation(&path, None, false).unwrap()
}

use entropyk_cli::run::SimulationResult;

fn assert_converged(result: &SimulationResult, label: &str) {
    assert!(
        matches!(result.status, SimulationStatus::Converged),
        "{label} did not converge: {:?} ({:?})",
        result.status,
        result.error
    );
}

// ── 1. Air-Water Heat Exchanger ──────────────────────────────────────────────
//   Hot water (60°C) heats cold air (20°C). Water cools down, air warms up.

#[test]
fn test_hx_air_water_4port() {
    let json = r#"
    {
      "fluid": "Water",
      "fluid_backend": "CoolProp",
      "circuits": [{
        "id": 0,
        "components": [
          { "type": "BrineSource", "name": "hot_in", "fluid": "Water", "p_set_bar": 2.0, "t_set_c": 60.0, "m_flow_kg_s": 0.5 },
          { "type": "HeatExchanger", "name": "hx", "ua": 3000.0, "hot_fluid_id": "Water", "cold_fluid_id": "Air", "cold_humidity_ratio": 0.010 },
          { "type": "BrineSink", "name": "hot_out", "fluid": "Water", "p_back_bar": 2.0 },
          { "type": "AirSource", "name": "cold_in", "p_set_bar": 1.01325, "t_dry_c": 20.0, "rh": 50.0, "m_flow_kg_s": 1.0 },
          { "type": "Fan", "name": "supply_fan", "fluid": "Air", "speed_ratio": 1.0, "air_density_kg_per_m3": 1.204, "design_flow_m3_s": 0.83, "curve_p0": 250.0, "curve_p1": 0.0, "curve_p2": -20.0, "eff_e0": 0.65, "eff_e1": 0.0, "eff_e2": 0.0 },
          { "type": "AirSink", "name": "cold_out", "p_back_bar": 1.01325 }
        ],
        "edges": [
          { "from": "hot_in:outlet", "to": "hx:hot_inlet" },
          { "from": "hx:hot_outlet", "to": "hot_out:inlet" },
          { "from": "cold_in:outlet", "to": "supply_fan:inlet" },
          { "from": "supply_fan:outlet", "to": "hx:cold_inlet" },
          { "from": "hx:cold_outlet", "to": "cold_out:inlet" }
        ]
      }],
      "solver": { "strategy": "newton", "max_iterations": 300, "tolerance": 1e-6 }
    }
    "#;
    let result = run_config(json);
    assert_converged(&result, "Air-Water HX");

    let state = result.state.expect("state");
    assert!(state.len() == 5, "5 edges expected, got {}", state.len());

    // Hot water cools down.
    let h_hot_in = state[0].enthalpy_kj_kg;
    let h_hot_out = state[1].enthalpy_kj_kg;
    assert!(
        h_hot_in > h_hot_out,
        "hot water must cool down: {h_hot_in} -> {h_hot_out} kJ/kg"
    );

    // Cold air warms up.
    let h_cold_in = state[3].enthalpy_kj_kg;
    let h_cold_out = state[4].enthalpy_kj_kg;
    assert!(
        h_cold_out > h_cold_in,
        "cold air must warm up: {h_cold_in} -> {h_cold_out} kJ/kg"
    );

    // Energy conservation: Q_hot = Q_cold (within 2%).
    // m_hot and m_cold are imposed by the sources.
    let m_hot = 0.5_f64; // kg/s
    let m_cold = 1.0_f64; // kg/s
    let q_hot = m_hot * (h_hot_in - h_hot_out); // kW
    let q_cold = m_cold * (h_cold_out - h_cold_in); // kW
    assert!(
        (q_hot - q_cold).abs() < 0.02 * q_hot.abs().max(0.001),
        "First Law: Q_hot={q_hot:.4} kW vs Q_cold={q_cold:.4} kW"
    );
    assert!(q_hot > 0.0, "heat must flow from hot to cold: Q={q_hot} kW");
}

// ── 2. Water-Water Heat Exchanger ────────────────────────────────────────────
//   Hot water (80°C) heats cold water (20°C).

#[test]
fn test_hx_water_water_4port() {
    let json = r#"
    {
      "fluid": "Water",
      "fluid_backend": "CoolProp",
      "circuits": [{
        "id": 0,
        "components": [
          { "type": "BrineSource", "name": "hot_in", "fluid": "Water", "p_set_bar": 2.0, "t_set_c": 80.0, "m_flow_kg_s": 0.3 },
          { "type": "HeatExchanger", "name": "hx", "ua": 5000.0, "hot_fluid_id": "Water", "cold_fluid_id": "Water" },
          { "type": "BrineSink", "name": "hot_out", "fluid": "Water", "p_back_bar": 2.0 },
          { "type": "BrineSource", "name": "cold_in", "fluid": "Water", "p_set_bar": 1.5, "t_set_c": 20.0, "m_flow_kg_s": 0.5 },
          { "type": "BrineSink", "name": "cold_out", "fluid": "Water", "p_back_bar": 1.5 }
        ],
        "edges": [
          { "from": "hot_in:outlet", "to": "hx:hot_inlet" },
          { "from": "hx:hot_outlet", "to": "hot_out:inlet" },
          { "from": "cold_in:outlet", "to": "hx:cold_inlet" },
          { "from": "hx:cold_outlet", "to": "cold_out:inlet" }
        ]
      }],
      "solver": { "strategy": "newton", "max_iterations": 300, "tolerance": 1e-6 }
    }
    "#;
    let result = run_config(json);
    assert_converged(&result, "Water-Water HX");

    let state = result.state.expect("state");
    assert!(state.len() == 4, "4 edges expected");

    let h_hot_in = state[0].enthalpy_kj_kg;
    let h_hot_out = state[1].enthalpy_kj_kg;
    assert!(
        h_hot_in > h_hot_out,
        "hot water must cool down: {h_hot_in} -> {h_hot_out}"
    );

    let h_cold_in = state[2].enthalpy_kj_kg;
    let h_cold_out = state[3].enthalpy_kj_kg;
    assert!(
        h_cold_out > h_cold_in,
        "cold water must warm up: {h_cold_in} -> {h_cold_out}"
    );

    let m_hot = 0.3_f64;
    let m_cold = 0.5_f64;
    let q_hot = m_hot * (h_hot_in - h_hot_out);
    let q_cold = m_cold * (h_cold_out - h_cold_in);
    assert!(
        (q_hot - q_cold).abs() < 0.02 * q_hot.abs().max(0.001),
        "First Law: Q_hot={q_hot:.4} vs Q_cold={q_cold:.4}"
    );
    assert!(q_hot > 0.0, "heat must flow from hot to cold");
}

// ── 3. Air/Refrigerant Evaporator ────────────────────────────────────────────
//   Warm air (25°C) heats cold refrigerant R134a (liquid at 5°C → vapor).
//   Hot side = Air, Cold side = R134a.

#[test]
fn test_hx_air_ref_evaporator_4port() {
    let json = r#"
    {
      "fluid": "R134a",
      "fluid_backend": "CoolProp",
      "circuits": [{
        "id": 0,
        "components": [
          { "type": "AirSource", "name": "hot_in", "p_set_bar": 1.01325, "t_dry_c": 25.0, "rh": 50.0, "m_flow_kg_s": 0.8 },
          { "type": "Evaporator", "name": "hx", "ua": 2000.0, "fluid": "R134a", "skip_pressure_eq": true, "secondary_fluid": "Air", "secondary_humidity_ratio": 0.010 },
          { "type": "AirSink", "name": "hot_out", "p_back_bar": 1.01325 },
          { "type": "RefrigerantSource", "name": "cold_in", "fluid": "R134a", "p_set_bar": 3.5, "quality": 0.0, "m_flow_kg_s": 0.05 },
          { "type": "RefrigerantSink", "name": "cold_out", "fluid": "R134a", "p_back_bar": 3.5 }
        ],
        "edges": [
          { "from": "cold_in:outlet", "to": "hx:inlet" },
          { "from": "hx:outlet", "to": "cold_out:inlet" },
          { "from": "hot_in:outlet", "to": "hx:secondary_inlet" },
          { "from": "hx:secondary_outlet", "to": "hot_out:inlet" }
        ]
      }],
      "solver": { "strategy": "newton", "max_iterations": 300, "tolerance": 1e-6 }
    }
    "#;
    let result = run_config(json);
    assert_converged(&result, "Air/Ref Evaporator");

    let state = result.state.expect("state");
    // Edge order: 0=ref_in, 1=ref_out, 2=air_in, 3=air_out
    let h_ref_in = state[0].enthalpy_kj_kg;
    let h_ref_out = state[1].enthalpy_kj_kg;
    assert!(
        h_ref_out > h_ref_in,
        "refrigerant must absorb heat (evaporate): {h_ref_in} -> {h_ref_out}"
    );

    let h_air_in = state[2].enthalpy_kj_kg;
    let h_air_out = state[3].enthalpy_kj_kg;
    assert!(
        h_air_in > h_air_out,
        "air must cool down: {h_air_in} -> {h_air_out}"
    );

    let m_air = 0.8_f64;
    let m_ref = 0.05_f64;
    let q_hot = m_air * (h_air_in - h_air_out);
    let q_cold = m_ref * (h_ref_out - h_ref_in);
    assert!(
        (q_hot - q_cold).abs() < 0.05 * q_hot.abs().max(0.001),
        "First Law: Q_air={q_hot:.4} vs Q_ref={q_cold:.4}"
    );
    assert!(q_hot > 0.0, "heat must flow from air to refrigerant");
}

// ── 4. Water/Refrigerant Evaporator ──────────────────────────────────────────
//   Chilled water (12°C) heats cold refrigerant R134a (liquid at 5°C → vapor).
//   Hot side = Water (secondary), Cold side = R134a (refrigerant).

#[test]
fn test_hx_water_ref_evaporator_4port() {
    let json = r#"
    {
      "fluid": "R134a",
      "fluid_backend": "CoolProp",
      "circuits": [{
        "id": 0,
        "components": [
          { "type": "BrineSource", "name": "hot_in", "fluid": "Water", "p_set_bar": 2.0, "t_set_c": 12.0, "m_flow_kg_s": 0.5 },
          { "type": "Evaporator", "name": "hx", "ua": 2000.0, "fluid": "R134a", "skip_pressure_eq": true, "secondary_fluid": "Water" },
          { "type": "BrineSink", "name": "hot_out", "fluid": "Water", "p_back_bar": 2.0 },
          { "type": "RefrigerantSource", "name": "cold_in", "fluid": "R134a", "p_set_bar": 3.5, "quality": 0.0, "m_flow_kg_s": 0.05 },
          { "type": "RefrigerantSink", "name": "cold_out", "fluid": "R134a", "p_back_bar": 3.5 }
        ],
        "edges": [
          { "from": "cold_in:outlet", "to": "hx:inlet" },
          { "from": "hx:outlet", "to": "cold_out:inlet" },
          { "from": "hot_in:outlet", "to": "hx:secondary_inlet" },
          { "from": "hx:secondary_outlet", "to": "hot_out:inlet" }
        ]
      }],
      "solver": { "strategy": "newton", "max_iterations": 300, "tolerance": 1e-6 }
    }
    "#;
    let result = run_config(json);
    assert_converged(&result, "Water/Ref Evaporator");

    let state = result.state.expect("state");
    // Edge order: 0=ref_in, 1=ref_out, 2=water_in, 3=water_out
    let h_ref_in = state[0].enthalpy_kj_kg;
    let h_ref_out = state[1].enthalpy_kj_kg;
    assert!(
        h_ref_out > h_ref_in,
        "refrigerant must absorb heat: {h_ref_in} -> {h_ref_out}"
    );

    let h_water_in = state[2].enthalpy_kj_kg;
    let h_water_out = state[3].enthalpy_kj_kg;
    assert!(
        h_water_in > h_water_out,
        "water must cool down: {h_water_in} -> {h_water_out}"
    );

    let m_water = 0.5_f64;
    let m_ref = 0.05_f64;
    let q_hot = m_water * (h_water_in - h_water_out);
    let q_cold = m_ref * (h_ref_out - h_ref_in);
    assert!(
        (q_hot - q_cold).abs() < 0.05 * q_hot.abs().max(0.001),
        "First Law: Q_water={q_hot:.4} vs Q_ref={q_cold:.4}"
    );
    assert!(q_hot > 0.0, "heat must flow from water to refrigerant");
}

// ── 5. Air/Refrigerant Condenser ─────────────────────────────────────────────
//   Hot refrigerant R134a (vapor at 50°C) heats cold air (35°C).
//   Hot side = R134a (refrigerant), Cold side = Air (secondary).

#[test]
fn test_hx_air_ref_condenser_4port() {
    let json = r#"
    {
      "fluid": "R134a",
      "fluid_backend": "CoolProp",
      "circuits": [{
        "id": 0,
        "components": [
          { "type": "RefrigerantSource", "name": "hot_in", "fluid": "R134a", "p_set_bar": 12.0, "quality": 1.0, "m_flow_kg_s": 0.05 },
          { "type": "Condenser", "name": "hx", "ua": 2500.0, "fluid": "R134a", "skip_pressure_eq": true, "secondary_fluid": "Air", "secondary_humidity_ratio": 0.010 },
          { "type": "RefrigerantSink", "name": "hot_out", "fluid": "R134a", "p_back_bar": 12.0 },
          { "type": "AirSource", "name": "cold_in", "p_set_bar": 1.01325, "t_dry_c": 35.0, "rh": 40.0, "m_flow_kg_s": 1.0 },
          { "type": "AirSink", "name": "cold_out", "p_back_bar": 1.01325 }
        ],
        "edges": [
          { "from": "hot_in:outlet", "to": "hx:inlet" },
          { "from": "hx:outlet", "to": "hot_out:inlet" },
          { "from": "cold_in:outlet", "to": "hx:secondary_inlet" },
          { "from": "hx:secondary_outlet", "to": "cold_out:inlet" }
        ]
      }],
      "solver": { "strategy": "newton", "max_iterations": 1000, "tolerance": 1e-6 }
    }
    "#;
    // NOTE: with the Modelica Free-P AirSource default, this case converges
    // linearly (~450 iterations) because the Condenser Jacobian is partially
    // finite-difference (see audit plan Step 4 — exact HX Jacobians). Restore
    // max_iterations: 300 once the analytic Jacobian lands.
    let result = run_config(json);
    assert_converged(&result, "Air/Ref Condenser");

    let state = result.state.expect("state");
    // Edge order: 0=ref_in, 1=ref_out, 2=air_in, 3=air_out
    let h_ref_in = state[0].enthalpy_kj_kg;
    let h_ref_out = state[1].enthalpy_kj_kg;
    assert!(
        h_ref_in > h_ref_out,
        "refrigerant must reject heat: {h_ref_in} -> {h_ref_out}"
    );

    let h_air_in = state[2].enthalpy_kj_kg;
    let h_air_out = state[3].enthalpy_kj_kg;
    assert!(
        h_air_out > h_air_in,
        "air must warm up: {h_air_in} -> {h_air_out}"
    );

    let m_ref = 0.05_f64;
    let m_air = 1.0_f64;
    let q_hot = m_ref * (h_ref_in - h_ref_out);
    let q_cold = m_air * (h_air_out - h_air_in);
    assert!(
        (q_hot - q_cold).abs() < 0.05 * q_hot.abs().max(0.001),
        "First Law: Q_ref={q_hot:.4} vs Q_air={q_cold:.4}"
    );
    assert!(q_hot > 0.0, "heat must flow from refrigerant to air");
}

// ── 6. Water/Refrigerant Condenser ───────────────────────────────────────────
//   Hot refrigerant R134a (vapor at 50°C) heats cold water (30°C).
//   Hot side = R134a (refrigerant), Cold side = Water (secondary).

#[test]
fn test_hx_water_ref_condenser_4port() {
    let json = r#"
    {
      "fluid": "R134a",
      "fluid_backend": "CoolProp",
      "circuits": [{
        "id": 0,
        "components": [
          { "type": "RefrigerantSource", "name": "hot_in", "fluid": "R134a", "p_set_bar": 12.0, "quality": 1.0, "m_flow_kg_s": 0.05 },
          { "type": "Condenser", "name": "hx", "ua": 2500.0, "fluid": "R134a", "skip_pressure_eq": true, "secondary_fluid": "Water" },
          { "type": "RefrigerantSink", "name": "hot_out", "fluid": "R134a", "p_back_bar": 12.0 },
          { "type": "BrineSource", "name": "cold_in", "fluid": "Water", "p_set_bar": 2.0, "t_set_c": 30.0, "m_flow_kg_s": 0.4 },
          { "type": "BrineSink", "name": "cold_out", "fluid": "Water", "p_back_bar": 2.0 }
        ],
        "edges": [
          { "from": "hot_in:outlet", "to": "hx:inlet" },
          { "from": "hx:outlet", "to": "hot_out:inlet" },
          { "from": "cold_in:outlet", "to": "hx:secondary_inlet" },
          { "from": "hx:secondary_outlet", "to": "cold_out:inlet" }
        ]
      }],
      "solver": { "strategy": "newton", "max_iterations": 300, "tolerance": 1e-6 }
    }
    "#;
    let result = run_config(json);
    assert_converged(&result, "Water/Ref Condenser");

    let state = result.state.expect("state");
    // Edge order: 0=ref_in, 1=ref_out, 2=water_in, 3=water_out
    let h_ref_in = state[0].enthalpy_kj_kg;
    let h_ref_out = state[1].enthalpy_kj_kg;
    assert!(
        h_ref_in > h_ref_out,
        "refrigerant must reject heat: {h_ref_in} -> {h_ref_out}"
    );

    let h_water_in = state[2].enthalpy_kj_kg;
    let h_water_out = state[3].enthalpy_kj_kg;
    assert!(
        h_water_out > h_water_in,
        "water must warm up: {h_water_in} -> {h_water_out}"
    );

    let m_ref = 0.05_f64;
    let m_water = 0.4_f64;
    let q_hot = m_ref * (h_ref_in - h_ref_out);
    let q_cold = m_water * (h_water_out - h_water_in);
    assert!(
        (q_hot - q_cold).abs() < 0.05 * q_hot.abs().max(0.001),
        "First Law: Q_ref={q_hot:.4} vs Q_water={q_cold:.4}"
    );
    assert!(q_hot > 0.0, "heat must flow from refrigerant to water");
}
