use fierros_core::{FierrosError, FierrosResult};
use serde::{Deserialize, Serialize};
use std::fs;
use std::path::Path;

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct EvalCase {
    pub id: String,
    pub input: String,
    pub expected_sources: Vec<String>,
    pub must_contain: Vec<String>,
    pub must_not_contain: Vec<String>,
    pub should_refuse: bool,
}

pub fn load_jsonl(path: &Path) -> FierrosResult<Vec<EvalCase>> {
    let content = fs::read_to_string(path).map_err(|error| {
        FierrosError::InvalidInput(format!(
            "failed to read eval dataset '{}': {error}",
            path.display()
        ))
    })?;

    parse_jsonl(&content)
}

pub fn parse_jsonl(content: &str) -> FierrosResult<Vec<EvalCase>> {
    let mut cases = Vec::new();
    for (index, line) in content.lines().enumerate() {
        let trimmed = line.trim();
        if trimmed.is_empty() {
            continue;
        }
        let case = serde_json::from_str::<EvalCase>(trimmed).map_err(|error| {
            FierrosError::InvalidInput(format!(
                "failed to parse eval case at line {}: {error}",
                index + 1
            ))
        })?;
        cases.push(case);
    }

    if cases.is_empty() {
        return Err(FierrosError::InvalidInput(
            "eval dataset contains no cases".into(),
        ));
    }

    Ok(cases)
}

#[cfg(test)]
mod tests {
    use super::{load_jsonl, parse_jsonl};
    use std::fs;
    use std::path::PathBuf;
    use std::time::{SystemTime, UNIX_EPOCH};

    fn unique_temp_file(test_name: &str) -> PathBuf {
        let nonce = SystemTime::now()
            .duration_since(UNIX_EPOCH)
            .unwrap()
            .as_nanos();
        std::env::temp_dir().join(format!("fierros-evals-{test_name}-{nonce}.jsonl"))
    }

    #[test]
    fn parse_jsonl_reads_multiple_cases() {
        let input = r#"{"id":"one","input":"q1","expected_sources":["a"],"must_contain":[],"must_not_contain":[],"should_refuse":false}
{"id":"two","input":"q2","expected_sources":[],"must_contain":[],"must_not_contain":[],"should_refuse":true}"#;
        let cases = parse_jsonl(input).unwrap();
        assert_eq!(cases.len(), 2);
        assert_eq!(cases[0].id, "one");
        assert!(cases[1].should_refuse);
    }

    #[test]
    fn parse_jsonl_rejects_invalid_case() {
        let input = r#"{"id":"one","input":"q1","expected_sources":["a"],"must_contain":[],"must_not_contain":[],"should_refuse":false}
{"id":"bad"}"#;
        let error = parse_jsonl(input).unwrap_err();
        assert!(format!("{error}").contains("line 2"));
    }

    #[test]
    fn load_jsonl_reads_file() {
        let path = unique_temp_file("load");
        fs::write(
            &path,
            r#"{"id":"one","input":"q1","expected_sources":["a"],"must_contain":[],"must_not_contain":[],"should_refuse":false}"#,
        )
        .unwrap();

        let cases = load_jsonl(&path).unwrap();
        assert_eq!(cases.len(), 1);
        assert_eq!(cases[0].id, "one");

        fs::remove_file(path).unwrap();
    }
}
