use super::fixtures::{insert_data_input, insert_data_output, insert_edge, insert_node};

use crate::rules::{EdgeEndpoint, plan_reconnect_edge};
use jellyflow_core::core::{
    EdgeId, EdgeReconnectable, EdgeReconnectableEndpoint, GraphBuilder, NodeId, PortCapacity,
    PortId,
};
use jellyflow_core::ops::GraphTransaction;

#[test]
fn plan_reconnect_preserves_edge_id() {
    let mut graph = GraphBuilder::default();

    let a = NodeId::new();
    let b = NodeId::new();
    let c = NodeId::new();
    insert_node(&mut graph, a, "core.a");
    insert_node(&mut graph, b, "core.b");
    insert_node(&mut graph, c, "core.c");

    let out1 = PortId::new();
    let out2 = PortId::new();
    let inn = PortId::new();
    insert_data_output(&mut graph, out1, a, "out1", PortCapacity::Multi);
    insert_data_output(&mut graph, out2, c, "out2", PortCapacity::Multi);
    insert_data_input(&mut graph, inn, b, "in", PortCapacity::Multi);

    let edge_id = EdgeId::new();
    insert_edge(&mut graph, edge_id, out1, inn);

    let noop_plan = plan_reconnect_edge(&graph, edge_id, EdgeEndpoint::From, out1);
    assert!(noop_plan.is_accept());
    assert!(noop_plan.ops().is_empty());

    let self_connection_plan = plan_reconnect_edge(&graph, edge_id, EdgeEndpoint::To, out1);
    assert!(self_connection_plan.is_reject());
    assert_eq!(
        self_connection_plan.diagnostics()[0].message,
        "cannot connect a port to itself"
    );
    assert!(self_connection_plan.ops().is_empty());

    let plan = plan_reconnect_edge(&graph, edge_id, EdgeEndpoint::From, out2);
    assert!(plan.is_accept());
    assert_eq!(plan.ops().len(), 1);

    let tx = GraphTransaction::from_ops(plan.into_ops());
    let mut graph = graph.build_unchecked();
    tx.apply_to(&mut graph).unwrap();

    let edge = graph.edges().get(&edge_id).unwrap();
    assert_eq!(edge.from, out2);
    assert_eq!(edge.to, inn);
}

#[test]
fn plan_reconnect_respects_edge_and_port_policy() {
    let mut graph = GraphBuilder::default();

    let a = NodeId::new();
    let b = NodeId::new();
    let c = NodeId::new();
    insert_node(&mut graph, a, "core.a");
    insert_node(&mut graph, b, "core.b");
    insert_node(&mut graph, c, "core.c");

    let out1 = PortId::new();
    let out2 = PortId::new();
    let in1 = PortId::new();
    let in2 = PortId::new();
    insert_data_output(&mut graph, out1, a, "out1", PortCapacity::Multi);
    insert_data_output(&mut graph, out2, c, "out2", PortCapacity::Multi);
    insert_data_input(&mut graph, in1, b, "in1", PortCapacity::Multi);
    insert_data_input(&mut graph, in2, c, "in2", PortCapacity::Multi);

    let edge_id = EdgeId::new();
    insert_edge(&mut graph, edge_id, out1, in1);
    graph
        .update_edge(&edge_id, |edge| {
            edge.reconnectable = Some(EdgeReconnectable::Endpoint(
                EdgeReconnectableEndpoint::Target,
            ))
        })
        .expect("edge exists");

    let source_plan = plan_reconnect_edge(&graph, edge_id, EdgeEndpoint::From, out2);
    assert!(source_plan.is_reject());

    graph
        .update_port(&in2, |port| port.connectable_end = Some(false))
        .expect("port exists");
    let target_plan = plan_reconnect_edge(&graph, edge_id, EdgeEndpoint::To, in2);
    assert!(target_plan.is_reject());

    graph
        .update_port(&in2, |port| port.connectable_end = Some(true))
        .expect("port exists");
    let target_plan = plan_reconnect_edge(&graph, edge_id, EdgeEndpoint::To, in2);
    assert!(target_plan.is_accept());

    graph
        .update_edge(&edge_id, |edge| {
            edge.reconnectable = Some(EdgeReconnectable::Bool(false))
        })
        .expect("edge exists");
    let disabled_plan = plan_reconnect_edge(&graph, edge_id, EdgeEndpoint::To, in2);
    assert!(disabled_plan.is_reject());
}

#[test]
fn plan_reconnect_single_target_disconnects_other_edges() {
    let mut graph = GraphBuilder::default();

    let a = NodeId::new();
    let b = NodeId::new();
    let c = NodeId::new();
    let d = NodeId::new();
    insert_node(&mut graph, a, "core.a");
    insert_node(&mut graph, b, "core.b");
    insert_node(&mut graph, c, "core.c");
    insert_node(&mut graph, d, "core.d");

    let out1 = PortId::new();
    let out2 = PortId::new();
    let out3 = PortId::new();
    let inn = PortId::new();
    insert_data_output(&mut graph, out1, a, "out1", PortCapacity::Multi);
    insert_data_output(&mut graph, out2, c, "out2", PortCapacity::Multi);
    insert_data_output(&mut graph, out3, d, "out3", PortCapacity::Multi);
    insert_data_input(&mut graph, inn, b, "in", PortCapacity::Single);

    let edge_keep = EdgeId::new();
    let edge_drop = EdgeId::new();
    insert_edge(&mut graph, edge_keep, out1, inn);
    insert_edge(&mut graph, edge_drop, out2, inn);

    let plan = plan_reconnect_edge(&graph, edge_keep, EdgeEndpoint::From, out3);
    assert!(plan.is_accept());
    assert_eq!(plan.ops().len(), 2, "expected remove + set_endpoints");

    let tx = GraphTransaction::from_ops(plan.into_ops());
    let mut graph = graph.build_unchecked();
    tx.apply_to(&mut graph).unwrap();

    assert!(
        !graph.edges().contains_key(&edge_drop),
        "expected other edge removed"
    );
    let edge = graph.edges().get(&edge_keep).unwrap();
    assert_eq!(edge.from, out3);
    assert_eq!(edge.to, inn);
}

#[test]
fn plan_reconnect_rejects_duplicate_connection() {
    let mut graph = GraphBuilder::default();

    let a = NodeId::new();
    let b = NodeId::new();
    let c = NodeId::new();
    insert_node(&mut graph, a, "core.a");
    insert_node(&mut graph, b, "core.b");
    insert_node(&mut graph, c, "core.c");

    let out1 = PortId::new();
    let out2 = PortId::new();
    let inn = PortId::new();
    insert_data_output(&mut graph, out1, a, "out1", PortCapacity::Multi);
    insert_data_output(&mut graph, out2, c, "out2", PortCapacity::Multi);
    insert_data_input(&mut graph, inn, b, "in", PortCapacity::Multi);

    let edge_a = EdgeId::new();
    let edge_b = EdgeId::new();
    insert_edge(&mut graph, edge_a, out1, inn);
    insert_edge(&mut graph, edge_b, out2, inn);

    let plan = plan_reconnect_edge(&graph, edge_a, EdgeEndpoint::From, out2);
    assert!(plan.is_reject());
}
