use std::collections::{HashMap, HashSet, VecDeque};
use std::error::Error;
use std::fmt::{Display, Formatter};
use std::fs::create_dir_all;
use std::path::PathBuf;

use serde::{Deserialize, Serialize};

use crate::{
    layout_radial_hierarchy_with_artifacts, write_svg_path_with_options, Edge, EdgeRouteKind,
    Graph, LayoutArtifacts, LayoutConfig, LayoutError, Node, SvgConfig, SvgExportError,
};

#[derive(Debug)]
pub enum BridgeError {
    DuplicateNodeId {
        node_id: u32,
    },
    DuplicateEdgeIndex {
        edge_index: usize,
    },
    MissingNodeRef {
        edge_index: usize,
        node_id: u32,
    },
    RootNotFound {
        root_iri: String,
    },
    NoDescendants {
        root_iri: String,
    },
    CreateOutputDir {
        path: PathBuf,
        source: std::io::Error,
    },
    SvgExport(SvgExportError),
    Layout(LayoutError),
}

impl Display for BridgeError {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        match self {
            BridgeError::DuplicateNodeId { node_id } => {
                write!(f, "bridge request contains duplicate node_id {node_id}")
            }
            BridgeError::DuplicateEdgeIndex { edge_index } => {
                write!(
                    f,
                    "bridge request contains duplicate edge_index {edge_index}"
                )
            }
            BridgeError::MissingNodeRef {
                edge_index,
                node_id,
            } => {
                write!(
                    f,
                    "bridge request edge {edge_index} references unknown node_id {node_id}"
                )
            }
            BridgeError::RootNotFound { root_iri } => {
                write!(
                    f,
                    "root class IRI {root_iri} was not found in the bridge graph"
                )
            }
            BridgeError::NoDescendants { root_iri } => {
                write!(
                    f,
                    "root class IRI {root_iri} has no subclass descendants in the bridge graph"
                )
            }
            BridgeError::CreateOutputDir { path, source } => write!(
                f,
                "failed to create SVG output directory {}: {source}",
                path.display()
            ),
            BridgeError::SvgExport(error) => Display::fmt(error, f),
            BridgeError::Layout(error) => Display::fmt(error, f),
        }
    }
}

impl Error for BridgeError {
    fn source(&self) -> Option<&(dyn Error + 'static)> {
        match self {
            BridgeError::CreateOutputDir { source, .. } => Some(source),
            BridgeError::SvgExport(error) => Some(error),
            BridgeError::Layout(error) => Some(error),
            _ => None,
        }
    }
}

impl From<LayoutError> for BridgeError {
    fn from(value: LayoutError) -> Self {
        Self::Layout(value)
    }
}

impl From<SvgExportError> for BridgeError {
    fn from(value: SvgExportError) -> Self {
        Self::SvgExport(value)
    }
}

#[derive(Debug, Clone, PartialEq)]
pub struct BridgeRuntimeConfig {
    pub layout: LayoutConfig,
    pub svg: SvgConfig,
    pub svg_output_path: Option<PathBuf>,
    pub canonicalize_input: bool,
}

impl BridgeRuntimeConfig {
    pub fn json_only(layout: LayoutConfig) -> Self {
        Self {
            layout,
            svg: SvgConfig::default(),
            svg_output_path: None,
            canonicalize_input: true,
        }
    }
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct GoBridgeRequest {
    pub root_iri: String,
    pub nodes: Vec<GoBridgeNode>,
    pub edges: Vec<GoBridgeEdge>,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct GoBridgeNode {
    pub node_id: u32,
    pub iri: String,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct GoBridgeEdge {
    pub edge_index: usize,
    pub parent_id: u32,
    pub child_id: u32,
    #[serde(default)]
    pub predicate_iri: Option<String>,
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct GoBridgeResponse {
    pub nodes: Vec<GoBridgeRoutedNode>,
    pub route_segments: Vec<GoBridgeRouteSegment>,
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct GoBridgeRoutedNode {
    pub node_id: u32,
    pub x: f64,
    pub y: f64,
    pub level: usize,
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct GoBridgeRouteSegment {
    pub edge_index: usize,
    pub kind: String,
    pub points: Vec<GoBridgePoint>,
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct GoBridgePoint {
    pub x: f64,
    pub y: f64,
}

struct BridgeGraph {
    root_iri: String,
    graph: Graph,
    node_ids: Vec<u32>,
    edge_indices: Vec<usize>,
}

pub fn process_go_bridge_request(
    request: GoBridgeRequest,
    config: LayoutConfig,
) -> Result<GoBridgeResponse, BridgeError> {
    process_go_bridge_request_with_options(request, BridgeRuntimeConfig::json_only(config))
}

pub fn process_go_bridge_request_with_options(
    request: GoBridgeRequest,
    config: BridgeRuntimeConfig,
) -> Result<GoBridgeResponse, BridgeError> {
    let bridge_graph = build_bridge_graph(request)?;
    let mut filtered = filter_bridge_graph_to_descendants(bridge_graph)?;
    if config.canonicalize_input {
        filtered = canonicalize_bridge_graph(filtered);
    }

    let artifacts = layout_radial_hierarchy_with_artifacts(&mut filtered.graph, config.layout)?;
    write_debug_svg_if_configured(&filtered.graph, &artifacts, &config)?;

    Ok(build_bridge_response(&filtered, &artifacts))
}

fn build_bridge_graph(request: GoBridgeRequest) -> Result<BridgeGraph, BridgeError> {
    let mut node_id_to_index = HashMap::with_capacity(request.nodes.len());
    let mut nodes = Vec::with_capacity(request.nodes.len());
    let mut node_ids = Vec::with_capacity(request.nodes.len());

    for node in request.nodes {
        if node_id_to_index.insert(node.node_id, nodes.len()).is_some() {
            return Err(BridgeError::DuplicateNodeId {
                node_id: node.node_id,
            });
        }
        nodes.push(Node {
            label: Some(node.iri),
            ..Node::default()
        });
        node_ids.push(node.node_id);
    }

    let mut seen_edge_indices = HashSet::with_capacity(request.edges.len());
    let mut edges = Vec::with_capacity(request.edges.len());
    let mut edge_indices = Vec::with_capacity(request.edges.len());
    for edge in request.edges {
        if !seen_edge_indices.insert(edge.edge_index) {
            return Err(BridgeError::DuplicateEdgeIndex {
                edge_index: edge.edge_index,
            });
        }

        let Some(&source) = node_id_to_index.get(&edge.parent_id) else {
            return Err(BridgeError::MissingNodeRef {
                edge_index: edge.edge_index,
                node_id: edge.parent_id,
            });
        };
        let Some(&target) = node_id_to_index.get(&edge.child_id) else {
            return Err(BridgeError::MissingNodeRef {
                edge_index: edge.edge_index,
                node_id: edge.child_id,
            });
        };

        edges.push(Edge::new(source, target));
        edge_indices.push(edge.edge_index);
    }

    Ok(BridgeGraph {
        root_iri: request.root_iri,
        graph: Graph::new(nodes, edges),
        node_ids,
        edge_indices,
    })
}

fn filter_bridge_graph_to_descendants(
    bridge_graph: BridgeGraph,
) -> Result<BridgeGraph, BridgeError> {
    let BridgeGraph {
        root_iri,
        graph,
        node_ids,
        edge_indices,
    } = bridge_graph;

    let Some(root_index) = graph
        .nodes
        .iter()
        .position(|node| node.label.as_deref() == Some(root_iri.as_str()))
    else {
        return Err(BridgeError::RootNotFound { root_iri });
    };

    let mut adjacency = vec![Vec::new(); graph.nodes.len()];
    for edge in &graph.edges {
        adjacency[edge.source].push(edge.target);
    }

    let mut visited = HashSet::from([root_index]);
    let mut queue = VecDeque::from([root_index]);
    while let Some(node) = queue.pop_front() {
        for &child in &adjacency[node] {
            if visited.insert(child) {
                queue.push_back(child);
            }
        }
    }

    if visited.len() <= 1 {
        return Err(BridgeError::NoDescendants { root_iri });
    }

    let mut reindex = HashMap::with_capacity(visited.len());
    let mut filtered_nodes = Vec::with_capacity(visited.len());
    let mut filtered_node_ids = Vec::with_capacity(visited.len());
    for (old_index, node) in graph.nodes.iter().enumerate() {
        if !visited.contains(&old_index) {
            continue;
        }
        let new_index = filtered_nodes.len();
        reindex.insert(old_index, new_index);
        filtered_nodes.push(node.clone());
        filtered_node_ids.push(node_ids[old_index]);
    }

    let mut filtered_edges = Vec::new();
    let mut filtered_edge_indices = Vec::new();
    for (old_edge_index, edge) in graph.edges.iter().enumerate() {
        if !visited.contains(&edge.source) || !visited.contains(&edge.target) {
            continue;
        }
        filtered_edges.push(Edge::new(reindex[&edge.source], reindex[&edge.target]));
        filtered_edge_indices.push(edge_indices[old_edge_index]);
    }

    Ok(BridgeGraph {
        root_iri,
        graph: Graph::new(filtered_nodes, filtered_edges),
        node_ids: filtered_node_ids,
        edge_indices: filtered_edge_indices,
    })
}

fn canonicalize_bridge_graph(bridge_graph: BridgeGraph) -> BridgeGraph {
    let BridgeGraph {
        root_iri,
        graph,
        node_ids,
        edge_indices,
    } = bridge_graph;

    let mut node_order = (0..graph.nodes.len()).collect::<Vec<_>>();
    node_order.sort_by(|left, right| {
        graph.nodes[*left]
            .label
            .as_deref()
            .unwrap_or("")
            .cmp(graph.nodes[*right].label.as_deref().unwrap_or(""))
            .then(node_ids[*left].cmp(&node_ids[*right]))
    });

    let mut reindex = vec![0usize; graph.nodes.len()];
    let mut nodes = Vec::with_capacity(graph.nodes.len());
    let mut canonical_node_ids = Vec::with_capacity(node_ids.len());
    for (new_index, old_index) in node_order.into_iter().enumerate() {
        reindex[old_index] = new_index;
        nodes.push(graph.nodes[old_index].clone());
        canonical_node_ids.push(node_ids[old_index]);
    }

    let mut edge_order = (0..graph.edges.len()).collect::<Vec<_>>();
    edge_order.sort_by(|left, right| {
        let left_edge = graph.edges[*left];
        let right_edge = graph.edges[*right];

        graph.nodes[left_edge.source]
            .label
            .as_deref()
            .unwrap_or("")
            .cmp(
                graph.nodes[right_edge.source]
                    .label
                    .as_deref()
                    .unwrap_or(""),
            )
            .then(
                graph.nodes[left_edge.target]
                    .label
                    .as_deref()
                    .unwrap_or("")
                    .cmp(
                        graph.nodes[right_edge.target]
                            .label
                            .as_deref()
                            .unwrap_or(""),
                    ),
            )
            .then(edge_indices[*left].cmp(&edge_indices[*right]))
    });

    let mut edges = Vec::with_capacity(graph.edges.len());
    let mut canonical_edge_indices = Vec::with_capacity(edge_indices.len());
    for old_edge_index in edge_order {
        let edge = graph.edges[old_edge_index];
        edges.push(Edge::new(reindex[edge.source], reindex[edge.target]));
        canonical_edge_indices.push(edge_indices[old_edge_index]);
    }

    BridgeGraph {
        root_iri,
        graph: Graph::new(nodes, edges),
        node_ids: canonical_node_ids,
        edge_indices: canonical_edge_indices,
    }
}

fn write_debug_svg_if_configured(
    graph: &Graph,
    artifacts: &LayoutArtifacts,
    config: &BridgeRuntimeConfig,
) -> Result<(), BridgeError> {
    let Some(path) = &config.svg_output_path else {
        return Ok(());
    };

    if let Some(parent) = path.parent() {
        create_dir_all(parent).map_err(|source| BridgeError::CreateOutputDir {
            path: parent.to_path_buf(),
            source,
        })?;
    }

    write_svg_path_with_options(path, graph, artifacts, config.layout, config.svg)?;
    Ok(())
}

fn build_bridge_response(graph: &BridgeGraph, artifacts: &LayoutArtifacts) -> GoBridgeResponse {
    let nodes = graph
        .graph
        .nodes
        .iter()
        .enumerate()
        .map(|(node_index, node)| GoBridgeRoutedNode {
            node_id: graph.node_ids[node_index],
            x: node.x,
            y: node.y,
            level: artifacts.node_levels[node_index],
        })
        .collect::<Vec<_>>();

    let route_segments = artifacts
        .edge_routes
        .iter()
        .map(|route| GoBridgeRouteSegment {
            edge_index: graph.edge_indices[route.original_edge_index],
            kind: route_kind_name(route.kind).to_owned(),
            points: route
                .points
                .iter()
                .map(|point| GoBridgePoint {
                    x: point.x,
                    y: point.y,
                })
                .collect(),
        })
        .collect::<Vec<_>>();

    GoBridgeResponse {
        nodes,
        route_segments,
    }
}

fn route_kind_name(kind: EdgeRouteKind) -> &'static str {
    match kind {
        EdgeRouteKind::Straight => "straight",
        EdgeRouteKind::Spiral => "spiral",
        EdgeRouteKind::IntraLevel => "intra_level",
    }
}

#[cfg(test)]
mod tests {
    use std::fs;
    use std::time::{SystemTime, UNIX_EPOCH};

    use super::*;
    use crate::{filter_graph_to_descendants, RingDistribution};

    fn node(node_id: u32, iri: &str) -> GoBridgeNode {
        GoBridgeNode {
            node_id,
            iri: iri.to_owned(),
        }
    }

    fn edge(edge_index: usize, parent_id: u32, child_id: u32) -> GoBridgeEdge {
        GoBridgeEdge {
            edge_index,
            parent_id,
            child_id,
            predicate_iri: None,
        }
    }

    fn runtime_config() -> BridgeRuntimeConfig {
        BridgeRuntimeConfig {
            layout: LayoutConfig {
                ring_distribution: RingDistribution::Adaptive,
                ..LayoutConfig::default()
            },
            svg: SvgConfig {
                shortest_edges: false,
                show_labels: false,
            },
            svg_output_path: None,
            canonicalize_input: true,
        }
    }

    fn sorted_nodes(mut nodes: Vec<GoBridgeRoutedNode>) -> Vec<(u32, usize, i64, i64)> {
        nodes.sort_by_key(|node| node.node_id);
        nodes
            .into_iter()
            .map(|node| {
                (
                    node.node_id,
                    node.level,
                    (node.x * 1_000_000.0).round() as i64,
                    (node.y * 1_000_000.0).round() as i64,
                )
            })
            .collect()
    }

    fn sorted_segments(
        mut segments: Vec<GoBridgeRouteSegment>,
    ) -> Vec<(usize, String, Vec<(i64, i64)>)> {
        segments.sort_by(|left, right| {
            left.edge_index
                .cmp(&right.edge_index)
                .then(left.kind.cmp(&right.kind))
                .then(left.points.len().cmp(&right.points.len()))
        });
        segments
            .into_iter()
            .map(|segment| {
                (
                    segment.edge_index,
                    segment.kind,
                    segment
                        .points
                        .into_iter()
                        .map(|point| {
                            (
                                (point.x * 1_000_000.0).round() as i64,
                                (point.y * 1_000_000.0).round() as i64,
                            )
                        })
                        .collect(),
                )
            })
            .collect()
    }

    #[test]
    fn filters_to_root_descendants_and_preserves_node_ids() {
        let response = process_go_bridge_request_with_options(
            GoBridgeRequest {
                root_iri: "root".to_owned(),
                nodes: vec![
                    node(10, "root"),
                    node(11, "child"),
                    node(12, "leaf"),
                    node(13, "other"),
                ],
                edges: vec![edge(0, 10, 11), edge(1, 11, 12), edge(2, 13, 12)],
            },
            runtime_config(),
        )
        .unwrap();

        let mut kept_ids = response
            .nodes
            .iter()
            .map(|node| node.node_id)
            .collect::<Vec<_>>();
        kept_ids.sort();
        assert_eq!(kept_ids, vec![10, 11, 12]);
        assert!(response
            .route_segments
            .iter()
            .all(|segment| segment.edge_index == 0 || segment.edge_index == 1));
    }

    #[test]
    fn returns_multiple_route_segments_for_long_edges_with_dummies() {
        let response = process_go_bridge_request_with_options(
            GoBridgeRequest {
                root_iri: "root".to_owned(),
                nodes: vec![node(1, "root"), node(2, "child"), node(3, "leaf")],
                edges: vec![edge(10, 1, 2), edge(11, 2, 3), edge(12, 1, 3)],
            },
            runtime_config(),
        )
        .unwrap();

        let long_edge_routes = response
            .route_segments
            .iter()
            .filter(|segment| segment.edge_index == 12)
            .count();
        assert!(long_edge_routes >= 2);
    }

    #[test]
    fn returns_error_when_root_is_missing() {
        let error = process_go_bridge_request_with_options(
            GoBridgeRequest {
                root_iri: "root".to_owned(),
                nodes: vec![node(1, "other")],
                edges: vec![],
            },
            runtime_config(),
        )
        .unwrap_err();

        assert!(matches!(
            error,
            BridgeError::RootNotFound { root_iri } if root_iri == "root"
        ));
    }

    #[test]
    fn returns_error_when_root_has_no_descendants() {
        let error = process_go_bridge_request_with_options(
            GoBridgeRequest {
                root_iri: "root".to_owned(),
                nodes: vec![node(1, "root"), node(2, "other")],
                edges: vec![edge(0, 2, 1)],
            },
            runtime_config(),
        )
        .unwrap_err();

        assert!(matches!(
            error,
            BridgeError::NoDescendants { root_iri } if root_iri == "root"
        ));
    }

    #[test]
    fn bridge_matches_direct_layout_for_same_graph_and_config() {
        let request = GoBridgeRequest {
            root_iri: "root".to_owned(),
            nodes: vec![
                node(5, "leaf"),
                node(1, "root"),
                node(4, "sibling"),
                node(2, "child"),
                node(3, "grandchild"),
            ],
            edges: vec![
                edge(12, 2, 3),
                edge(10, 1, 2),
                edge(11, 1, 4),
                edge(13, 1, 5),
            ],
        };
        let config = runtime_config();

        let response =
            process_go_bridge_request_with_options(request.clone(), config.clone()).unwrap();

        let node_index_by_id = request
            .nodes
            .iter()
            .enumerate()
            .map(|(index, node)| (node.node_id, index))
            .collect::<HashMap<_, _>>();
        let direct_edges = request
            .edges
            .iter()
            .map(|edge| {
                Edge::new(
                    *node_index_by_id.get(&edge.parent_id).unwrap(),
                    *node_index_by_id.get(&edge.child_id).unwrap(),
                )
            })
            .collect::<Vec<_>>();
        let direct_nodes = request
            .nodes
            .iter()
            .map(|node| Node {
                label: Some(node.iri.clone()),
                ..Node::default()
            })
            .collect::<Vec<_>>();
        let direct_graph = Graph::new(direct_nodes, direct_edges);
        let filtered_direct =
            filter_graph_to_descendants(&direct_graph, &request.root_iri).unwrap();

        let mut filtered =
            filter_bridge_graph_to_descendants(build_bridge_graph(request).unwrap()).unwrap();
        filtered = canonicalize_bridge_graph(filtered);
        let mut direct_expected_iris = filtered_direct
            .nodes
            .iter()
            .filter_map(|node| node.label.clone())
            .collect::<Vec<_>>();
        let mut actual_iris = filtered
            .graph
            .nodes
            .iter()
            .filter_map(|node| node.label.clone())
            .collect::<Vec<_>>();
        direct_expected_iris.sort();
        actual_iris.sort();
        assert_eq!(actual_iris, direct_expected_iris);

        let artifacts =
            layout_radial_hierarchy_with_artifacts(&mut filtered.graph, config.layout).unwrap();
        let expected = build_bridge_response(&filtered, &artifacts);

        assert_eq!(sorted_nodes(response.nodes), sorted_nodes(expected.nodes));
        assert_eq!(
            sorted_segments(response.route_segments),
            sorted_segments(expected.route_segments)
        );
    }

    #[test]
    fn canonicalization_makes_bridge_positions_independent_of_input_order() {
        let config = runtime_config();
        let request_a = GoBridgeRequest {
            root_iri: "root".to_owned(),
            nodes: vec![
                node(1, "root"),
                node(2, "child"),
                node(3, "leaf"),
                node(4, "sibling"),
            ],
            edges: vec![edge(0, 1, 2), edge(1, 2, 3), edge(2, 1, 4)],
        };
        let request_b = GoBridgeRequest {
            root_iri: "root".to_owned(),
            nodes: vec![
                node(4, "sibling"),
                node(3, "leaf"),
                node(2, "child"),
                node(1, "root"),
            ],
            edges: vec![edge(2, 1, 4), edge(1, 2, 3), edge(0, 1, 2)],
        };

        let response_a = process_go_bridge_request_with_options(request_a, config.clone()).unwrap();
        let response_b = process_go_bridge_request_with_options(request_b, config).unwrap();

        assert_eq!(
            sorted_nodes(response_a.nodes),
            sorted_nodes(response_b.nodes)
        );
        assert_eq!(
            sorted_segments(response_a.route_segments),
            sorted_segments(response_b.route_segments)
        );
    }

    #[test]
    fn writes_debug_svg_to_configured_output_path() {
        let unique = SystemTime::now()
            .duration_since(UNIX_EPOCH)
            .unwrap()
            .as_nanos();
        let dir = std::env::temp_dir().join(format!(
            "radial_sugiyama_bridge_svg_{}_{}",
            std::process::id(),
            unique
        ));
        let path = dir.join("layout.svg");
        let mut config = runtime_config();
        config.svg_output_path = Some(path.clone());

        let response = process_go_bridge_request_with_options(
            GoBridgeRequest {
                root_iri: "root".to_owned(),
                nodes: vec![node(1, "root"), node(2, "child"), node(3, "leaf")],
                edges: vec![edge(0, 1, 2), edge(1, 2, 3)],
            },
            config,
        )
        .unwrap();

        assert_eq!(response.nodes.len(), 3);
        let svg = fs::read_to_string(&path).unwrap();
        assert!(svg.contains("<svg"));
        assert!(svg.contains("<path"));

        let _ = fs::remove_file(&path);
        let _ = fs::remove_dir_all(&dir);
    }
}
