use std::str::FromStr;

use graphannis::{
    graph::AnnoKey,
    model::{AnnotationComponent, AnnotationComponentType},
    update::UpdateEvent,
};
use itertools::Itertools;
use serde::{Deserialize, Deserializer, Serialize, Serializer};

use crate::estarde::IntoInner;

pub fn deserialize<'de, D: Deserializer<'de>>(
    deserializer: D,
) -> Result<Vec<UpdateEvent>, D::Error> {
    let raw = Vec::<SerdeUE>::deserialize(deserializer)?;
    Ok(raw
        .into_iter()
        .flat_map(|entry| entry.into_inner())
        .collect_vec())
}

pub fn serialize<'a, S: Serializer, T: IntoIterator<Item = &'a UpdateEvent>>(
    value: T,
    serializer: S,
) -> Result<S::Ok, S::Error> {
    let update_vec = value.into_iter().flat_map(SerdeUE::try_from).collect_vec();
    update_vec.serialize(serializer)
}

impl TryFrom<&UpdateEvent> for SerdeUE {
    type Error = anyhow::Error;

    fn try_from(value: &UpdateEvent) -> Result<Self, Self::Error> {
        let sue = match value {
            UpdateEvent::AddNode {
                node_name,
                node_type,
            } => SerdeUE::Add(RawAdd::Node {
                nodes: vec![node_name.to_string()],
                node_type: node_type.to_string(),
            }),
            UpdateEvent::DeleteNode { node_name } => SerdeUE::RM(RawRemove::Nodes {
                nodes: vec![node_name.to_string()],
            }),
            UpdateEvent::AddNodeLabel {
                node_name,
                anno_ns,
                anno_name,
                anno_value,
            } => SerdeUE::Add(RawAdd::NodeLabels {
                nodes: vec![node_name.to_string()],
                anno: AnnoKey {
                    name: anno_name.to_string(),
                    ns: anno_ns.to_string(),
                },
                value: anno_value.to_string(),
            }),
            UpdateEvent::DeleteNodeLabel {
                node_name,
                anno_ns,
                anno_name,
            } => SerdeUE::RM(RawRemove::NodeLabels {
                nodes: vec![node_name.to_string()],
                annos: vec![AnnoKey {
                    ns: anno_ns.to_string(),
                    name: anno_name.to_string(),
                }],
            }),
            UpdateEvent::AddEdge {
                source_node,
                target_node,
                layer,
                component_type,
                component_name,
            } => SerdeUE::Add(RawAdd::Edges {
                edges: vec![EdgeConfig {
                    source: source_node.to_string(),
                    target: target_node.to_string(),
                }],
                component: AnnotationComponent::new(
                    AnnotationComponentType::from_str(component_type)?,
                    layer.to_string(),
                    component_name.to_string(),
                ),
            }),
            UpdateEvent::DeleteEdge {
                source_node,
                target_node,
                layer,
                component_type,
                component_name,
            } => SerdeUE::RM(RawRemove::Edges {
                edges: vec![EdgeConfig {
                    source: source_node.to_string(),
                    target: target_node.to_string(),
                }],
                component: AnnotationComponent::new(
                    AnnotationComponentType::from_str(component_type)?,
                    layer.to_string(),
                    component_name.to_string(),
                ),
            }),
            UpdateEvent::AddEdgeLabel {
                source_node,
                target_node,
                layer,
                component_type,
                component_name,
                anno_ns,
                anno_name,
                anno_value,
            } => SerdeUE::Add(RawAdd::EdgeLabels {
                edges: vec![EdgeConfig {
                    source: source_node.to_string(),
                    target: target_node.to_string(),
                }],
                component: AnnotationComponent::new(
                    AnnotationComponentType::from_str(component_type)?,
                    layer.to_string(),
                    component_name.to_string(),
                ),
                anno: AnnoKey {
                    name: anno_name.to_string(),
                    ns: anno_ns.to_string(),
                },
                value: anno_value.to_string(),
            }),
            UpdateEvent::DeleteEdgeLabel {
                source_node,
                target_node,
                layer,
                component_type,
                component_name,
                anno_ns,
                anno_name,
            } => SerdeUE::RM(RawRemove::EdgeLabels {
                edges: vec![EdgeConfig {
                    source: source_node.to_string(),
                    target: target_node.to_string(),
                }],
                component: AnnotationComponent::new(
                    AnnotationComponentType::from_str(component_type)?,
                    layer.to_string(),
                    component_name.to_string(),
                ),
                annos: vec![AnnoKey {
                    ns: anno_ns.to_string(),
                    name: anno_name.to_string(),
                }],
            }),
        };
        Ok(sue)
    }
}

impl IntoInner for SerdeUE {
    type I = Vec<UpdateEvent>;

    fn into_inner(self) -> Self::I {
        match self {
            SerdeUE::Add(raw_add) => raw_add.into_inner(),
            SerdeUE::RM(raw_remove) => raw_remove.into_inner(),
        }
    }
}

impl IntoInner for RawAdd {
    type I = Vec<UpdateEvent>;

    fn into_inner(self) -> Self::I {
        match self {
            RawAdd::Node {
                nodes: names,
                node_type,
            } => names
                .into_iter()
                .map(|name| UpdateEvent::AddNode {
                    node_name: name,
                    node_type: node_type.clone(),
                })
                .collect_vec(),
            RawAdd::NodeLabels {
                nodes: names,
                anno,
                value,
            } => names
                .into_iter()
                .map(|name| UpdateEvent::AddNodeLabel {
                    node_name: name,
                    anno_ns: anno.ns.to_string(),
                    anno_name: anno.name.to_string(),
                    anno_value: value.to_string(),
                })
                .collect_vec(),
            RawAdd::Edges { edges, component } => edges
                .into_iter()
                .map(|edge| UpdateEvent::AddEdge {
                    source_node: edge.source,
                    target_node: edge.target,
                    layer: component.layer.to_string(),
                    component_type: component.get_type().to_string(),
                    component_name: component.name.to_string(),
                })
                .collect_vec(),
            RawAdd::EdgeLabels {
                edges,
                component,
                anno,
                value,
            } => edges
                .into_iter()
                .map(|edge| UpdateEvent::AddEdgeLabel {
                    source_node: edge.source,
                    target_node: edge.target,
                    layer: component.layer.to_string(),
                    component_type: component.get_type().to_string(),
                    component_name: component.name.to_string(),
                    anno_ns: anno.ns.to_string(),
                    anno_name: anno.name.to_string(),
                    anno_value: value.to_string(),
                })
                .collect_vec(),
        }
    }
}

impl IntoInner for RawRemove {
    type I = Vec<UpdateEvent>;

    fn into_inner(self) -> Self::I {
        match self {
            RawRemove::Nodes { nodes } => nodes
                .into_iter()
                .map(|node| UpdateEvent::DeleteNode { node_name: node })
                .collect_vec(),
            RawRemove::NodeLabels { nodes, annos } => nodes
                .into_iter()
                .cartesian_product(annos)
                .map(|(node, anno)| UpdateEvent::DeleteNodeLabel {
                    node_name: node,
                    anno_ns: anno.ns.to_string(),
                    anno_name: anno.name.to_string(),
                })
                .collect_vec(),
            RawRemove::Edges { edges, component } => edges
                .into_iter()
                .map(|edge| UpdateEvent::DeleteEdge {
                    source_node: edge.source,
                    target_node: edge.target,
                    layer: component.layer.to_string(),
                    component_type: component.get_type().to_string(),
                    component_name: component.name.to_string(),
                })
                .collect_vec(),
            RawRemove::EdgeLabels {
                edges,
                component,
                annos,
            } => edges
                .into_iter()
                .cartesian_product(annos)
                .map(|(edge, anno)| UpdateEvent::DeleteEdgeLabel {
                    source_node: edge.source,
                    target_node: edge.target,
                    layer: component.layer.to_string(),
                    component_type: component.get_type().to_string(),
                    component_name: component.name.to_string(),
                    anno_ns: anno.ns.to_string(),
                    anno_name: anno.name.to_string(),
                })
                .collect_vec(),
        }
    }
}

#[derive(Deserialize, Serialize)]
#[serde(rename_all = "lowercase", tag = "do", deny_unknown_fields)]
enum SerdeUE {
    Add(RawAdd),
    RM(RawRemove),
}

#[derive(Deserialize, Serialize)]
#[serde(untagged, deny_unknown_fields)]
enum RawAdd {
    Node {
        nodes: Vec<String>,
        #[serde(alias = "type", default = "default_node_type")]
        node_type: String,
    },
    NodeLabels {
        nodes: Vec<String>,
        #[serde(with = "crate::estarde::anno_key")]
        anno: AnnoKey,
        value: String,
    },
    Edges {
        edges: Vec<EdgeConfig>,
        #[serde(with = "crate::estarde::annotation_component")]
        component: AnnotationComponent,
    },
    EdgeLabels {
        edges: Vec<EdgeConfig>,
        #[serde(with = "crate::estarde::annotation_component")]
        component: AnnotationComponent,
        #[serde(with = "crate::estarde::anno_key")]
        anno: AnnoKey,
        value: String,
    },
}

fn default_node_type() -> String {
    "node".to_string()
}

#[derive(Deserialize, Serialize)]
#[serde(untagged, deny_unknown_fields)]
enum RawRemove {
    Nodes {
        nodes: Vec<String>,
    },
    NodeLabels {
        nodes: Vec<String>,
        #[serde(with = "crate::estarde::anno_key::in_sequence")]
        annos: Vec<AnnoKey>,
    },
    Edges {
        edges: Vec<EdgeConfig>,
        #[serde(with = "crate::estarde::annotation_component")]
        component: AnnotationComponent,
    },
    EdgeLabels {
        edges: Vec<EdgeConfig>,
        #[serde(with = "crate::estarde::annotation_component")]
        component: AnnotationComponent,
        #[serde(with = "crate::estarde::anno_key::in_sequence")]
        annos: Vec<AnnoKey>,
    },
}

#[derive(Clone, Deserialize, Serialize)]
#[serde(deny_unknown_fields)]
struct EdgeConfig {
    source: String,
    target: String,
}
