//! Integration tests for multi-circuit machine definition (Story 3.3, FR9).
//!
//! Verifies multi-circuit heat pump topology (refrigerant + water) without thermal coupling.
//! Tests circuits from 2 up to the maximum of 5 circuits (circuit IDs 0-4).

use entropyk_components::{
    Component, ComponentError, ConnectedPort, JacobianBuilder, ResidualVector, StateSlice,
};
use entropyk_core::ThermalConductance;
use entropyk_solver::{CircuitId, System, ThermalCoupling, TopologyError};

/// Mock refrigerant component (e.g. compressor, condenser refrigerant side).
struct RefrigerantMock {
    n_equations: usize,
}

impl Component for RefrigerantMock {
    fn compute_residuals(
        &self,
        _state: &StateSlice,
        residuals: &mut ResidualVector,
    ) -> Result<(), ComponentError> {
        for r in residuals.iter_mut().take(self.n_equations) {
            *r = 0.0;
        }
        Ok(())
    }

    fn jacobian_entries(
        &self,
        _state: &StateSlice,
        _jacobian: &mut JacobianBuilder,
    ) -> Result<(), ComponentError> {
        Ok(())
    }

    fn n_equations(&self) -> usize {
        self.n_equations
    }

    fn get_ports(&self) -> &[ConnectedPort] {
        &[]
    }
}

#[test]
fn test_two_circuit_heat_pump_topology() {
    let mut sys = System::new();

    // Circuit 0: refrigerant (compressor -> condenser -> valve -> evaporator)
    let comp = sys
        .add_component_to_circuit(
            Box::new(RefrigerantMock { n_equations: 2 }),
            CircuitId::ZERO,
        )
        .unwrap();
    let cond = sys
        .add_component_to_circuit(
            Box::new(RefrigerantMock { n_equations: 2 }),
            CircuitId::ZERO,
        )
        .unwrap();
    let valve = sys
        .add_component_to_circuit(
            Box::new(RefrigerantMock { n_equations: 2 }),
            CircuitId::ZERO,
        )
        .unwrap();
    let evap = sys
        .add_component_to_circuit(
            Box::new(RefrigerantMock { n_equations: 2 }),
            CircuitId::ZERO,
        )
        .unwrap();

    sys.add_edge(comp, cond).unwrap();
    sys.add_edge(cond, valve).unwrap();
    sys.add_edge(valve, evap).unwrap();
    sys.add_edge(evap, comp).unwrap();

    // Circuit 1: water (pump -> condenser water side -> evaporator water side)
    let pump = sys
        .add_component_to_circuit(Box::new(RefrigerantMock { n_equations: 1 }), CircuitId(1))
        .unwrap();
    let cond_w = sys
        .add_component_to_circuit(Box::new(RefrigerantMock { n_equations: 1 }), CircuitId(1))
        .unwrap();
    let evap_w = sys
        .add_component_to_circuit(Box::new(RefrigerantMock { n_equations: 1 }), CircuitId(1))
        .unwrap();

    sys.add_edge(pump, cond_w).unwrap();
    sys.add_edge(cond_w, evap_w).unwrap();
    sys.add_edge(evap_w, pump).unwrap();

    assert_eq!(sys.circuit_count(), 2);
    assert_eq!(sys.circuit_nodes(CircuitId::ZERO).count(), 4);
    assert_eq!(sys.circuit_nodes(CircuitId(1)).count(), 3);
    assert_eq!(sys.circuit_edges(CircuitId::ZERO).count(), 4);
    assert_eq!(sys.circuit_edges(CircuitId(1)).count(), 3);

    let result = sys.finalize();
    assert!(
        result.is_ok(),
        "finalize should succeed: {:?}",
        result.err()
    );
}

#[test]
fn test_cross_circuit_rejected_integration() {
    let mut sys = System::new();
    let n0 = sys
        .add_component_to_circuit(
            Box::new(RefrigerantMock { n_equations: 0 }),
            CircuitId::ZERO,
        )
        .unwrap();
    let n1 = sys
        .add_component_to_circuit(Box::new(RefrigerantMock { n_equations: 0 }), CircuitId(1))
        .unwrap();

    let result = sys.add_edge(n0, n1);
    assert!(result.is_err());
    assert!(matches!(
        result,
        Err(TopologyError::CrossCircuitConnection { .. })
    ));
}

#[test]
fn test_maximum_five_circuits_integration() {
    // Integration test: Verify maximum of 5 circuits (IDs 0-4) is supported
    let mut sys = System::new();

    // Create 5 separate circuits, each with 2 nodes forming a cycle
    for circuit_id in 0..=4 {
        let n0 = sys
            .add_component_to_circuit(
                Box::new(RefrigerantMock { n_equations: 1 }),
                CircuitId(circuit_id),
            )
            .unwrap();
        let n1 = sys
            .add_component_to_circuit(
                Box::new(RefrigerantMock { n_equations: 1 }),
                CircuitId(circuit_id),
            )
            .unwrap();
        sys.add_edge(n0, n1).unwrap();
        sys.add_edge(n1, n0).unwrap();
    }

    assert_eq!(sys.circuit_count(), 5, "should have exactly 5 circuits");

    // Verify each circuit has its own nodes and edges
    for circuit_id in 0..=4 {
        assert_eq!(
            sys.circuit_nodes(CircuitId(circuit_id)).count(),
            2,
            "circuit {} should have 2 nodes",
            circuit_id
        );
        assert_eq!(
            sys.circuit_edges(CircuitId(circuit_id)).count(),
            2,
            "circuit {} should have 2 edges",
            circuit_id
        );
    }

    // Verify 6th circuit is rejected
    let result =
        sys.add_component_to_circuit(Box::new(RefrigerantMock { n_equations: 1 }), CircuitId(5));
    assert!(
        result.is_err(),
        "circuit 5 should be rejected (exceeds max of 4)"
    );
    assert!(matches!(
        result,
        Err(TopologyError::TooManyCircuits { requested: 5 })
    ));

    // Verify system can still be finalized with 5 circuits
    sys.finalize().unwrap();
}

#[test]
fn test_coupling_residuals_basic() {
    // Two circuits with one thermal coupling; verify coupling_residual_count and coupling_residuals.
    let mut sys = System::new();
    let n0 = sys
        .add_component_to_circuit(
            Box::new(RefrigerantMock { n_equations: 1 }),
            CircuitId::ZERO,
        )
        .unwrap();
    let n1 = sys
        .add_component_to_circuit(
            Box::new(RefrigerantMock { n_equations: 1 }),
            CircuitId::ZERO,
        )
        .unwrap();
    sys.add_edge(n0, n1).unwrap();
    sys.add_edge(n1, n0).unwrap();

    let n2 = sys
        .add_component_to_circuit(Box::new(RefrigerantMock { n_equations: 1 }), CircuitId(1))
        .unwrap();
    let n3 = sys
        .add_component_to_circuit(Box::new(RefrigerantMock { n_equations: 1 }), CircuitId(1))
        .unwrap();
    sys.add_edge(n2, n3).unwrap();
    sys.add_edge(n3, n2).unwrap();

    let coupling = ThermalCoupling::new(
        CircuitId::ZERO,
        CircuitId(1),
        ThermalConductance::from_watts_per_kelvin(1000.0),
    );
    sys.add_thermal_coupling(coupling).unwrap();

    sys.finalize().unwrap();

    assert_eq!(sys.coupling_residual_count(), 1);

    let temperatures = [(350.0_f64, 300.0_f64)]; // T_hot, T_cold in K
    let mut out = [0.0_f64; 4];
    sys.coupling_residuals(&temperatures, &mut out);
    // Q = UA * (T_hot - T_cold) = 1000 * 50 = 50000 W into cold circuit
    assert!(out[0] > 0.0);
    assert!((out[0] - 50000.0).abs() < 1.0);
}
