use std::collections::HashSet;
use std::path::Path;

use once_cell::sync::Lazy;
use rand::seq::SliceRandom;
use rand::Rng;
use regex::Regex;

use super::seed::SeedCsvAccess;

const SEED_CSV_SAMPLES: usize = 40;

static RE_WS: Lazy<Regex> = Lazy::new(|| Regex::new(r"\s+").unwrap());
static RE_TRIM_TOK: Lazy<Regex> = Lazy::new(|| Regex::new(r"(?u)^[\W_]+|[\W_]+$").unwrap());

fn skip_name_tokens() -> &'static HashSet<&'static str> {
    static SET: Lazy<HashSet<&'static str>> = Lazy::new(|| {
        [
            "b", "bv", "b.v", "b.v.", "nv", "vof", "cv", "ab", "kb", "as", "asa", "oy", "oyj",
            "oü", "ou", "ltd", "llc", "inc", "plc", "gmbh", "ag", "sas", "sa", "srl", "spa", "sl",
            "slu", "ooo", "zoo", "see",
        ]
        .into_iter()
        .collect()
    });
    &SET
}

fn bad_query_whole() -> &'static HashSet<&'static str> {
    static SET: Lazy<HashSet<&'static str>> = Lazy::new(|| {
        ["b.v.", "bv", "nv", "vof", "ab", "oy", "ltd", "llc", "inc"]
            .into_iter()
            .collect()
    });
    &SET
}

fn normalize_query_token(t: &str) -> String {
    RE_TRIM_TOK.replace_all(t, "").to_string().to_lowercase()
}

fn meaningful_tokens_from_name(name: &str) -> Vec<String> {
    let name = name.trim();
    if name.is_empty() {
        return vec![];
    }
    let skip = skip_name_tokens();
    let mut out = Vec::new();
    for p in RE_WS.split(name) {
        if p.is_empty() {
            continue;
        }
        let norm = normalize_query_token(p);
        if norm.is_empty() || skip.contains(norm.as_str()) {
            continue;
        }
        if norm.len() == 1 && !norm.chars().next().is_some_and(|c| c.is_alphanumeric()) {
            continue;
        }
        out.push(p.trim().to_string());
    }
    out
}

fn query_candidates_from_name(name: &str, min_len: usize, max_len: usize) -> Vec<String> {
    let bad = bad_query_whole();
    let mut seen_local: HashSet<String> = HashSet::new();
    let mut cands: Vec<String> = Vec::new();

    let mut add = |s: &str| {
        let s = s.trim();
        let c_len = s.chars().count();
        if c_len < min_len || c_len > max_len {
            return;
        }
        let low = s.to_lowercase();
        if bad.contains(low.as_str()) {
            return;
        }
        if !s.chars().any(|c| c.is_alphabetic()) {
            return;
        }
        if seen_local.insert(low) {
            cands.push(s.to_string());
        }
    };

    let tokens = meaningful_tokens_from_name(name);
    if !tokens.is_empty() {
        for n in 1..=tokens.len() {
            let chunk = tokens[..n].join(" ");
            let chunk = chunk.trim();
            let mut chunk = chunk.to_string();
            if chunk.chars().count() > max_len {
                chunk = chunk.chars().take(max_len).collect::<String>();
                while chunk.ends_with(' ') {
                    chunk.pop();
                }
            }
            add(&chunk);
        }
        let joined = tokens.join(" ");
        let upper = max_len.min(joined.chars().count());
        if upper >= min_len {
            for l in (min_len..=upper).rev() {
                add(&joined.chars().take(l).collect::<String>());
            }
        }
    } else {
        let stripped = name.trim();
        let upper = max_len.min(stripped.chars().count());
        if upper >= min_len {
            for l in (min_len..=upper).rev() {
                add(&stripped.chars().take(l).collect::<String>());
            }
        }
    }
    cands
}

fn query_is_acceptable(q: &str, min_len: usize, max_len: usize) -> bool {
    let q = q.trim();
    let c_len = q.chars().count();
    if c_len < min_len || c_len > max_len {
        return false;
    }
    if bad_query_whole().contains(q.to_lowercase().as_str()) {
        return false;
    }
    q.chars().any(|c| c.is_alphabetic())
}

pub fn load_words_file(path: &Path) -> Vec<String> {
    if !path.is_file() {
        return vec![];
    }
    let Ok(data) = std::fs::read_to_string(path) else {
        return vec![];
    };
    data.lines()
        .map(|l| l.trim())
        .filter(|l| !l.is_empty() && !l.starts_with('#'))
        .map(String::from)
        .collect()
}

pub struct QueryGenerator<'a> {
    words: Vec<String>,
    seed_access: Option<&'a SeedCsvAccess>,
    text_seed: String,
    seen: HashSet<String>,
}

impl<'a> QueryGenerator<'a> {
    pub fn new(
        words: Vec<String>,
        seed_access: Option<&'a SeedCsvAccess>,
        text_seed: String,
    ) -> Self {
        Self {
            words,
            seed_access,
            text_seed,
            seen: HashSet::new(),
        }
    }

    pub fn mark_seen(&mut self, query: String) {
        self.seen.insert(query);
    }

    pub fn seen_count(&self) -> usize {
        self.seen.len()
    }

    pub fn next(
        &mut self,
        last_result_names: &[String],
        min_len: usize,
        max_len: usize,
        rng: &mut impl Rng,
    ) -> (String, bool) {
        let mut pool: Vec<String> = Vec::new();
        for name in last_result_names {
            pool.extend(query_candidates_from_name(name, min_len, max_len));
        }
        pool.shuffle(rng);
        for c in pool {
            if !self.seen.contains(&c) && query_is_acceptable(&c, min_len, max_len) {
                return (c, false);
            }
        }

        if let Some(access) = self.seed_access {
            for _ in 0..SEED_CSV_SAMPLES {
                let Some(row) = access.random_row(rng) else {
                    continue;
                };
                let name = row
                    .get("name")
                    .or_else(|| row.get("naam"))
                    .map(|s| s.trim().to_string())
                    .filter(|s| !s.is_empty());
                let Some(name) = name else {
                    continue;
                };
                let mut cands = query_candidates_from_name(&name, min_len, max_len);
                cands.shuffle(rng);
                for c in cands {
                    if !self.seen.contains(&c) && query_is_acceptable(&c, min_len, max_len) {
                        return (c, false);
                    }
                }
            }
        }

        let mut bag = self.words.clone();
        bag.shuffle(rng);
        for w in bag {
            let c = w.trim().chars().take(max_len).collect::<String>();
            let c = c.trim().to_string();
            if !self.seen.contains(&c) && query_is_acceptable(&c, min_len, max_len) {
                return (c, false);
            }
        }

        for c in query_candidates_from_name(&self.text_seed, min_len, max_len) {
            if !self.seen.contains(&c) {
                return (c, false);
            }
        }

        for _ in 0..1000 {
            let n: u64 = rng.gen_range(100_000..1_000_000_000);
            let c = n.to_string();
            let c = c.chars().take(max_len).collect::<String>();
            if c.len() >= min_len && !self.seen.contains(&c) {
                return (c, true);
            }
        }

        let c = format!(
            "q{}",
            std::time::SystemTime::now()
                .duration_since(std::time::UNIX_EPOCH)
                .map(|d| d.as_nanos())
                .unwrap_or(0)
        )
        .chars()
        .take(max_len)
        .collect::<String>();

        let c = if c.len() < min_len {
            format!("{c}{}", "0".repeat(min_len - c.len()))
        } else {
            c
        };
        (c, true)
    }
}
