use crate::bibtex::validation::validate_for_export;
use crate::error::BibtexError;
use crate::model::{Person, Reference};

/// Escape a BibTeX field value by wrapping special characters in braces.
/// Handles `{`, `}`, `\`, and preserves existing braced groups.
fn escape_field(value: &str) -> String {
    // Wrap the whole value in braces — simple and safe for most content.
    // This prevents BibTeX from case-folding titles and handles special chars.
    format!("{{{value}}}")
}

/// Format a list of persons as a BibTeX "and"-separated author string.
fn format_persons(persons: &[Person]) -> String {
    persons
        .iter()
        .map(|p| p.to_bibtex())
        .collect::<Vec<_>>()
        .join(" and ")
}

/// Export a single reference as a BibTeX entry string.
///
/// Returns an error if required fields are missing.
pub fn export_reference(reference: &Reference) -> Result<String, BibtexError> {
    validate_for_export(reference)?;

    let mut out = String::new();

    out.push('@');
    out.push_str(reference.entry_type.bibtex_name());
    out.push('{');
    out.push_str(&reference.cite_key);
    out.push_str(",\n");

    // Authors and editors come first for readability.
    if !reference.authors.is_empty() {
        let authors = format_persons(&reference.authors);
        out.push_str(&format!("  author = {},\n", escape_field(&authors)));
    }
    if !reference.editors.is_empty() {
        let editors = format_persons(&reference.editors);
        out.push_str(&format!("  editor = {},\n", escape_field(&editors)));
    }

    // All other fields in sorted order (BTreeMap guarantees this).
    for (key, value) in &reference.fields {
        out.push_str(&format!("  {key} = {},\n", escape_field(value)));
    }

    out.push('}');

    Ok(out)
}

/// Export multiple references as a `.bib` file string.
///
/// Skips references with missing required fields and collects all errors.
/// Returns the BibTeX string and a list of any export errors.
pub fn export_references(references: &[Reference]) -> (String, Vec<BibtexError>) {
    let mut out = String::new();
    let mut errors = Vec::new();

    for reference in references {
        match export_reference(reference) {
            Ok(entry) => {
                if !out.is_empty() {
                    out.push('\n');
                }
                out.push_str(&entry);
                out.push('\n');
            }
            Err(e) => errors.push(e),
        }
    }

    (out, errors)
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::model::{EntryType, Person, Reference};

    fn make_article() -> Reference {
        let mut r = Reference::new("turing1950", EntryType::Article);
        r.authors.push(Person {
            family: "Turing".into(),
            given: Some("Alan M.".into()),
            prefix: None,
            suffix: None,
        });
        r.fields.insert(
            "title".into(),
            "Computing Machinery and Intelligence".into(),
        );
        r.fields.insert("journal".into(), "Mind".into());
        r.fields.insert("year".into(), "1950".into());
        r.fields.insert("volume".into(), "59".into());
        r
    }

    #[test]
    fn article_export() {
        let r = make_article();
        let bibtex = export_reference(&r).unwrap();
        assert!(bibtex.starts_with("@article{turing1950,"));
        assert!(bibtex.contains("author = {Turing, Alan M.}"));
        assert!(bibtex.contains("title = {Computing Machinery and Intelligence}"));
        assert!(bibtex.contains("journal = {Mind}"));
        assert!(bibtex.contains("year = {1950}"));
    }

    #[test]
    fn multi_author_formatting() {
        let mut r = Reference::new("ab2024", EntryType::Article);
        r.authors.push(Person {
            family: "Doe".into(),
            given: Some("Jane".into()),
            prefix: None,
            suffix: None,
        });
        r.authors.push(Person {
            family: "Smith".into(),
            given: Some("John".into()),
            prefix: None,
            suffix: None,
        });
        r.fields.insert("title".into(), "A Paper".into());
        r.fields.insert("journal".into(), "Nature".into());
        r.fields.insert("year".into(), "2024".into());

        let bibtex = export_reference(&r).unwrap();
        assert!(bibtex.contains("Doe, Jane and Smith, John"));
    }

    #[test]
    fn missing_required_field_returns_error() {
        let mut r = make_article();
        r.fields.remove("journal");
        assert!(export_reference(&r).is_err());
    }

    #[test]
    fn book_with_editor() {
        let mut r = Reference::new("knuth1986", EntryType::Book);
        r.editors.push(Person::new("Knuth"));
        r.fields.insert("title".into(), "The TeXbook".into());
        r.fields.insert("publisher".into(), "Addison-Wesley".into());
        r.fields.insert("year".into(), "1986".into());

        let bibtex = export_reference(&r).unwrap();
        assert!(bibtex.starts_with("@book{"));
        assert!(bibtex.contains("editor = {Knuth}"));
    }

    #[test]
    fn fields_appear_in_sorted_order() {
        let r = make_article();
        let bibtex = export_reference(&r).unwrap();
        let journal_pos = bibtex.find("journal").unwrap();
        let title_pos = bibtex.find("title").unwrap();
        let year_pos = bibtex.find("year").unwrap();
        // BTreeMap order: journal < title < volume < year (alphabetical)
        assert!(journal_pos < title_pos);
        assert!(title_pos < year_pos);
    }

    #[test]
    fn export_references_collects_errors() {
        let good = make_article();
        let bad = Reference::new("incomplete", EntryType::Article);
        // Missing author, title, journal, year

        let (bibtex, errors) = export_references(&[good, bad]);
        assert_eq!(errors.len(), 1);
        assert!(bibtex.contains("@article{turing1950,"));
    }
}
