use std::collections::HashMap;
use std::fs::File;
use std::io::{Read, Seek, SeekFrom};
use std::path::{Path, PathBuf};

use rand::Rng;

/// Byte offset of each line (first line typically CSV header).
fn csv_line_offsets(bytes: &[u8]) -> Vec<u64> {
    let mut out = vec![0u64];
    for (i, &b) in bytes.iter().enumerate() {
        if b == b'\n' {
            out.push((i + 1) as u64);
        }
    }
    out
}

pub struct SeedCsvAccess {
    path: PathBuf,
    offsets: Vec<u64>,
    fieldnames: Vec<String>,
}

impl SeedCsvAccess {
    pub fn open(path: &Path) -> std::io::Result<Option<Self>> {
        if !path.is_file() {
            return Ok(None);
        }
        let bytes = std::fs::read(path)?;
        let offsets = csv_line_offsets(&bytes);
        if offsets.len() < 2 {
            return Ok(None);
        }
        let text = std::str::from_utf8(&bytes)
            .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))?;
        let header_line = text.lines().next().unwrap_or("").trim_end_matches('\r');
        let mut rdr = csv::ReaderBuilder::new()
            .has_headers(false)
            .from_reader(header_line.as_bytes());
        let rec = match rdr.records().next() {
            Some(Ok(r)) => r,
            _ => return Ok(None),
        };
        let hdr: Vec<String> = rec.iter().map(String::from).collect();
        if hdr.is_empty() {
            return Ok(None);
        }
        tracing::info!(
            "Randomize seed CSV: {} ({} data lines)",
            path.display(),
            offsets.len().saturating_sub(1)
        );
        Ok(Some(Self {
            path: path.to_path_buf(),
            offsets,
            fieldnames: hdr,
        }))
    }

    pub fn random_row(&self, rng: &mut impl Rng) -> Option<HashMap<String, String>> {
        if self.offsets.len() < 2 {
            return None;
        }
        let j = rng.gen_range(1..self.offsets.len());
        let start = self.offsets[j] as usize;
        let file_len = std::fs::metadata(&self.path).ok()?.len() as usize;
        let end = self
            .offsets
            .get(j + 1)
            .map(|&x| x as usize)
            .unwrap_or(file_len);
        let mut f = File::open(&self.path).ok()?;
        let to_read = end.saturating_sub(start).max(1);
        let mut buf = vec![0u8; to_read];
        f.seek(SeekFrom::Start(start as u64)).ok()?;
        let n = f.read(&mut buf).ok()?;
        let slice = std::str::from_utf8(&buf[..n]).ok()?;
        let line = slice.lines().next()?.trim().trim_end_matches('\r');
        if line.is_empty() {
            return None;
        }
        let mut rdr = csv::ReaderBuilder::new()
            .has_headers(false)
            .from_reader(line.as_bytes());
        let rec = rdr.records().next()?.ok()?;
        let mut m = HashMap::new();
        for (i, fname) in self.fieldnames.iter().enumerate() {
            if let Some(v) = rec.get(i) {
                m.insert(fname.clone(), v.to_string());
            }
        }
        Some(m)
    }
}
