use entropyk_components::port::{Connected, FluidId, Port};
/// Test d'intégration : boucle réfrigération simple R134a en Rust natif.
///
/// Ce test valide que le solveur Newton converge sur un cycle 4 composants
/// en utilisant des mock components algébriques linéaires dont les équations
/// sont mathématiquement cohérentes (ferment la boucle).
use entropyk_components::{
    Component, ComponentError, ConnectedPort, JacobianBuilder, ResidualVector, StateSlice,
};
use entropyk_core::{Enthalpy, MassFlow, Pressure};
use entropyk_solver::{
    solver::{NewtonConfig, Solver},
    system::{System, DEFAULT_MASS_FLOW_SEED_KG_S},
};

// Type alias: Port<Connected> ≡ ConnectedPort
type CP = Port<Connected>;

// ─── Mock compresseur ─────────────────────────────────────────────────────────
// r[0] = p_disc - (p_suc + 1 MPa)
// r[1] = h_disc - (h_suc + 75 kJ/kg)
struct MockCompressor {
    port_suc: CP,
    port_disc: CP,
}
impl Component for MockCompressor {
    fn compute_residuals(
        &self,
        _s: &StateSlice,
        r: &mut ResidualVector,
    ) -> Result<(), ComponentError> {
        r[0] = self.port_disc.pressure().to_pascals()
            - (self.port_suc.pressure().to_pascals() + 1_000_000.0);
        r[1] = self.port_disc.enthalpy().to_joules_per_kg()
            - (self.port_suc.enthalpy().to_joules_per_kg() + 75_000.0);
        Ok(())
    }
    fn jacobian_entries(
        &self,
        _s: &StateSlice,
        _j: &mut JacobianBuilder,
    ) -> Result<(), ComponentError> {
        Ok(())
    }
    fn n_equations(&self) -> usize {
        2
    }
    fn get_ports(&self) -> &[ConnectedPort] {
        &[]
    }
    fn port_mass_flows(&self, _: &StateSlice) -> Result<Vec<MassFlow>, ComponentError> {
        Ok(vec![
            MassFlow::from_kg_per_s(0.05),
            MassFlow::from_kg_per_s(-0.05),
        ])
    }
}

// ─── Mock condenseur ──────────────────────────────────────────────────────────
// r[0] = p_out - p_in
// r[1] = h_out - (h_in - 225 kJ/kg)
struct MockCondenser {
    port_in: CP,
    port_out: CP,
}
impl Component for MockCondenser {
    fn compute_residuals(
        &self,
        _s: &StateSlice,
        r: &mut ResidualVector,
    ) -> Result<(), ComponentError> {
        r[0] = self.port_out.pressure().to_pascals() - self.port_in.pressure().to_pascals();
        r[1] = self.port_out.enthalpy().to_joules_per_kg()
            - (self.port_in.enthalpy().to_joules_per_kg() - 225_000.0);
        Ok(())
    }
    fn jacobian_entries(
        &self,
        _s: &StateSlice,
        _j: &mut JacobianBuilder,
    ) -> Result<(), ComponentError> {
        Ok(())
    }
    fn n_equations(&self) -> usize {
        2
    }
    fn get_ports(&self) -> &[ConnectedPort] {
        &[]
    }
    fn port_mass_flows(&self, _: &StateSlice) -> Result<Vec<MassFlow>, ComponentError> {
        Ok(vec![
            MassFlow::from_kg_per_s(0.05),
            MassFlow::from_kg_per_s(-0.05),
        ])
    }
}

// ─── Mock détendeur ───────────────────────────────────────────────────────────
// r[0] = p_out - (p_in - 1 MPa)
// r[1] = h_out - h_in
struct MockValve {
    port_in: CP,
    port_out: CP,
}
impl Component for MockValve {
    fn compute_residuals(
        &self,
        _s: &StateSlice,
        r: &mut ResidualVector,
    ) -> Result<(), ComponentError> {
        r[0] = self.port_out.pressure().to_pascals()
            - (self.port_in.pressure().to_pascals() - 1_000_000.0);
        r[1] = self.port_out.enthalpy().to_joules_per_kg()
            - self.port_in.enthalpy().to_joules_per_kg();
        Ok(())
    }
    fn jacobian_entries(
        &self,
        _s: &StateSlice,
        _j: &mut JacobianBuilder,
    ) -> Result<(), ComponentError> {
        Ok(())
    }
    fn n_equations(&self) -> usize {
        2
    }
    fn get_ports(&self) -> &[ConnectedPort] {
        &[]
    }
    fn port_mass_flows(&self, _: &StateSlice) -> Result<Vec<MassFlow>, ComponentError> {
        Ok(vec![
            MassFlow::from_kg_per_s(0.05),
            MassFlow::from_kg_per_s(-0.05),
        ])
    }
}

// ─── Mock évaporateur ─────────────────────────────────────────────────────────
// r[0] = p_out - p_in
// r[1] = h_out - (h_in + 150 kJ/kg)
struct MockEvaporator {
    port_in: CP,
    port_out: CP,
}
impl Component for MockEvaporator {
    fn compute_residuals(
        &self,
        _s: &StateSlice,
        r: &mut ResidualVector,
    ) -> Result<(), ComponentError> {
        r[0] = self.port_out.pressure().to_pascals() - self.port_in.pressure().to_pascals();
        r[1] = self.port_out.enthalpy().to_joules_per_kg()
            - (self.port_in.enthalpy().to_joules_per_kg() + 150_000.0);
        Ok(())
    }
    fn jacobian_entries(
        &self,
        _s: &StateSlice,
        _j: &mut JacobianBuilder,
    ) -> Result<(), ComponentError> {
        Ok(())
    }
    fn n_equations(&self) -> usize {
        2
    }
    fn get_ports(&self) -> &[ConnectedPort] {
        &[]
    }
    fn port_mass_flows(&self, _: &StateSlice) -> Result<Vec<MassFlow>, ComponentError> {
        Ok(vec![
            MassFlow::from_kg_per_s(0.05),
            MassFlow::from_kg_per_s(-0.05),
        ])
    }
}

// ─── Helpers ──────────────────────────────────────────────────────────────────
fn port(p_pa: f64, h_j_kg: f64) -> CP {
    let (connected, _) = Port::new(
        FluidId::new("R134a"),
        Pressure::from_pascals(p_pa),
        Enthalpy::from_joules_per_kg(h_j_kg),
    )
    .connect(Port::new(
        FluidId::new("R134a"),
        Pressure::from_pascals(p_pa),
        Enthalpy::from_joules_per_kg(h_j_kg),
    ))
    .unwrap();
    connected
}

// ─── Test ─────────────────────────────────────────────────────────────────────
#[test]
fn test_simple_refrigeration_loop_rust() {
    //   Les équations :
    //     Comp    : p0 = p3 + 1 MPa  ;  h0 = h3 + 75 kJ/kg
    //     Cond    : p1 = p0          ;  h1 = h0 - 225 kJ/kg
    //     Valve   : p2 = p1 - 1 MPa  ;  h2 = h1
    //     Evap    : p3 = p2          ;  h3 = h2 + 150 kJ/kg
    //
    //   Bilan enthalpique en boucle : 75 - 225 + 150 = 0 → fermé ✓
    //   Bilan pressionnel en boucle : +1 - 0 - 1 - 0 = 0 → fermé ✓
    //
    //   Solution analytique (8 inconnues, 8 équations → infinité de solutions
    //   dépendant du point de référence, mais le solveur en trouve une) :
    //   En posant h3 = 410 kJ/kg, p3 = 350 kPa :
    //     h0 = 485, p0 = 1.35 MPa
    //     h1 = 260, p1 = 1.35 MPa
    //     h2 = 260, p2 = 350 kPa
    //     h3 = 410, p3 = 350 kPa

    let p_lp = 350_000.0_f64; // Pa
    let p_hp = 1_350_000.0_f64; // Pa = p_lp + 1 MPa

    // Les 4 bords (edge) du cycle :
    //   edge0 : comp → cond
    //   edge1 : cond → valve
    //   edge2 : valve → evap
    //   edge3 : evap → comp
    let comp = Box::new(MockCompressor {
        port_suc: port(p_lp, 410_000.0),
        port_disc: port(p_hp, 485_000.0),
    });
    let cond = Box::new(MockCondenser {
        port_in: port(p_hp, 485_000.0),
        port_out: port(p_hp, 260_000.0),
    });
    let valv = Box::new(MockValve {
        port_in: port(p_hp, 260_000.0),
        port_out: port(p_lp, 260_000.0),
    });
    let evap = Box::new(MockEvaporator {
        port_in: port(p_lp, 260_000.0),
        port_out: port(p_lp, 410_000.0),
    });

    let mut system = System::new();
    let n_comp = system.add_component(comp);
    let n_cond = system.add_component(cond);
    let n_valv = system.add_component(valv);
    let n_evap = system.add_component(evap);

    system.add_edge(n_comp, n_cond).unwrap();
    system.add_edge(n_cond, n_valv).unwrap();
    system.add_edge(n_valv, n_evap).unwrap();
    system.add_edge(n_evap, n_comp).unwrap();

    system.finalize().unwrap();

    let n_vars = system.full_state_vector_len();
    println!("Variables d'état : {}", n_vars);

    // État initial = solution analytique exacte → résidus = 0 → converge 1 itération.
    // CM1.4 layout: 1 ṁ partagé (branche série unique) + (P, h) par arête.
    // state = [ṁ, P₀, h₀, P₁, h₁, P₂, h₂, P₃, h₃]  (9 éléments)
    let m = DEFAULT_MASS_FLOW_SEED_KG_S;
    let initial_state = vec![
        m, // ṁ partagé (branche 0)
        p_hp, 485_000.0, // edge0 comp→cond : P, h
        p_hp, 260_000.0, // edge1 cond→valve : P, h
        p_lp, 260_000.0, // edge2 valve→evap : P, h
        p_lp, 410_000.0, // edge3 evap→comp : P, h
    ];

    let mut config = NewtonConfig {
        max_iterations: 50,
        tolerance: 1e-6,
        line_search: false,
        use_numerical_jacobian: true, // analytique vide → numérique
        initial_state: Some(initial_state),
        ..NewtonConfig::default()
    };

    let t0 = std::time::Instant::now();
    let result = config.solve(&mut system);
    let elapsed = t0.elapsed();

    println!("Durée : {:?}", elapsed);

    match &result {
        Ok(converged) => {
            println!(
                "✅ Convergé en {} itérations ({:?})",
                converged.iterations, elapsed
            );
            let sv = &converged.state;
            // CM1.4 layout: sv[0]=ṁ, then (P,h) per edge at stride 2.
            println!(
                "  comp→cond  : P={:.2} bar, h={:.1} kJ/kg",
                sv[1] / 1e5,
                sv[2] / 1e3
            );
            println!(
                "  cond→valve : P={:.2} bar, h={:.1} kJ/kg",
                sv[3] / 1e5,
                sv[4] / 1e3
            );
            println!(
                "  valve→evap : P={:.2} bar, h={:.1} kJ/kg",
                sv[5] / 1e5,
                sv[6] / 1e3
            );
            println!(
                "  evap→comp  : P={:.2} bar, h={:.1} kJ/kg",
                sv[7] / 1e5,
                sv[8] / 1e3
            );
        }
        Err(e) => {
            panic!("❌ Solveur échoué : {:?}", e);
        }
    }

    assert!(elapsed.as_millis() < 5000, "Doit converger en < 5 secondes");
    assert!(result.is_ok(), "Solveur doit converger");
}

// ─── T6 — Topology presolve assertions ───────────────────────────────────────

/// AC #3, #5: For a pure 4-edge series cycle, the topology presolve must:
///   - Produce state_vector_len = 9 (1 ṁ branch + 4×2 P,h) instead of 12 (old 4×3).
///   - Assign the same ṁ state index to all 4 edges (shared branch).
///   - Keep the system square: n_branches inferred as state_len - 2×edge_count = 1.
#[test]
fn test_topology_presolve_state_layout() {
    let p_lp = 350_000.0_f64;
    let p_hp = 1_350_000.0_f64;

    let comp = Box::new(MockCompressor {
        port_suc: port(p_lp, 410_000.0),
        port_disc: port(p_hp, 485_000.0),
    });
    let cond = Box::new(MockCondenser {
        port_in: port(p_hp, 485_000.0),
        port_out: port(p_hp, 260_000.0),
    });
    let valv = Box::new(MockValve {
        port_in: port(p_hp, 260_000.0),
        port_out: port(p_lp, 260_000.0),
    });
    let evap = Box::new(MockEvaporator {
        port_in: port(p_lp, 260_000.0),
        port_out: port(p_lp, 410_000.0),
    });

    let mut system = System::new();
    let n_comp = system.add_component(comp);
    let n_cond = system.add_component(cond);
    let n_valv = system.add_component(valv);
    let n_evap = system.add_component(evap);

    let e0 = system.add_edge(n_comp, n_cond).unwrap();
    let e1 = system.add_edge(n_cond, n_valv).unwrap();
    let e2 = system.add_edge(n_valv, n_evap).unwrap();
    let e3 = system.add_edge(n_evap, n_comp).unwrap();

    system.finalize().unwrap();

    // AC #3: CM1.4 state layout must be |B| + 2|E| = 1 + 8 = 9 (not 12).
    let state_len = system.state_vector_len();
    assert_eq!(
        state_len, 9,
        "CM1.4 state must be 1 branch + 4×2 P,h = 9, got {}",
        state_len
    );

    // AC #3: Branch count inference — all branches used exactly 1 ṁ slot.
    let edge_count = 4;
    let n_branches_inferred = state_len - 2 * edge_count;
    assert_eq!(
        n_branches_inferred, 1,
        "pure series cycle must have exactly 1 branch, inferred {}",
        n_branches_inferred
    );

    // AC #3: All 4 edges share the same ṁ state index.
    let m_idx: Vec<usize> = [e0, e1, e2, e3]
        .iter()
        .map(|&e| system.edge_state_indices_full(e).0)
        .collect();

    let first_m = m_idx[0];
    assert!(
        m_idx.iter().all(|&m| m == first_m),
        "all edges in a series branch must share the same ṁ index; got {:?}",
        m_idx
    );
    assert_eq!(first_m, 0, "shared ṁ index must be 0 (first slot)");
}

/// AC #5: A two-circuit system (2 independent series cycles) must have
/// 2 independent branch ṁ unknowns and state_vector_len = 2×(1 + 2×4) = 18.
#[test]
fn test_topology_presolve_two_independent_circuits() {
    use entropyk_solver::CircuitId;

    let p_lp = 350_000.0_f64;
    let p_hp = 1_350_000.0_f64;

    let mut system = System::new();

    // ── Circuit 0 ──
    let c0_comp = system
        .add_component_to_circuit(
            Box::new(MockCompressor {
                port_suc: port(p_lp, 410_000.0),
                port_disc: port(p_hp, 485_000.0),
            }),
            CircuitId::ZERO,
        )
        .unwrap();
    let c0_cond = system
        .add_component_to_circuit(
            Box::new(MockCondenser {
                port_in: port(p_hp, 485_000.0),
                port_out: port(p_hp, 260_000.0),
            }),
            CircuitId::ZERO,
        )
        .unwrap();
    let c0_valv = system
        .add_component_to_circuit(
            Box::new(MockValve {
                port_in: port(p_hp, 260_000.0),
                port_out: port(p_lp, 260_000.0),
            }),
            CircuitId::ZERO,
        )
        .unwrap();
    let c0_evap = system
        .add_component_to_circuit(
            Box::new(MockEvaporator {
                port_in: port(p_lp, 260_000.0),
                port_out: port(p_lp, 410_000.0),
            }),
            CircuitId::ZERO,
        )
        .unwrap();

    system.add_edge(c0_comp, c0_cond).unwrap();
    system.add_edge(c0_cond, c0_valv).unwrap();
    system.add_edge(c0_valv, c0_evap).unwrap();
    system.add_edge(c0_evap, c0_comp).unwrap();

    // ── Circuit 1 ──
    let c1 = CircuitId::from_number(1);
    let c1_comp = system
        .add_component_to_circuit(
            Box::new(MockCompressor {
                port_suc: port(p_lp, 410_000.0),
                port_disc: port(p_hp, 485_000.0),
            }),
            c1,
        )
        .unwrap();
    let c1_cond = system
        .add_component_to_circuit(
            Box::new(MockCondenser {
                port_in: port(p_hp, 485_000.0),
                port_out: port(p_hp, 260_000.0),
            }),
            c1,
        )
        .unwrap();
    let c1_valv = system
        .add_component_to_circuit(
            Box::new(MockValve {
                port_in: port(p_hp, 260_000.0),
                port_out: port(p_lp, 260_000.0),
            }),
            c1,
        )
        .unwrap();
    let c1_evap = system
        .add_component_to_circuit(
            Box::new(MockEvaporator {
                port_in: port(p_lp, 260_000.0),
                port_out: port(p_lp, 410_000.0),
            }),
            c1,
        )
        .unwrap();

    system.add_edge(c1_comp, c1_cond).unwrap();
    system.add_edge(c1_cond, c1_valv).unwrap();
    system.add_edge(c1_valv, c1_evap).unwrap();
    system.add_edge(c1_evap, c1_comp).unwrap();

    system.finalize().unwrap();

    // 2 circuits × (1 branch + 4×2 P,h) = 2 × 9 = 18 state variables.
    let state_len = system.state_vector_len();
    assert_eq!(
        state_len, 18,
        "two independent 4-edge cycles = 2 branches + 8×2 P,h = 18, got {}",
        state_len
    );

    // Inferred branch count = 18 - 2*8 = 2.
    let n_branches_inferred = state_len - 2 * 8;
    assert_eq!(
        n_branches_inferred, 2,
        "two independent cycles must have 2 branches, inferred {}",
        n_branches_inferred
    );
}
