//! Hash-based graph partitioning.
//!
//! This module implements a simple hash-based partitioning strategy that
//! distributes vertices across partitions using a hash function.

use crate::distributed::partition::metadata::PartitionMap;
use crate::distributed::partition::partitioner::{GraphPartitioner, PartitionConfig};
use crate::error::CypherResult;
use crate::graph::PropertyGraph;
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};

/// Hash-based partitioner.
///
/// This partitioner uses a hash function to assign vertices to partitions:
/// `partition_id = hash(vertex_id) % num_partitions`
///
/// **Pros:**
/// - Very fast (O(V + E))
/// - Deterministic
/// - Uniform distribution
///
/// **Cons:**
/// - Ignores graph structure (high edge cut)
/// - Not locality-aware
#[derive(Debug, Default)]
pub struct HashPartitioner;

impl HashPartitioner {
    /// Create a new hash partitioner.
    pub fn new() -> Self {
        Self
    }

    /// Hash a vertex ID to a partition.
    fn hash_to_partition(&self, vertex_id: u64, num_partitions: u32) -> u32 {
        let mut hasher = DefaultHasher::new();
        vertex_id.hash(&mut hasher);
        let hash = hasher.finish();
        (hash % num_partitions as u64) as u32
    }
}

impl GraphPartitioner for HashPartitioner {
    fn partition(&self, graph: &PropertyGraph, config: &PartitionConfig) -> CypherResult<PartitionMap> {
        let mut partition_map = PartitionMap::new(config.num_partitions);

        // Assign vertices to partitions using hash
        for node in graph.all_nodes() {
            let vertex_id = node.id;
            let partition_id = self.hash_to_partition(vertex_id, config.num_partitions);

            partition_map.assign_vertex(vertex_id, partition_id);

            // Track labels
            for label in &node.labels {
                partition_map.add_label(partition_id, label.clone());
            }
        }

        // Assign edges to partitions based on source vertex
        // We need to iterate over all nodes and their edges to get src/dst info
        for node in graph.all_nodes() {
            let src_id = node.id;
            for (from_id, to_id, edge) in graph.all_edges_for_node(src_id) {
                // Only process each edge once (from source perspective)
                if from_id != src_id {
                    continue;
                }

                let src_partition = partition_map.get_vertex_partition(from_id)
                    .expect("Source vertex not assigned to partition");

                partition_map.assign_edge(edge.id, src_partition);

                // Check if edge crosses partition boundaries
                let dst_partition = partition_map.get_vertex_partition(to_id)
                    .expect("Destination vertex not assigned to partition");

                if src_partition != dst_partition {
                    partition_map.mark_cross_partition_edge(src_partition);
                }

                // Track relationship type
                partition_map.add_relationship_type(src_partition, edge.rel_type.clone());
            }
        }

        Ok(partition_map)
    }

    fn strategy_name(&self) -> &str {
        "hash"
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::execute;

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

        // Create 100 nodes
        execute(&mut graph, "UNWIND range(1, 100) AS i CREATE (n:Node {id: i})").unwrap();

        // Create some edges
        execute(&mut graph, "
            MATCH (a:Node), (b:Node)
            WHERE a.id < b.id AND a.id <= 10
            CREATE (a)-[:CONNECTS]->(b)
        ").unwrap();

        let partitioner = HashPartitioner::new();
        let config = PartitionConfig::new(4);
        let partition_map = partitioner.partition(&graph, &config).unwrap();

        // Verify all nodes are assigned
        assert_eq!(partition_map.vertex_to_partition.len(), 100);

        // Verify partitions are reasonably balanced (within 30% of average)
        let metrics = partition_map.calculate_quality_metrics();
        println!("Hash partitioner metrics: {:?}", metrics);
        assert!(metrics.imbalance < 0.3, "Imbalance too high: {}", metrics.imbalance);

        // Verify all partitions have at least some nodes
        for metadata in &partition_map.partition_metadata {
            assert!(metadata.node_count > 0, "Partition {} is empty", metadata.partition_id);
        }
    }

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

        // Create a simple chain: 1 -> 2 -> 3 -> 4
        execute(&mut graph, "
            CREATE (n1:Node {id: 1})-[:NEXT]->(n2:Node {id: 2})
                  -[:NEXT]->(n3:Node {id: 3})-[:NEXT]->(n4:Node {id: 4})
        ").unwrap();

        let partitioner = HashPartitioner::new();
        let config = PartitionConfig::new(2);
        let partition_map = partitioner.partition(&graph, &config).unwrap();

        // Verify all edges are assigned
        assert_eq!(partition_map.edge_to_partition.len(), 3);

        // Calculate edge cut (hash partitioning typically has high edge cut)
        let metrics = partition_map.calculate_quality_metrics();
        println!("Edge cut ratio: {}", metrics.edge_cut_ratio);
    }

    #[test]
    fn test_hash_deterministic() {
        let mut graph = PropertyGraph::new();
        execute(&mut graph, "UNWIND range(1, 50) AS i CREATE (:Node {id: i})").unwrap();

        let partitioner = HashPartitioner::new();
        let config = PartitionConfig::new(3);

        // Partition twice
        let map1 = partitioner.partition(&graph, &config).unwrap();
        let map2 = partitioner.partition(&graph, &config).unwrap();

        // Verify deterministic assignment
        for (vertex_id, partition_id) in &map1.vertex_to_partition {
            assert_eq!(map2.get_vertex_partition(*vertex_id), Some(*partition_id),
                       "Hash partitioning is not deterministic");
        }
    }
}
