use std::{cmp::Reverse, collections::BinaryHeap, sync::Mutex};

use vectorium::core::dataset::ScoredItemGeneric;
use vectorium::vector_encoder::VectorEncoder;
use vectorium::{Dataset, QueryEvaluator, VectorId};

/// Adds a candidate to the min and max heaps used during the construction and search processes in the HNSW algorithm.
///
/// This function manages the insertion of a given candidate into both the minimum and maximum heaps,
/// which are utilized during both the construction and search phases of the HNSW algorithm.
/// The function ensures that the heaps do not exceed the size defined by the `ef_parameter`,
/// which controls the number of elements maintained during these processes.
///
/// # Description
///
/// The function follows these steps:
/// 1. If the number of elements in the `max_heap` is less than `ef_parameter`, the candidate is added to both heaps.
/// 2. If the `max_heap` is full but the new candidate is closer (i.e., has a smaller distance) than the
///    farthest candidate in the `max_heap`, the candidate is added to both the heaps.
/// 3. If the `max_heap` exceeds the `ef_parameter` size after adding the new candidate, the farthest candidate
///    is removed from the `max_heap`.
///
/// # Parameters
/// - `min_heap`: A mutable reference to a `BinaryHeap` of `Reverse<ScoredItemGeneric<i32, usize>>` objects.
/// - `max_heap`: A mutable reference to a `BinaryHeap` of `ScoredItemGeneric` objects.
/// - `candidate`: The `ScoredItemGeneric` to be potentially added to the heaps.
/// - `ef_parameter`: The maximum number of elements that should be maintained in the heaps during the construction and search processes.
///
/// # Example
/// ```rust
/// use std::collections::BinaryHeap;
/// use std::cmp::Reverse;
/// use kannolo::hnsw_utils::add_neighbor_to_heaps;
/// use vectorium::core::dataset::ScoredItemGeneric;
///
/// let mut min_heap: BinaryHeap<Reverse<ScoredItemGeneric<i32, usize>>> = BinaryHeap::new();
/// let mut max_heap: BinaryHeap<ScoredItemGeneric<i32, usize>> = BinaryHeap::new();
/// let candidate = ScoredItemGeneric { distance: 5, vector: 1 }; // Example candidate
/// let ef_parameter = 10;
///
/// add_neighbor_to_heaps(&mut min_heap, &mut max_heap, candidate, ef_parameter);
/// ```
pub fn add_neighbor_to_heaps<D: Ord + Copy>(
    min_heap: &mut BinaryHeap<Reverse<ScoredItemGeneric<D, usize>>>,
    max_heap: &mut BinaryHeap<ScoredItemGeneric<D, usize>>,
    candidate: ScoredItemGeneric<D, usize>,
    ef_parameter: usize,
) {
    let should_add_node = if max_heap.len() < ef_parameter {
        true
    } else if let Some(top_node) = max_heap.peek() {
        candidate < *top_node
    } else {
        false
    };

    if should_add_node {
        min_heap.push(Reverse(candidate));
        max_heap.push(candidate);
    }

    if max_heap.len() > ef_parameter {
        max_heap.pop();
    }
}

pub fn add_neighbors_to_heaps<D: Ord + Copy>(
    min_heap: &mut BinaryHeap<Reverse<ScoredItemGeneric<D, usize>>>,
    max_heap: &mut BinaryHeap<ScoredItemGeneric<D, usize>>,
    distances: &[D],
    ids: &[usize],
    ef_parameter: usize,
) {
    for (distance, id) in distances.iter().zip(ids.iter()) {
        let should_add_node = if max_heap.len() < ef_parameter {
            true
        } else if let Some(top_node) = max_heap.peek() {
            ScoredItemGeneric {
                distance: *distance,
                vector: *id,
            } < *top_node
        } else {
            false
        };

        if should_add_node {
            let candidate = ScoredItemGeneric {
                distance: *distance,
                vector: *id,
            };
            min_heap.push(Reverse(candidate));
            max_heap.push(candidate);
        }

        if max_heap.len() > ef_parameter {
            max_heap.pop();
        }
    }
}
/// Determines the closest neighbor from a list by comparing distances to a query vector.
///
/// This function iterates through a list of neighbor indices and calculates their distances to the query vector using the
/// provided `QueryEvaluator`. It updates the closest neighbor and its distance if a closer neighbor is found.
///
/// # Parameters
/// - `query_evaluator`: A reference to an object implementing the `QueryEvaluator` trait. This object provides the method to compute the distance between the query vector and each neighbor.
/// - `neighbors`: A slice of `usize` representing the indices of the neighbors to be evaluated.
/// - `nearest_vec`: A mutable reference to a `usize` variable that will be updated to the index of the closest neighbor found.
/// - `dis_nearest_vec`: A mutable reference to a distance variable that will be updated to the distance of the closest neighbor found.
///
/// # Description
/// The function iterates over each neighbor in the `neighbors` slice. For each neighbor, it computes the
/// distance to the query vector using the
/// `query_evaluator`. If the computed distance is less than the current closest distance (`dis_nearest_vec`),
///  the function updates
/// `nearest_vec` to the current neighbor and `dis_nearest_vec` to the new shortest distance.
///
#[inline]
pub fn compute_closest_from_neighbors<'e, D>(
    dataset: &D,
    query_evaluator: &<D::Encoder as VectorEncoder>::Evaluator<'e>,
    neighbors: &[usize],
    nearest_vec: &mut usize,
    dis_nearest_vec: &mut <D::Encoder as VectorEncoder>::Distance,
) where
    D: Dataset,
    <D::Encoder as VectorEncoder>::Distance: Ord + Copy,
{
    for &neighbor in neighbors {
        let distance_neighbor = query_evaluator.compute_distance(dataset.get(neighbor as VectorId));

        if distance_neighbor < *dis_nearest_vec {
            *nearest_vec = neighbor;
            *dis_nearest_vec = distance_neighbor;
        }
    }
}

/// Inserts the k closest results for a single query into a shared top-k vector containing the
/// closest results for multiple queries.
///
/// This function updates a shared vector of top-k results, which holds the closest vectors
/// for multiple queries. It inserts the top-k results for a single query into the appropriate
/// segment of this shared vector, ensuring that the `topk_query` vector has the correct length.
///
/// # Parameters
/// - `topk`: A `Mutex<Vec<(f32, usize)>>` representing a shared vector of top-k results
///   for multiple queries. Each element is a tuple `(distance, index)`.
/// - `query_topk`: A vector of tuples `(f32, usize)` containing the top-k results for a
///   single query. Each tuple represents a closest vector.
/// - `index`: The index of the query whose results are being inserted into the shared vector.
///   This `index` determines the segment in the `topk` vector where the `query_topk` results will be placed.
/// - `k`: The number of top results to manage per query. This defines both the size of `query_topk`
///   and the length of the segment in `topk` that will be replaced.
///
/// # Description
/// - If `query_top_k` does not have a length of `k`, it is resized to `k`, with any additional elements
///   filled with default values.
/// - The function ensures that `topk_query` is resized correctly to the specified length `k`. It will panic
///   if the length is not as expected.
/// - The function locks the `topk` vector and replaces the segment from `index * k` to `(index + 1) * k` with
///   the `query_topk` results.
///
/// # Example
/// ```rust
/// use kannolo::hnsw_utils::insert_into_topk;
/// use vectorium::core::dataset::ScoredItemGeneric;
/// use std::sync::Mutex;
///
/// fn si(distance: i32, vector: usize) -> ScoredItemGeneric<i32, usize> {
///     ScoredItemGeneric { distance, vector }
/// }
///
/// // The vector holds results for two queries, each with 3 closest vectors (k=3).
/// let topk = Mutex::new(vec![
///     si(10, 1), si(20, 2), si(30, 3),
///     si(i32::MAX, usize::MAX), si(i32::MAX, usize::MAX), si(i32::MAX, usize::MAX),
/// ]);
///
/// let index = 1;
/// let k = 3;
///
/// //top-k closest vectors for query with index 1.
/// let query_topk = vec![si(5, 5), si(15, 6), si(25, 7)];
///
/// insert_into_topk(&topk, query_topk, index, k);
///
/// let topk_locked = topk.lock().unwrap();
/// let expected = vec![si(10, 1), si(20, 2), si(30, 3), si(5, 5), si(15, 6), si(25, 7)];
/// assert_eq!(&*topk_locked, &expected);
/// ```
#[inline]
pub fn insert_into_topk<D: Ord + Copy + Default>(
    topk: &Mutex<Vec<ScoredItemGeneric<D, usize>>>,
    mut query_topk: Vec<ScoredItemGeneric<D, usize>>,
    index: usize,
    k: usize,
) {
    if query_topk.len() != k {
        query_topk.resize_with(k, || ScoredItemGeneric {
            distance: D::default(),
            vector: usize::MAX,
        });
    }
    assert_eq!(
        query_topk.len(),
        k,
        "The length of the result vec has to be equal to k"
    );

    let start_index = index * k;
    topk.lock()
        .unwrap()
        .splice(start_index..start_index + k, query_topk);
}

/// Converts a `BinaryHeap<Node>` from a max-heap to a min-heap.
///
/// # Description
/// This function transforms a binary heap that is organized as a max-heap into a binary heap organized as a min-heap.
/// The conversion is done by first draining all elements from the max-heap, then wrapping each element in a `Reverse`
/// wrapper to reverse the heap order, and finally creating a new min-heap from these reversed elements.
///
/// # Parameters
/// - `max_heap`: A mutable reference to a `BinaryHeap<Node>` that is currently organized as a max-heap.
///
/// # Returns
/// - Returns a new `BinaryHeap<Reverse<Node>>` that is organized as a min-heap.
/// # Example
/// ```rust
/// use std::collections::BinaryHeap;
/// use std::cmp::Reverse;
/// use kannolo::hnsw_utils::from_max_heap_to_min_heap;
/// use vectorium::core::dataset::ScoredItemGeneric;
///
/// let mut max_heap = BinaryHeap::new();
/// max_heap.push(ScoredItemGeneric { distance: 10, vector: 1 });
/// max_heap.push(ScoredItemGeneric { distance: 20, vector: 2 });
/// max_heap.push(ScoredItemGeneric { distance: 15, vector: 3 });
///
/// let min_heap = from_max_heap_to_min_heap(&mut max_heap);
/// assert_eq!(min_heap.len(), 3);
/// ```
pub fn from_max_heap_to_min_heap<D: Ord + Copy>(
    max_heap: &mut BinaryHeap<ScoredItemGeneric<D, usize>>,
) -> BinaryHeap<Reverse<ScoredItemGeneric<D, usize>>> {
    let vec: Vec<_> = max_heap.drain().collect();
    BinaryHeap::from(vec.into_iter().map(Reverse).collect::<Vec<_>>())
}

#[cfg(test)]
mod tests_from_max_heap_to_min_heap {
    use super::*;

    /// Tests the conversion of a max-heap to a min-heap with a standard set of elements.
    ///
    /// This test initializes a max-heap with a set of `Node` elements and then converts it to a min-heap.
    /// The result is checked to ensure that the elements are ordered correctly in the min-heap.
    #[test]
    fn test_from_max_heap_to_min_heap() {
        let mut max_heap: BinaryHeap<ScoredItemGeneric<i32, usize>> = BinaryHeap::new();
        max_heap.push(ScoredItemGeneric {
            distance: 32,
            vector: 10,
        });
        max_heap.push(ScoredItemGeneric {
            distance: 22,
            vector: 8,
        });
        max_heap.push(ScoredItemGeneric {
            distance: 62,
            vector: 12,
        });
        max_heap.push(ScoredItemGeneric {
            distance: 72,
            vector: 2,
        });
        max_heap.push(ScoredItemGeneric {
            distance: 322,
            vector: 4,
        });
        max_heap.push(ScoredItemGeneric {
            distance: 42,
            vector: 14,
        });
        max_heap.push(ScoredItemGeneric {
            distance: 12,
            vector: 6,
        });
        max_heap.push(ScoredItemGeneric {
            distance: 72,
            vector: 6,
        });

        let mut min_heap = from_max_heap_to_min_heap(&mut max_heap);
        let mut min_heap_vec: Vec<ScoredItemGeneric<i32, usize>> = Vec::new();
        while let Some(node) = min_heap.pop() {
            min_heap_vec.push(node.0);
        }

        let expected_vec: Vec<ScoredItemGeneric<i32, usize>> = vec![
            ScoredItemGeneric {
                distance: 12,
                vector: 6,
            },
            ScoredItemGeneric {
                distance: 22,
                vector: 8,
            },
            ScoredItemGeneric {
                distance: 32,
                vector: 10,
            },
            ScoredItemGeneric {
                distance: 42,
                vector: 14,
            },
            ScoredItemGeneric {
                distance: 62,
                vector: 12,
            },
            ScoredItemGeneric {
                distance: 72,
                vector: 2,
            },
            ScoredItemGeneric {
                distance: 72,
                vector: 6,
            },
            ScoredItemGeneric {
                distance: 322,
                vector: 4,
            },
        ];
        assert_eq!(expected_vec, min_heap_vec);
    }

    /// Tests the conversion of a max-heap to a min-heap with a large dataset.
    ///
    /// This test evaluates the performance and correctness of the conversion function when handling a large number of elements.
    /// It ensures that all elements are correctly converted from a max-heap to a min-heap and that the ordering is preserved.
    #[test]
    fn test_from_max_heap_to_min_heap_with_large_data() {
        let n = 100000;
        let mut max_heap: BinaryHeap<ScoredItemGeneric<i32, usize>> = BinaryHeap::new();

        for i in 0..n {
            max_heap.push(ScoredItemGeneric {
                distance: i as i32,
                vector: i,
            });
        }

        let mut min_heap = from_max_heap_to_min_heap(&mut max_heap);
        let mut min_heap_vec: Vec<ScoredItemGeneric<i32, usize>> = Vec::new();
        while let Some(node) = min_heap.pop() {
            min_heap_vec.push(node.0);
        }

        let mut expected_vec: Vec<ScoredItemGeneric<i32, usize>> = Vec::new();
        for i in 0..n {
            expected_vec.push(ScoredItemGeneric {
                distance: i as i32,
                vector: i,
            });
        }

        assert_eq!(expected_vec, min_heap_vec);
    }

    /// Tests the conversion of an empty max-heap to a min-heap.
    ///
    /// This test ensures that the function correctly handles the case where the max-heap is empty.
    /// The resulting min-heap should also be empty.
    #[test]
    fn test_from_max_heap_to_min_heap_empty() {
        let mut max_heap: BinaryHeap<ScoredItemGeneric<i32, usize>> = BinaryHeap::new();
        let min_heap = from_max_heap_to_min_heap(&mut max_heap);
        assert!(min_heap.is_empty());
    }

    /// Tests the conversion of a max-heap to a min-heap with a single element.
    ///
    /// This test verifies that the function correctly converts a max-heap containing only one element to a min-heap,
    /// ensuring that the single element is correctly handled.
    #[test]
    fn test_from_max_heap_to_min_heap_single_element() {
        let mut max_heap: BinaryHeap<ScoredItemGeneric<i32, usize>> = BinaryHeap::new();
        max_heap.push(ScoredItemGeneric {
            distance: 42,
            vector: 1,
        });

        let mut min_heap = from_max_heap_to_min_heap(&mut max_heap);
        let node = min_heap.pop().unwrap().0;

        assert_eq!(
            node,
            ScoredItemGeneric {
                distance: 42,
                vector: 1
            }
        );
        assert!(min_heap.is_empty());
    }

    /// Tests the conversion of a max-heap to a min-heap where all elements are identical.
    ///
    /// This test ensures that the function correctly handles the case where all elements in the heap have the same value,
    /// and verifies that the min-heap retains the correct number of elements.
    #[test]
    fn test_from_max_heap_to_min_heap_all_elements_same() {
        let mut max_heap: BinaryHeap<ScoredItemGeneric<i32, usize>> = BinaryHeap::new();
        for _ in 0..10 {
            max_heap.push(ScoredItemGeneric {
                distance: 5,
                vector: 100,
            });
        }

        let mut min_heap = from_max_heap_to_min_heap(&mut max_heap);
        let mut min_heap_vec: Vec<ScoredItemGeneric<i32, usize>> = Vec::new();
        while let Some(Reverse(node)) = min_heap.pop() {
            min_heap_vec.push(node);
        }

        let expected_vec: Vec<ScoredItemGeneric<i32, usize>> = vec![
            ScoredItemGeneric {
                distance: 5,
                vector: 100
            };
            10
        ];
        assert_eq!(expected_vec, min_heap_vec);
    }

    /// Tests the conversion of a max-heap to a min-heap with negative values.
    ///
    /// This test ensures that the function correctly handles and orders negative values
    /// when converting a max-heap to a min-heap.
    #[test]
    fn test_from_max_heap_to_min_heap_with_negative_values() {
        let mut max_heap: BinaryHeap<ScoredItemGeneric<i32, usize>> = BinaryHeap::new();
        max_heap.push(ScoredItemGeneric {
            distance: -1,
            vector: 1,
        });
        max_heap.push(ScoredItemGeneric {
            distance: -2,
            vector: 2,
        });
        max_heap.push(ScoredItemGeneric {
            distance: -3,
            vector: 3,
        });

        let mut min_heap = from_max_heap_to_min_heap(&mut max_heap);
        let mut min_heap_vec: Vec<ScoredItemGeneric<i32, usize>> = Vec::new();
        while let Some(Reverse(node)) = min_heap.pop() {
            min_heap_vec.push(node);
        }

        let expected_vec: Vec<ScoredItemGeneric<i32, usize>> = vec![
            ScoredItemGeneric {
                distance: -3,
                vector: 3,
            },
            ScoredItemGeneric {
                distance: -2,
                vector: 2,
            },
            ScoredItemGeneric {
                distance: -1,
                vector: 1,
            },
        ];
        assert_eq!(expected_vec, min_heap_vec);
    }

    /// Tests the conversion of a max-heap to a min-heap with mixed values.
    ///
    /// This test verifies the function's correctness when handling a max-heap containing a mix of positive, negative,
    /// and zero values. The resulting min-heap should correctly reflect the min-heap order.
    #[test]
    fn test_from_max_heap_to_min_heap_with_mixed_values() {
        let mut max_heap: BinaryHeap<ScoredItemGeneric<i32, usize>> = BinaryHeap::new();
        max_heap.push(ScoredItemGeneric {
            distance: 0,
            vector: 0,
        });
        max_heap.push(ScoredItemGeneric {
            distance: -1,
            vector: 1,
        });
        max_heap.push(ScoredItemGeneric {
            distance: 2,
            vector: 2,
        });
        max_heap.push(ScoredItemGeneric {
            distance: -2,
            vector: 3,
        });
        max_heap.push(ScoredItemGeneric {
            distance: 1,
            vector: 4,
        });

        let mut min_heap = from_max_heap_to_min_heap(&mut max_heap);
        let mut min_heap_vec: Vec<ScoredItemGeneric<i32, usize>> = Vec::new();
        while let Some(Reverse(node)) = min_heap.pop() {
            min_heap_vec.push(node);
        }

        let expected_vec: Vec<ScoredItemGeneric<i32, usize>> = vec![
            ScoredItemGeneric {
                distance: -2,
                vector: 3,
            },
            ScoredItemGeneric {
                distance: -1,
                vector: 1,
            },
            ScoredItemGeneric {
                distance: 0,
                vector: 0,
            },
            ScoredItemGeneric {
                distance: 1,
                vector: 4,
            },
            ScoredItemGeneric {
                distance: 2,
                vector: 2,
            },
        ];
        assert_eq!(expected_vec, min_heap_vec);
    }
}

#[cfg(test)]
mod tests_insert_into_topk {
    use std::{
        panic,
        sync::{Arc, Mutex},
    };

    use crate::hnsw_utils::insert_into_topk;
    use vectorium::core::dataset::ScoredItemGeneric;

    fn si(distance: i32, vector: usize) -> ScoredItemGeneric<i32, usize> {
        ScoredItemGeneric { distance, vector }
    }

    const DEFAULT: ScoredItemGeneric<i32, usize> = ScoredItemGeneric {
        distance: 0,
        vector: usize::MAX,
    };

    #[test]
    fn test_exact_size_query_start_index() {
        let topk = Arc::new(Mutex::new(vec![si(10, 1), si(20, 2), si(30, 3)]));
        let topk_query = vec![si(5, 5), si(15, 6), si(25, 7)];
        let index = 0;
        let k = 3;

        insert_into_topk(&topk, topk_query, index, k);

        let topk_locked = topk.lock().unwrap();
        assert_eq!(&*topk_locked, &[si(5, 5), si(15, 6), si(25, 7)]);
    }

    #[test]
    fn test_smaller_query_start_index() {
        let topk = Arc::new(Mutex::new(vec![si(10, 1), si(20, 2), si(30, 3)]));
        let topk_query = vec![si(5, 5)];
        let index = 0;
        let k = 3;

        insert_into_topk(&topk, topk_query, index, k);

        let topk_locked = topk.lock().unwrap();
        assert_eq!(&*topk_locked, &[si(5, 5), DEFAULT, DEFAULT]);
    }

    #[test]
    fn test_larger_query_start_index() {
        let topk = Arc::new(Mutex::new(vec![
            si(10, 1),
            si(20, 2),
            si(30, 3),
            si(60, 4),
            si(80, 5),
            si(90, 6),
        ]));
        let topk_query = vec![si(5, 5), si(15, 6), si(25, 7), si(35, 8)];
        let index = 0;
        let k = 3;

        insert_into_topk(&topk, topk_query, index, k);

        let topk_locked = topk.lock().unwrap();
        assert_eq!(
            &*topk_locked,
            &[
                si(5, 5),
                si(15, 6),
                si(25, 7),
                si(60, 4),
                si(80, 5),
                si(90, 6)
            ]
        );
    }

    #[test]
    fn test_empty_query_start_index() {
        let topk = Arc::new(Mutex::new(vec![si(10, 1), si(20, 2), si(30, 3)]));
        let topk_query: Vec<ScoredItemGeneric<i32, usize>> = vec![];
        let index = 0;
        let k = 3;

        insert_into_topk(&topk, topk_query, index, k);

        let topk_locked = topk.lock().unwrap();
        assert_eq!(&*topk_locked, &[DEFAULT, DEFAULT, DEFAULT]);
    }

    #[test]
    fn test_exact_size_query_middle_index() {
        let topk = Arc::new(Mutex::new(vec![
            ScoredItemGeneric {
                distance: i32::MAX,
                vector: usize::MAX
            };
            100
        ]));
        let topk_query = vec![si(5, 5), si(15, 6), si(25, 7), si(27, 1), si(31, 3)];
        let index = 10;
        let k = 5;

        insert_into_topk(&topk, topk_query.clone(), index, k);

        let start_index = index * k;

        for i in 0..k {
            assert_eq!(topk.lock().unwrap()[i + start_index], topk_query[i]);
        }
    }

    #[test]
    fn test_smaller_query_middle_index() {
        let topk = Arc::new(Mutex::new(vec![
            ScoredItemGeneric {
                distance: i32::MAX,
                vector: usize::MAX
            };
            1000
        ]));
        let topk_query = vec![si(5, 5), si(15, 6), si(25, 7)];
        let index = 20;
        let k = 5;

        insert_into_topk(&topk, topk_query, index, k);

        let expected = [si(5, 5), si(15, 6), si(25, 7), DEFAULT, DEFAULT];

        let start_index = index * k;

        for i in 0..k {
            assert_eq!(topk.lock().unwrap()[i + start_index], expected[i]);
        }
    }

    #[test]
    fn test_bigger_query_middle_index() {
        let topk = Arc::new(Mutex::new(vec![
            ScoredItemGeneric {
                distance: i32::MAX,
                vector: usize::MAX
            };
            200
        ]));
        let topk_query = vec![
            si(5, 5),
            si(15, 6),
            si(25, 7),
            si(45, 3),
            si(59, 1),
            si(125, 72),
        ];
        let index = 20;
        let k = 5;

        insert_into_topk(&topk, topk_query, index, k);

        let expected = [si(5, 5), si(15, 6), si(25, 7), si(45, 3), si(59, 1)];

        let start_index = index * k;

        for i in 0..k {
            assert_eq!(topk.lock().unwrap()[i + start_index], expected[i]);
        }
    }

    #[test]
    fn test_exact_size_query_last_index() {
        let topk = Arc::new(Mutex::new(vec![
            ScoredItemGeneric {
                distance: i32::MAX,
                vector: usize::MAX
            };
            200
        ]));
        let topk_query = vec![si(5, 5), si(15, 6), si(25, 7), si(45, 3), si(59, 1)];
        let index = 39;
        let k = 5;

        insert_into_topk(&topk, topk_query.clone(), index, k);

        let start_index = index * k;

        for i in 0..k {
            assert_eq!(topk.lock().unwrap()[i + start_index], topk_query[i]);
        }
    }

    #[test]
    fn test_smaller_query_last_index() {
        let topk = Arc::new(Mutex::new(vec![
            ScoredItemGeneric {
                distance: i32::MAX,
                vector: usize::MAX
            };
            200
        ]));
        let topk_query = vec![si(5, 5), si(15, 6)];
        let index = 39;
        let k = 5;

        insert_into_topk(&topk, topk_query, index, k);

        let expected = [si(5, 5), si(15, 6), DEFAULT, DEFAULT, DEFAULT];

        let start_index = index * k;

        for i in 0..k {
            assert_eq!(topk.lock().unwrap()[i + start_index], expected[i]);
        }
    }

    #[test]
    fn test_bigger_query_last_index() {
        let topk = Arc::new(Mutex::new(vec![
            ScoredItemGeneric {
                distance: i32::MAX,
                vector: usize::MAX
            };
            200
        ]));
        let topk_query = vec![
            si(5, 5),
            si(15, 6),
            si(25, 7),
            si(45, 3),
            si(59, 1),
            si(125, 72),
        ];
        let index = 39;
        let k = 5;

        insert_into_topk(&topk, topk_query.clone(), index, k);

        let start_index = index * k;

        for i in 0..k {
            assert_eq!(topk.lock().unwrap()[i + start_index], topk_query[i]);
        }
    }

    #[test]
    fn test_index_out_of_range() {
        let topk = Arc::new(Mutex::new(vec![
            ScoredItemGeneric {
                distance: i32::MAX,
                vector: usize::MAX
            };
            200
        ]));
        let topk_query = vec![
            si(5, 5),
            si(15, 6),
            si(25, 7),
            si(45, 3),
            si(59, 1),
            si(125, 72),
        ];
        let index = 40; // This index is out of range
        let k = 5;

        let topk_initial = topk.lock().unwrap().clone();

        let result = panic::catch_unwind(|| {
            insert_into_topk(&topk, topk_query.clone(), index, k);
        });

        // Ensure the function panicked
        assert!(result.is_err(), "Expected panic, but code did not panic");

        let topk_after = topk.lock().unwrap_or_else(|poisoned| poisoned.into_inner());

        assert_eq!(
            *topk_after, topk_initial,
            "Topk should remain unchanged after panic"
        );
    }
}

#[cfg(test)]
mod tests_add_neighbors_to_heaps {
    use super::*;
    use std::collections::BinaryHeap;

    /// Tests the `add_neighbor_to_heaps` function when both heaps are empty.
    ///
    /// This test initializes empty `min_heap` and `max_heap`, and adds a single `Node` with a
    /// distance of 10.0 and an ID of 1.
    /// The `ef_parameter` is set to 3, which allows for up to 3 nodes in the heaps.
    /// The expected result is that both heaps will contain the new node, and the sizes of
    /// both heaps should be 1.

    #[test]
    fn test_add_to_empty_heaps() {
        let mut min_heap: BinaryHeap<Reverse<ScoredItemGeneric<i32, usize>>> = BinaryHeap::new();
        let mut max_heap: BinaryHeap<ScoredItemGeneric<i32, usize>> = BinaryHeap::new();
        let candidate = ScoredItemGeneric {
            distance: 10,
            vector: 1,
        };
        let ef_parameter = 3;

        add_neighbor_to_heaps(&mut min_heap, &mut max_heap, candidate, ef_parameter);

        assert_eq!(min_heap.len(), 1);
        assert_eq!(max_heap.len(), 1);
        assert_eq!(min_heap.peek(), Some(&Reverse(candidate)));
        assert_eq!(max_heap.peek(), Some(&candidate));
    }

    /// Tests the `add_neighbor_to_heaps` function when the number of nodes is within the `ef_parameter` limit.
    ///
    /// This test initializes empty `min_heap` and `max_heap`, and adds three nodes.
    /// The `ef_parameter` is set to 3, allowing all nodes to be added to both heaps. The expected result
    /// is that both heaps will have 3 elements, with the top of the `max_heap` containing the largest
    /// distance, and the top of the `min_heap` containing the smallest distance.
    #[test]
    fn test_add_within_ef_parameter_limit() {
        let mut min_heap: BinaryHeap<Reverse<ScoredItemGeneric<i32, usize>>> = BinaryHeap::new();
        let mut max_heap: BinaryHeap<ScoredItemGeneric<i32, usize>> = BinaryHeap::new();
        let ef_parameter = 3;

        let nodes = [
            ScoredItemGeneric {
                distance: 10,
                vector: 1,
            },
            ScoredItemGeneric {
                distance: 5,
                vector: 2,
            },
            ScoredItemGeneric {
                distance: 7,
                vector: 3,
            },
        ];

        for node in nodes.iter().cloned() {
            add_neighbor_to_heaps(&mut min_heap, &mut max_heap, node, ef_parameter);
        }

        assert_eq!(min_heap.len(), 3);
        assert_eq!(max_heap.len(), 3);
        assert_eq!(max_heap.peek().unwrap().distance, 10);
        assert_eq!(min_heap.peek().unwrap().0.distance, 5);
    }

    /// Tests the `add_neighbor_to_heaps` function when the number of nodes exceeds the `ef_parameter` limit.
    ///
    /// This test initializes empty `min_heap` and `max_heap`, and adds four nodes with distances 10.0, 5.0, 7.0, and 3.0.
    /// The `ef_parameter` is set to 3, so the `max_heap` should only hold the 3 closest nodes, with the largest of those
    /// distances at the top. The `min_heap` should contain all 4 nodes, with the smallest distance at the top.
    /// The expected result is that the top of the `max_heap` will contain the distance 7.0, and the top of the `min_heap`
    /// will contain the distance 3.0.
    #[test]
    fn test_add_beyond_ef_parameter_limit() {
        let mut min_heap: BinaryHeap<Reverse<ScoredItemGeneric<i32, usize>>> = BinaryHeap::new();
        let mut max_heap: BinaryHeap<ScoredItemGeneric<i32, usize>> = BinaryHeap::new();
        let ef_parameter = 3;

        let nodes = [
            ScoredItemGeneric {
                distance: 10,
                vector: 1,
            },
            ScoredItemGeneric {
                distance: 5,
                vector: 2,
            },
            ScoredItemGeneric {
                distance: 7,
                vector: 3,
            },
            ScoredItemGeneric {
                distance: 3,
                vector: 4,
            },
        ];

        for node in nodes.iter().cloned() {
            add_neighbor_to_heaps(&mut min_heap, &mut max_heap, node, ef_parameter);
        }

        assert_eq!(min_heap.len(), 4);
        assert_eq!(max_heap.len(), 3);
        assert_eq!(max_heap.peek().unwrap().distance, 7);
        assert_eq!(min_heap.peek().unwrap().0.distance, 3);
    }

    /// Tests the `add_neighbor_to_heaps` function when adding a node with a higher distance than the current maximum.
    ///
    /// This test initializes empty `min_heap` and `max_heap`, and adds two nodes with distances 5.0 and 3.0.
    /// The `ef_parameter` is set to 2, allowing only 2 nodes to be included in the heaps. Then,
    /// a new node with a distance of 8.0 is added. The expected result is that the heaps remain unchanged,
    /// as the new node's distance is greater than the current maximum at the top of the `max_heap`, which is 5.0.
    #[test]
    fn test_add_node_with_higher_distance_than_current_max() {
        let mut min_heap: BinaryHeap<Reverse<ScoredItemGeneric<i32, usize>>> = BinaryHeap::new();
        let mut max_heap: BinaryHeap<ScoredItemGeneric<i32, usize>> = BinaryHeap::new();
        let ef_parameter = 2;

        let nodes = [
            ScoredItemGeneric {
                distance: 5,
                vector: 1,
            },
            ScoredItemGeneric {
                distance: 3,
                vector: 2,
            },
        ];

        for node in nodes.iter().cloned() {
            add_neighbor_to_heaps(&mut min_heap, &mut max_heap, node, ef_parameter);
        }

        let new_node = ScoredItemGeneric {
            distance: 8,
            vector: 3,
        };
        add_neighbor_to_heaps(&mut min_heap, &mut max_heap, new_node, ef_parameter);

        // No change should be made, as the new node's distance is greater than current max
        assert_eq!(min_heap.len(), 2);
        assert_eq!(max_heap.len(), 2);
        assert_eq!(max_heap.peek().unwrap().distance, 5);
        assert_eq!(min_heap.peek().unwrap().0.distance, 3);
    }

    /// Tests the `add_neighbor_to_heaps` function with an edge case where `ef_parameter` is set to 1.
    ///
    /// This test initializes empty `min_heap` and `max_heap`, and adds three nodes with distances 5.0, 3.0, and 2.0.
    /// The `ef_parameter` is set to 1, allowing only 1 node to be included in the `max_heap`. The expected result is that
    /// the top of the `max_heap` contains only the node with the smallest distance (2.0), while the `min_heap` contains
    /// all 3 nodes, with the smallest distance node at the top.
    #[test]
    fn test_ef_parameter_of_one() {
        let mut min_heap: BinaryHeap<Reverse<ScoredItemGeneric<i32, usize>>> = BinaryHeap::new();
        let mut max_heap: BinaryHeap<ScoredItemGeneric<i32, usize>> = BinaryHeap::new();
        let ef_parameter = 1;

        let nodes = [
            ScoredItemGeneric {
                distance: 5,
                vector: 1,
            },
            ScoredItemGeneric {
                distance: 3,
                vector: 2,
            },
            ScoredItemGeneric {
                distance: 2,
                vector: 3,
            },
        ];

        for node in nodes.iter().cloned() {
            add_neighbor_to_heaps(&mut min_heap, &mut max_heap, node, ef_parameter);
        }

        assert_eq!(min_heap.len(), 3);
        assert_eq!(max_heap.len(), 1);
        assert_eq!(max_heap.peek().unwrap().distance, 2);
        assert_eq!(min_heap.peek().unwrap().0.distance, 2);
    }

    /// Tests the `add_neighbor_to_heaps` function when adding a node with a distance equal to the current maximum.
    ///
    /// This test initializes empty `min_heap` and `max_heap`, and adds two nodes with distances 5.0 and 3.0.
    /// The `ef_parameter` is set to 2, allowing both nodes to be included in the heaps. Then, a new node with the same
    /// distance as the current maximum (5.0) is added. The expected result is that the heaps remain unchanged, with
    /// the top of the `max_heap` containing the distance 5.0, and the top of the `min_heap` containing the distance 3.0.
    #[test]
    fn test_add_node_equal_to_current_max() {
        let mut min_heap: BinaryHeap<Reverse<ScoredItemGeneric<i32, usize>>> = BinaryHeap::new();
        let mut max_heap: BinaryHeap<ScoredItemGeneric<i32, usize>> = BinaryHeap::new();
        let ef_parameter = 2;

        let nodes = [
            ScoredItemGeneric {
                distance: 5,
                vector: 1,
            },
            ScoredItemGeneric {
                distance: 3,
                vector: 2,
            },
        ];

        for node in nodes.iter().cloned() {
            add_neighbor_to_heaps(&mut min_heap, &mut max_heap, node, ef_parameter);
        }

        let new_node = ScoredItemGeneric {
            distance: 5,
            vector: 3,
        };
        add_neighbor_to_heaps(&mut min_heap, &mut max_heap, new_node, ef_parameter);

        assert_eq!(min_heap.len(), 2);
        assert_eq!(max_heap.len(), 2);
        assert_eq!(max_heap.peek().unwrap().distance, 5);
        assert_eq!(min_heap.peek().unwrap().0.distance, 3);
    }
}
