use crate::types::NormalizedEntity;
use crate::blocking::BlockingStrategy;
use std::collections::HashSet;
use rand::{Rng, SeedableRng};
use rand_chacha::ChaCha8Rng;

pub struct LshBlocking {
    pub fields: Vec<String>,
    pub shingle_size: usize,
    pub num_hashes: usize,
    pub bands: usize,
    pub rows: usize, 
    a: Vec<u64>,
    b: Vec<u64>,
}

impl LshBlocking {
    pub fn new(fields: Vec<String>, bands: usize, rows: usize) -> Self {
        let num_hashes = bands * rows;
        let mut rng = ChaCha8Rng::seed_from_u64(42);
        let a: Vec<u64> = (0..num_hashes).map(|_| rng.gen()).collect();
        let b: Vec<u64> = (0..num_hashes).map(|_| rng.gen()).collect();

        Self {
            fields,
            shingle_size: 3,
            num_hashes,
            bands,
            rows,
            a,
            b,
        }
    }

    fn get_shingles(&self, text: &str) -> HashSet<String> {
        let mut shingles = HashSet::new();
        if text.len() < self.shingle_size {
            shingles.insert(text.to_string());
            return shingles;
        }
        for i in 0..=text.len() - self.shingle_size {
            shingles.insert(text[i..i + self.shingle_size].to_string());
        }
        shingles
    }

    fn minhash(&self, shingles: &HashSet<String>) -> Vec<u64> {
        let mut signatures = vec![u64::MAX; self.num_hashes];
        
        for shingle in shingles {
            let x = seahash::hash(shingle.as_bytes());
            
            for i in 0..self.num_hashes {
                let h = x.wrapping_mul(self.a[i]).wrapping_add(self.b[i]);
                if h < signatures[i] {
                    signatures[i] = h;
                }
            }
        }
        signatures
    }
}

impl BlockingStrategy for LshBlocking {
    fn generate_block_key(&self, entity: &NormalizedEntity) -> Vec<String> {
        let mut combined_text = String::new();
        for field in &self.fields {
            if let Some(val) = entity.data.get(field).map(|v| v.as_str()) {
                combined_text.push_str(val);
                combined_text.push(' ');
            }
        }

        if combined_text.is_empty() {
            return vec![];
        }

        let shingles = self.get_shingles(&combined_text);
        let signatures = self.minhash(&shingles);

        let mut block_keys = Vec::new();
        for b in 0..self.bands {
            let start = b * self.rows;
            let end = start + self.rows;
            let band_signature = &signatures[start..end];
            
            let mut s = String::new();
            for sig in band_signature {
                s.push_str(&sig.to_string());
            }
            let band_hash = seahash::hash(s.as_bytes());
            block_keys.push(format!("lsh:{}:{}", b, band_hash));
        }

        block_keys
    }
}
