use geographdb_core::{query_4d, GeoCypherResult, GraphNode4D, TemporalEdge};
use serde_json::json;

fn edge(dst: u64, begin_ts: u64, end_ts: u64) -> TemporalEdge {
    TemporalEdge {
        dst,
        weight: 1.0,
        begin_ts,
        end_ts,
    }
}

fn node(id: u64, xyz: (f32, f32, f32), successors: Vec<TemporalEdge>) -> GraphNode4D {
    let properties = [("name".to_string(), json!(format!("relay-{id}")))]
        .into_iter()
        .collect();

    GraphNode4D {
        id,
        x: xyz.0,
        y: xyz.1,
        z: xyz.2,
        begin_ts: 0,
        end_ts: 100,
        properties,
        successors,
    }
}

fn graph() -> Vec<GraphNode4D> {
    vec![
        node(1, (0.0, 0.0, 0.0), vec![edge(2, 0, 100), edge(4, 40, 100)]),
        node(2, (1.0, 0.0, 0.0), vec![edge(3, 0, 100)]),
        node(3, (2.0, 0.0, 0.0), vec![edge(6, 0, 30)]),
        node(4, (0.0, 2.0, 0.0), vec![edge(5, 0, 100)]),
        node(5, (1.0, 2.0, 0.0), vec![edge(3, 40, 100), edge(6, 40, 100)]),
        node(6, (3.0, 0.0, 0.0), vec![]),
    ]
}

#[test]
fn match_edges_accepts_property_projection_syntax() {
    let result = query_4d(&graph(), "MATCH (a)-[:CONNECTS]->(b) RETURN a.name, b.name").unwrap();

    let GeoCypherResult::Rows(rows) = result else {
        panic!("expected projected rows");
    };
    assert_eq!(rows.len(), 7);
    assert!(rows
        .iter()
        .any(|row| row["a.name"] == json!("relay-1") && row["b.name"] == json!("relay-2")));
}

#[test]
fn match_edges_can_project_builtin_node_fields() {
    let result = query_4d(&graph(), "MATCH (a)-[:CONNECTS]->(b) RETURN a.id, b.x").unwrap();

    let GeoCypherResult::Rows(rows) = result else {
        panic!("expected projected rows");
    };
    assert!(rows
        .iter()
        .any(|row| row["a.id"] == json!(1) && row["b.x"] == json!(1.0)));
}

#[test]
fn match_edges_supports_time_window_where_clause() {
    let result = query_4d(
        &graph(),
        "MATCH (a)-[:CONNECTS]->(b) WHERE time_window(50, 60) RETURN a, b",
    )
    .unwrap();

    assert_eq!(
        result,
        GeoCypherResult::Edges(vec![(1, 2), (1, 4), (2, 3), (4, 5), (5, 3), (5, 6)])
    );
}

#[test]
fn match_edges_supports_spatial_sphere_where_clause() {
    let result = query_4d(
        &graph(),
        "MATCH (a)-[:CONNECTS]->(b) WHERE spatial_sphere([0.0, 0.0, 0.0], 2.1) RETURN a, b",
    )
    .unwrap();

    assert_eq!(result, GeoCypherResult::Edges(vec![(1, 2), (1, 4), (2, 3)]));
}

#[test]
fn call_temporal_route_dispatches_to_earliest_arrival() {
    let result = query_4d(
        &graph(),
        "CALL db.route.temporal(1, 6, departure: 10) YIELD path, arrival_time, duration RETURN path, arrival_time",
    )
    .unwrap();

    match result {
        GeoCypherResult::Journey(journey) => {
            assert_eq!(journey.node_ids, vec![1, 2, 3, 6]);
            assert_eq!(journey.arrival_time, 13);
        }
        other => panic!("unexpected result: {other:?}"),
    }
}

#[test]
fn call_impact_radius_dispatches_to_reachability() {
    let result = query_4d(
        &graph(),
        "CALL db.impact.radius(1, spatial_sphere([0,0,0], 2.1), time_window(50, 60)) YIELD reachable RETURN reachable",
    )
    .unwrap();

    assert_eq!(result, GeoCypherResult::NodeIds(vec![1, 2, 4, 3]));
}

#[test]
fn call_resilience_bottlenecks_dispatches_to_low_link_algorithms() {
    let result = query_4d(
        &graph(),
        "CALL db.resilience.bottlenecks(spatial_sphere([0,0,0], 10.0), time_window(10, 20)) YIELD articulation_points, bridges RETURN articulation_points, bridges",
    )
    .unwrap();

    assert_eq!(
        result,
        GeoCypherResult::Bottlenecks {
            articulation_points: vec![2, 3],
            bridges: vec![(1, 2), (2, 3), (3, 6), (4, 5)],
        }
    );
}

#[test]
fn call_signal_propagate_dispatches_to_time_dependent_dijkstra() {
    let result = query_4d(
        &graph(),
        "CALL db.signal.propagate(1, departure: 10, spatial_sphere([0,0,0], 10.0)) YIELD reachable, unreachable RETURN reachable, unreachable",
    )
    .unwrap();

    match result {
        GeoCypherResult::Dijkstra(result) => {
            let arrivals: Vec<_> = result
                .reachable
                .iter()
                .map(|arrival| (arrival.node_id, arrival.arrival_time))
                .collect();
            assert_eq!(
                arrivals,
                vec![(1, 10), (2, 11), (3, 12), (6, 13), (4, 41), (5, 42)]
            );
        }
        other => panic!("unexpected result: {other:?}"),
    }
}
