//! Graph serialization and persistence.
//!
//! Provides save/load functionality for property graphs.

use std::fs::File;
use std::io::{BufReader, BufWriter};
use std::path::Path;

use serde::{Deserialize, Serialize};

use super::types::{GraphEdge, GraphNode};
use super::storage::PropertyGraph;
use crate::error::{ExecutionError, ExecutionResult};

/// Serializable graph format (legacy — kept for backward compatibility).
#[derive(Debug, Serialize, Deserialize)]
pub struct SerializedGraph {
    pub nodes: Vec<GraphNode>,
    pub edges: Vec<SerializedEdge>,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct SerializedEdge {
    pub edge: GraphEdge,
    pub start_id: u64,
    pub end_id: u64,
}

/// Versioned graph dump format with metadata (preferred over SerializedGraph).
///
/// Contains enough metadata to reconstruct the graph on any backend, and to
/// identify the format version and originating backend for tooling.
#[derive(Debug, Serialize, Deserialize)]
pub struct GraphDump {
    /// Dump format version — bump when breaking changes are made to the format.
    pub format_version: u32,
    /// OCG crate version that wrote this dump.
    pub ocg_version: String,
    /// Which backend was used: "PropertyGraph", "NetworKitRust", "RustworkxCore", "Graphrs".
    pub backend: String,
    /// ISO 8601 creation timestamp (UTC).
    pub created_at: String,
    /// Graph nodes with all labels and properties.
    pub nodes: Vec<GraphNode>,
    /// Graph edges with all types, endpoints, and properties.
    pub edges: Vec<SerializedEdge>,
}

impl GraphDump {
    /// Create a new dump with auto-populated metadata fields.
    pub fn new(backend: &str, nodes: Vec<GraphNode>, edges: Vec<SerializedEdge>) -> Self {
        Self {
            format_version: 1,
            ocg_version: env!("CARGO_PKG_VERSION").to_string(),
            backend: backend.to_string(),
            created_at: chrono::Utc::now().to_rfc3339(),
            nodes,
            edges,
        }
    }
}

/// Serialize `nodes` and `edges` for the named `backend` and write to `path` as pretty JSON.
pub fn dump_to_file<P: AsRef<Path>>(
    backend: &str,
    nodes: Vec<GraphNode>,
    edges: Vec<SerializedEdge>,
    path: P,
) -> ExecutionResult<()> {
    let dump = GraphDump::new(backend, nodes, edges);
    let file = File::create(path)
        .map_err(|e| ExecutionError::Internal(format!("Failed to create file: {}", e)))?;
    let writer = BufWriter::new(file);
    serde_json::to_writer_pretty(writer, &dump)
        .map_err(|e| ExecutionError::Internal(format!("Failed to serialize graph dump: {}", e)))?;
    Ok(())
}

/// Load a `GraphDump` from a JSON file previously written by `dump_to_file`.
pub fn load_dump<P: AsRef<Path>>(path: P) -> ExecutionResult<GraphDump> {
    let file = File::open(path)
        .map_err(|e| ExecutionError::Internal(format!("Failed to open file: {}", e)))?;
    let reader = BufReader::new(file);
    let dump: GraphDump = serde_json::from_reader(reader)
        .map_err(|e| ExecutionError::Internal(format!("Failed to deserialize graph dump: {}", e)))?;
    Ok(dump)
}

impl PropertyGraph {
    /// Save the graph to a JSON file.
    pub fn save_to_file<P: AsRef<Path>>(&self, path: P) -> ExecutionResult<()> {
        let nodes: Vec<GraphNode> = self.all_nodes().cloned().collect();
        let edges: Vec<SerializedEdge> = self
            .all_edges()
            .map(|edge| {
                let (start_id, end_id) = self.get_edge_endpoints(edge.id).unwrap_or((0, 0));
                SerializedEdge {
                    edge: edge.clone(),
                    start_id,
                    end_id,
                }
            })
            .collect();

        let serialized = SerializedGraph { nodes, edges };

        let file = File::create(path)
            .map_err(|e| ExecutionError::Internal(format!("Failed to create file: {}", e)))?;

        let writer = BufWriter::new(file);
        serde_json::to_writer_pretty(writer, &serialized)
            .map_err(|e| ExecutionError::Internal(format!("Failed to serialize graph: {}", e)))?;

        Ok(())
    }

    /// Load a graph from a JSON file.
    pub fn load_from_file<P: AsRef<Path>>(path: P) -> ExecutionResult<Self> {
        let file = File::open(path)
            .map_err(|e| ExecutionError::Internal(format!("Failed to open file: {}", e)))?;

        let reader = BufReader::new(file);
        let serialized: SerializedGraph = serde_json::from_reader(reader)
            .map_err(|e| ExecutionError::Internal(format!("Failed to deserialize graph: {}", e)))?;

        let mut graph = PropertyGraph::new();

        // Restore nodes with original IDs
        for node in serialized.nodes {
            let created = graph.create_node(node.labels.iter().cloned(), node.properties.clone());
            assert_eq!(created.id, node.id, "Node ID mismatch during load");
        }

        // Restore edges
        for serialized_edge in serialized.edges {
            graph.create_relationship(
                serialized_edge.start_id,
                serialized_edge.end_id,
                serialized_edge.edge.rel_type.clone(),
                serialized_edge.edge.properties.clone(),
            )?;
        }

        Ok(graph)
    }

    // Binary serialization removed - bincode was unmaintained and not used in codebase
    // If needed in the future, can implement using:
    // - bincode 2.0 (requires Encode/Decode derives)
    // - postcard (smaller, more efficient)
    // - rmp-serde (MessagePack format)
    //
    // For now, JSON serialization via save_to_file/load_from_file is sufficient
}

#[cfg(test)]
mod tests {
    use super::*;
    use indexmap::IndexMap;
    use crate::graph::PropertyValue;

    #[test]
    fn test_save_load_json() {
        let mut graph = PropertyGraph::new();

        // Create test data
        let mut props = IndexMap::new();
        props.insert("name".to_string(), PropertyValue::String("Alice".to_string()));
        graph.create_node(vec!["Person"], props);

        let mut props = IndexMap::new();
        props.insert("name".to_string(), PropertyValue::String("Bob".to_string()));
        graph.create_node(vec!["Person"], props);

        graph.create_relationship(1, 2, "KNOWS", IndexMap::new()).unwrap();

        // Save
        let path = "/tmp/test_graph.json";
        graph.save_to_file(path).unwrap();

        // Load
        let loaded = PropertyGraph::load_from_file(path).unwrap();

        assert_eq!(loaded.node_count(), 2);
        assert_eq!(loaded.edge_count(), 1);

        std::fs::remove_file(path).ok();
    }
}
