/// End-to-end integration test using a real Brittle<FsStore> repository.
///
/// Exercises the full workflow: create repo, add references with authors,
/// organize in libraries, export BibTeX, create a snapshot, modify state,
/// restore the snapshot, and verify everything reverted correctly.
use brittle_core::{
    AnnotationType, Brittle, BrittleError, Color, EntryType, Person, TextMarkupType,
    ValidationError,
};

#[test]
fn full_workflow() {
    let tmp = tempfile::tempdir().unwrap();
    let mut db = Brittle::create(tmp.path()).unwrap();

    // ---- Create references ----
    let mut turing = db
        .create_reference("turing1950", EntryType::Article)
        .unwrap();
    turing.authors.push(Person {
        family: "Turing".into(),
        given: Some("Alan M.".into()),
        prefix: None,
        suffix: None,
    });
    turing.fields.insert(
        "title".into(),
        "Computing Machinery and Intelligence".into(),
    );
    turing.fields.insert("journal".into(), "Mind".into());
    turing.fields.insert("year".into(), "1950".into());
    let turing = db.update_reference(turing).unwrap();

    let mut knuth = db.create_reference("knuth1984", EntryType::Book).unwrap();
    knuth.authors.push(Person::new("Knuth"));
    knuth.fields.insert("title".into(), "The TeXbook".into());
    knuth
        .fields
        .insert("publisher".into(), "Addison-Wesley".into());
    knuth.fields.insert("year".into(), "1984".into());
    let knuth = db.update_reference(knuth).unwrap();

    // ---- Organize in libraries ----
    let cs = db.create_library("Computer Science", None).unwrap();
    let ai = db.create_library("AI", Some(cs.id)).unwrap();

    db.add_to_library(cs.id, turing.id).unwrap();
    db.add_to_library(ai.id, turing.id).unwrap(); // multi-membership
    db.add_to_library(cs.id, knuth.id).unwrap();

    // Both references are in CS.
    let cs_refs = db.list_library_references(cs.id).unwrap();
    assert_eq!(cs_refs.len(), 2);

    // Turing is in both CS and AI.
    let turing_libs = db.list_reference_libraries(turing.id).unwrap();
    assert_eq!(turing_libs.len(), 2);

    // ---- BibTeX export ----
    let (bibtex, errors) = db.export_library_bibtex(cs.id).unwrap();
    assert!(errors.is_empty(), "unexpected BibTeX errors: {errors:?}");
    assert!(bibtex.contains("@article{turing1950,"));
    assert!(bibtex.contains("Turing, Alan M."));
    assert!(bibtex.contains("Computing Machinery and Intelligence"));
    assert!(bibtex.contains("@book{knuth1984,"));

    // ---- Annotations ----
    let ann = db
        .create_annotation(
            turing.id,
            0,
            AnnotationType::TextMarkup {
                markup_type: TextMarkupType::Highlight,
                quads: vec![],
                color: Color::YELLOW,
                selected_text: Some("The Imitation Game".into()),
            },
            Some("Key concept".into()),
        )
        .unwrap();

    let annotations = db.get_annotations(turing.id).unwrap();
    assert_eq!(annotations.len(), 1);
    assert_eq!(annotations[0].content.as_deref(), Some("Key concept"));

    // ---- Snapshot ----
    let snap = db
        .create_snapshot("Baseline with Turing and Knuth")
        .unwrap();
    assert!(!snap.id.is_empty());

    let snapshots = db.list_snapshots().unwrap();
    // At least our named snapshot + the initial "Initialize Brittle repository" commit.
    assert!(snapshots.len() >= 2);
    assert!(
        snapshots
            .iter()
            .any(|s| s.message == "Baseline with Turing and Knuth")
    );

    // ---- Modify state after snapshot ----
    db.delete_reference(knuth.id).unwrap();
    assert!(db.get_reference(knuth.id).is_err());

    let cs_refs_after = db.list_library_references(cs.id).unwrap();
    assert_eq!(
        cs_refs_after.len(),
        1,
        "Knuth should have been removed from library"
    );

    // ---- Restore snapshot ----
    // The knuth deletion is written to disk but not committed — verify this.
    assert!(db.has_uncommitted_changes().unwrap());

    // restore_snapshot errors on uncommitted changes; use discard_changes instead.
    db.discard_changes().unwrap();

    // After restore, Knuth should be back.
    let knuth_restored = db.get_reference(knuth.id).unwrap();
    assert_eq!(knuth_restored.cite_key, "knuth1984");

    // CS library should have 2 members again.
    let cs_refs_restored = db.list_library_references(cs.id).unwrap();
    assert_eq!(cs_refs_restored.len(), 2);

    // Inspect git log to verify history is human-readable.
    let snapshots_after = db.list_snapshots().unwrap();
    assert!(!snapshots_after.is_empty());
}

#[test]
fn get_pdf_path_returns_error_when_no_pdf_attached() {
    let tmp = tempfile::tempdir().unwrap();
    let mut db = Brittle::create(tmp.path()).unwrap();
    let r = db.create_reference("nopdf2024", EntryType::Misc).unwrap();

    let err = db.get_pdf_path(r.id).unwrap_err();
    assert!(
        matches!(
            err,
            BrittleError::Validation(ValidationError::NoPdfAttached { .. })
        ),
        "expected NoPdfAttached, got {err}"
    );
}

#[test]
fn get_pdf_path_returns_path_after_attach() {
    let tmp = tempfile::tempdir().unwrap();
    let mut db = Brittle::create(tmp.path()).unwrap();
    let r = db.create_reference("withpdf2024", EntryType::Misc).unwrap();

    // Write a dummy PDF file.
    let source = tmp.path().join("dummy.pdf");
    std::fs::write(&source, b"%PDF-1.4 dummy").unwrap();

    db.attach_pdf(r.id, &source).unwrap();

    let path = db.get_pdf_path(r.id).unwrap();
    assert!(path.exists(), "PDF path {path:?} should exist on disk");
    assert_eq!(path.extension().and_then(|e| e.to_str()), Some("pdf"));
}
