use super::super::fixtures::make_graph;

use crate::runtime::xyflow::changes::{
    ChangesToTransactionError, EdgeChange, NodeChange, NodeGraphChanges,
};
use jellyflow_core::core::{
    CanvasPoint, EdgeId, EdgeLabelAnchor, EdgeViewDescriptor, NodeId, NodeOrigin, PortId,
};
use jellyflow_core::ops::GraphOp;

#[test]
fn changes_to_transaction_is_reversible_and_applicable() {
    let (g0, a, _b, out_port, in_port, eid) = make_graph();
    let edge_data = serde_json::json!({ "condition": "approved" });
    let edge_view = EdgeViewDescriptor {
        renderer_key: Some("decision-edge".to_owned()),
        label: Some("Approved".to_owned()),
        label_anchor: Some(EdgeLabelAnchor::Target),
        source_marker_key: None,
        target_marker_key: Some("arrow".to_owned()),
        style_token: Some("success".to_owned()),
        route_kind: None,
        hit_target_width: Some(26.0),
    };

    let changes = NodeGraphChanges::from_parts(
        vec![
            NodeChange::Position {
                id: a,
                position: CanvasPoint { x: 42.0, y: 7.0 },
            },
            NodeChange::Origin {
                id: a,
                origin: Some(NodeOrigin { x: 0.5, y: 0.25 }),
            },
        ],
        vec![
            EdgeChange::Endpoints {
                id: eid,
                from: out_port,
                to: in_port,
            },
            EdgeChange::Hidden {
                id: eid,
                hidden: true,
            },
            EdgeChange::InteractionWidth {
                id: eid,
                interaction_width: Some(30.0),
            },
            EdgeChange::Data {
                id: eid,
                data: edge_data.clone(),
            },
            EdgeChange::View {
                id: eid,
                view: edge_view.clone(),
            },
        ],
    );

    let tx = changes.to_transaction(&g0).expect("tx");
    let mut g1 = g0.clone();
    tx.apply_to(&mut g1).expect("apply");

    assert_eq!(
        g1.nodes().get(&a).unwrap().pos,
        CanvasPoint { x: 42.0, y: 7.0 }
    );
    assert_eq!(
        g1.nodes().get(&a).unwrap().origin,
        Some(NodeOrigin { x: 0.5, y: 0.25 })
    );
    assert_eq!(g1.edges().get(&eid).unwrap().from, out_port);
    assert_eq!(g1.edges().get(&eid).unwrap().to, in_port);
    assert!(g1.edges().get(&eid).unwrap().hidden);
    assert_eq!(g1.edges().get(&eid).unwrap().interaction_width, Some(30.0));
    assert_eq!(g1.edges().get(&eid).unwrap().data, edge_data);
    assert_eq!(g1.edges().get(&eid).unwrap().view, edge_view);
}

#[test]
fn changes_to_transaction_remove_node_captures_ports_and_edges() {
    let (g0, a, b, out_port, in_port, eid) = make_graph();

    let changes = NodeGraphChanges::from_parts(vec![NodeChange::Remove { id: a }], Vec::new());

    let tx = changes.to_transaction(&g0).expect("tx");
    assert_eq!(tx.ops().len(), 1);
    match &tx.ops()[0] {
        GraphOp::RemoveNode {
            id, ports, edges, ..
        } => {
            assert_eq!(*id, a);
            assert_eq!(
                ports.iter().map(|(id, _)| *id).collect::<Vec<_>>(),
                vec![out_port]
            );
            assert_eq!(
                edges.iter().map(|(id, _)| *id).collect::<Vec<_>>(),
                vec![eid]
            );
        }
        other => panic!("expected remove node op, got {other:?}"),
    }

    let mut g1 = g0.clone();
    tx.apply_to(&mut g1).expect("apply");
    assert!(!g1.nodes().contains_key(&a));
    assert!(g1.nodes().contains_key(&b));
    assert!(!g1.ports().contains_key(&out_port));
    assert!(g1.ports().contains_key(&in_port));
    assert!(!g1.edges().contains_key(&eid));
}

#[test]
fn changes_to_transaction_reports_missing_node() {
    let (g0, _a, _b, _out_port, _in_port, _eid) = make_graph();
    let missing = NodeId::new();
    let changes = NodeGraphChanges::from_parts(
        vec![NodeChange::Position {
            id: missing,
            position: CanvasPoint { x: 10.0, y: 20.0 },
        }],
        Vec::new(),
    );

    let err = changes.to_transaction(&g0).expect_err("missing node");
    assert!(matches!(err, ChangesToTransactionError::MissingNode(id) if id == missing));
}

#[test]
fn changes_to_transaction_reports_missing_edge() {
    let (g0, _a, _b, _out_port, _in_port, _eid) = make_graph();
    let missing = EdgeId::new();
    let changes = NodeGraphChanges::from_parts(
        Vec::new(),
        vec![EdgeChange::Endpoints {
            id: missing,
            from: PortId::new(),
            to: PortId::new(),
        }],
    );

    let err = changes.to_transaction(&g0).expect_err("missing edge");
    assert!(matches!(err, ChangesToTransactionError::MissingEdge(id) if id == missing));
}

#[test]
fn changes_to_transaction_accepts_empty_changes() {
    let (g0, _a, _b, _out_port, _in_port, _eid) = make_graph();
    let changes = NodeGraphChanges::from_parts(Vec::new(), Vec::new());

    let tx = changes.to_transaction(&g0).expect("tx");
    assert!(tx.is_empty());
    assert!(tx.label().is_none());
}

#[test]
fn node_graph_changes_facade_consumes_parts() {
    let node = NodeId::new();
    let edge = EdgeId::new();
    let changes = NodeGraphChanges::from_parts(
        vec![NodeChange::Hidden {
            id: node,
            hidden: true,
        }],
        vec![EdgeChange::Remove { id: edge }],
    );

    let (nodes, edges) = changes.into_parts();

    assert!(matches!(nodes.as_slice(), [NodeChange::Hidden { id, hidden: true }] if *id == node));
    assert!(matches!(edges.as_slice(), [EdgeChange::Remove { id }] if *id == edge));
}
