//! Integration tests for SystemBuilder::edge_with_ports()

use entropyk::SystemBuilder;
use entropyk_components::{ComponentError, ConnectedPort, JacobianBuilder, ResidualVector};

/// A minimal mock component with configurable ports for testing.
struct MockComponentWithPorts {
    n_eqs: usize,
    ports: Vec<ConnectedPort>,
}

impl MockComponentWithPorts {
    fn new(n_eqs: usize) -> Self {
        Self {
            n_eqs,
            ports: Vec::new(),
        }
    }

    #[allow(dead_code)]
    fn with_ports(n_eqs: usize, ports: Vec<ConnectedPort>) -> Self {
        Self { n_eqs, ports }
    }
}

impl entropyk_components::Component for MockComponentWithPorts {
    fn compute_residuals(
        &self,
        _state: &[f64],
        _residuals: &mut ResidualVector,
    ) -> Result<(), ComponentError> {
        Ok(())
    }

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

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

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

#[test]
fn test_edge_with_ports_component_not_found() {
    let result = SystemBuilder::new()
        .component("a", Box::new(MockComponentWithPorts::new(1)))
        .unwrap()
        .edge_with_ports("missing", "outlet", "a", "inlet");

    assert!(result.is_err());
    if let Err(ref err) = result {
        assert!(
            err.to_string().contains("not found"),
            "Expected ComponentNotFound error, got: {}",
            err
        );
    }
}

#[test]
fn test_edge_with_ports_target_not_found() {
    let result = SystemBuilder::new()
        .component("a", Box::new(MockComponentWithPorts::new(1)))
        .unwrap()
        .edge_with_ports("a", "outlet", "missing", "inlet");

    assert!(result.is_err());
    if let Err(ref err) = result {
        assert!(
            err.to_string().contains("not found"),
            "Expected ComponentNotFound error, got: {}",
            err
        );
    }
}

#[test]
fn test_edge_with_ports_cross_circuit_rejected() {
    use entropyk_core::CircuitId;

    let result = SystemBuilder::new()
        .component_in_circuit(
            "a",
            Box::new(MockComponentWithPorts::new(1)),
            CircuitId::ZERO,
        )
        .unwrap()
        .component_in_circuit("b", Box::new(MockComponentWithPorts::new(1)), CircuitId(1))
        .unwrap()
        .edge_with_ports("a", "outlet", "b", "inlet");

    assert!(result.is_err());
    if let Err(ref err) = result {
        assert!(
            err.to_string().contains("different circuits"),
            "Expected CrossCircuitEdge error, got: {}",
            err
        );
    }
}

#[test]
fn test_edge_with_ports_convention_based_names() {
    let builder = SystemBuilder::new()
        .component("a", Box::new(MockComponentWithPorts::new(2)))
        .unwrap()
        .component("b", Box::new(MockComponentWithPorts::new(2)))
        .unwrap()
        .edge_with_ports("a", "outlet", "b", "inlet")
        .expect("edge_with_ports should resolve convention-based port names");

    assert_eq!(builder.edge_count(), 1);
}

#[test]
fn test_edge_with_ports_suction_discharge_names() {
    let builder = SystemBuilder::new()
        .component("comp", Box::new(MockComponentWithPorts::new(2)))
        .unwrap()
        .component("pipe", Box::new(MockComponentWithPorts::new(2)))
        .unwrap()
        .edge_with_ports("comp", "discharge", "pipe", "inlet")
        .expect("edge_with_ports should resolve suction/discharge convention names");

    assert_eq!(builder.edge_count(), 1);
}

#[test]
fn test_edge_with_ports_unknown_port_name_error() {
    use entropyk::SystemBuilderError;

    let result = SystemBuilder::new()
        .component("a", Box::new(MockComponentWithPorts::new(2)))
        .unwrap()
        .component("b", Box::new(MockComponentWithPorts::new(2)))
        .unwrap()
        .edge_with_ports("a", "bogus_port", "b", "inlet");

    assert!(result.is_err());
    if let Err(SystemBuilderError::PortNotFound {
        component,
        port_name,
    }) = result
    {
        assert_eq!(component, "a");
        assert!(
            port_name.starts_with("bogus_port"),
            "port_name should start with the port name, got: {port_name}"
        );
    } else {
        panic!("Expected PortNotFound error");
    }
}

#[test]
fn test_edge_with_ports_same_circuit_succeeds() {
    use entropyk_core::CircuitId;

    let builder = SystemBuilder::new()
        .component_in_circuit(
            "a",
            Box::new(MockComponentWithPorts::new(2)),
            CircuitId::ZERO,
        )
        .unwrap()
        .component_in_circuit(
            "b",
            Box::new(MockComponentWithPorts::new(2)),
            CircuitId::ZERO,
        )
        .unwrap()
        .edge_with_ports("a", "outlet", "b", "inlet")
        .expect("edge_with_ports should succeed for same-circuit components");

    assert_eq!(builder.edge_count(), 1);
}

#[test]
fn test_build_system_with_port_validated_edges() {
    // DoF ledger post-CM1.4: 2-edge chain → 1 branch ṁ + 2×(P,h) = 5 unknowns,
    // so component equations must total 5 for the finalize DoF gate (2+2+1).
    // The last mock contributes a single equation to keep the system square.
    let system = SystemBuilder::new()
        .component("a", Box::new(MockComponentWithPorts::new(2)))
        .unwrap()
        .component("b", Box::new(MockComponentWithPorts::new(2)))
        .unwrap()
        .component("c", Box::new(MockComponentWithPorts::new(1)))
        .unwrap()
        .edge_with_ports("a", "outlet", "b", "inlet")
        .unwrap()
        .edge_with_ports("b", "outlet", "c", "inlet")
        .unwrap()
        .build()
        .expect("build should succeed");

    assert_eq!(system.node_count(), 3);
    assert_eq!(system.edge_count(), 2);
}
