//! Integration tests for the Tauri command layer.
//!
//! These tests exercise `AppState` and the command logic end-to-end using a
//! real `Brittle<FsStore>` repository in a temp directory. Tauri IPC is not
//! involved — the functions under test are plain Rust.

use brittle_app::state::AppState;
use brittle_core::{Brittle, EntryType, Person};

fn open_state() -> (AppState, tempfile::TempDir) {
    let tmp = tempfile::tempdir().unwrap();
    let state = AppState::new();
    let brittle = Brittle::create(tmp.path()).unwrap();
    *state.brittle.lock().unwrap() = Some(brittle);
    (state, tmp)
}

// ── AppState ─────────────────────────────────────────────────────────────────

#[test]
fn no_repo_open_returns_error() {
    let state = AppState::new();
    let err = state.with_repo(|_| Ok(())).unwrap_err();
    assert_eq!(err, "no repository open");
}

#[test]
fn with_repo_propagates_brittle_errors() {
    let (state, _tmp) = open_state();
    // Trying to get a non-existent reference propagates the StoreError.
    let err = state
        .with_repo_read(|b| {
            use brittle_core::model::ids::ReferenceId;
            b.get_reference(ReferenceId::new())
        })
        .unwrap_err();
    assert!(!err.is_empty());
}

// ── Repository lifecycle ──────────────────────────────────────────────────────

#[test]
fn create_and_reopen_repository() {
    let tmp = tempfile::tempdir().unwrap();
    let state = AppState::new();

    // Create
    {
        let brittle = Brittle::create(tmp.path()).unwrap();
        *state.brittle.lock().unwrap() = Some(brittle);
    }

    // Close
    *state.brittle.lock().unwrap() = None;
    assert!(state.with_repo_read(|_| Ok(())).is_err());

    // Reopen
    {
        let brittle = Brittle::open(tmp.path()).unwrap();
        *state.brittle.lock().unwrap() = Some(brittle);
    }
    assert!(state.with_repo_read(|_| Ok(())).is_ok());
}

// ── Reference CRUD ───────────────────────────────────────────────────────────

#[test]
fn create_and_list_references() {
    let (state, _tmp) = open_state();

    state
        .with_repo(|b| b.create_reference("turing1950", EntryType::Article))
        .unwrap();
    state
        .with_repo(|b| b.create_reference("knuth1984", EntryType::Book))
        .unwrap();

    let refs = state.with_repo_read(|b| b.list_references()).unwrap();
    assert_eq!(refs.len(), 2);
    let keys: Vec<&str> = refs.iter().map(|r| r.cite_key.as_str()).collect();
    assert!(keys.contains(&"turing1950"));
    assert!(keys.contains(&"knuth1984"));
}

#[test]
fn delete_reference_removes_it() {
    let (state, _tmp) = open_state();

    let r = state
        .with_repo(|b| b.create_reference("gone2024", EntryType::Misc))
        .unwrap();

    state.with_repo(|b| b.delete_reference(r.id)).unwrap();

    let refs = state.with_repo_read(|b| b.list_references()).unwrap();
    assert!(refs.is_empty());
}

#[test]
fn set_and_remove_field() {
    let (state, _tmp) = open_state();

    let r = state
        .with_repo(|b| b.create_reference("fields2024", EntryType::Article))
        .unwrap();

    state
        .with_repo(|b| b.set_field(r.id, "title", "A Test Title"))
        .unwrap();

    let fetched = state.with_repo_read(|b| b.get_reference(r.id)).unwrap();
    assert_eq!(
        fetched.fields.get("title").map(String::as_str),
        Some("A Test Title")
    );

    state.with_repo(|b| b.remove_field(r.id, "title")).unwrap();

    let fetched2 = state.with_repo_read(|b| b.get_reference(r.id)).unwrap();
    assert!(!fetched2.fields.contains_key("title"));
}

#[test]
fn search_references_filters_by_query() {
    let (state, _tmp) = open_state();

    state
        .with_repo(|b| b.create_reference("turing1950", EntryType::Article))
        .unwrap();
    state
        .with_repo(|b| b.create_reference("knuth1984", EntryType::Book))
        .unwrap();

    let results = state
        .with_repo_read(|b| b.search_references("turing"))
        .unwrap();
    assert_eq!(results.len(), 1);
    assert_eq!(results[0].cite_key, "turing1950");
}

// ── Library ───────────────────────────────────────────────────────────────────

#[test]
fn create_nested_libraries_and_query_hierarchy() {
    let (state, _tmp) = open_state();

    let root = state.with_repo(|b| b.create_library("Root", None)).unwrap();
    let child = state
        .with_repo(|b| b.create_library("Child", Some(root.id)))
        .unwrap();

    let roots = state.with_repo_read(|b| b.list_root_libraries()).unwrap();
    assert_eq!(roots.len(), 1);
    assert_eq!(roots[0].id, root.id);

    let children = state
        .with_repo_read(|b| b.list_child_libraries(root.id))
        .unwrap();
    assert_eq!(children.len(), 1);
    assert_eq!(children[0].id, child.id);

    let ancestors = state
        .with_repo_read(|b| b.get_library_ancestors(child.id))
        .unwrap();
    assert_eq!(ancestors.len(), 1);
    assert_eq!(ancestors[0].id, root.id);
}

#[test]
fn add_reference_to_library_and_query() {
    let (state, _tmp) = open_state();

    let r = state
        .with_repo(|b| b.create_reference("member2024", EntryType::Article))
        .unwrap();
    let lib = state.with_repo(|b| b.create_library("Lib", None)).unwrap();

    state.with_repo(|b| b.add_to_library(lib.id, r.id)).unwrap();

    let members = state
        .with_repo_read(|b| b.list_library_references(lib.id))
        .unwrap();
    assert_eq!(members.len(), 1);
    assert_eq!(members[0].id, r.id);
}

#[test]
fn force_delete_library_removes_subtree() {
    let (state, _tmp) = open_state();

    let root = state.with_repo(|b| b.create_library("Root", None)).unwrap();
    state
        .with_repo(|b| b.create_library("Child", Some(root.id)))
        .unwrap();

    state
        .with_repo(|b| b.force_delete_library(root.id))
        .unwrap();

    let all = state.with_repo_read(|b| b.list_root_libraries()).unwrap();
    assert!(all.is_empty());
}

// ── BibTeX export ─────────────────────────────────────────────────────────────

#[test]
fn export_library_bibtex_contains_entries() {
    let (state, _tmp) = open_state();

    let mut r = state
        .with_repo(|b| b.create_reference("turing1950", EntryType::Article))
        .unwrap();
    r.authors.push(Person::new("Turing"));
    r.fields.insert(
        "title".into(),
        "Computing Machinery and Intelligence".into(),
    );
    r.fields.insert("journal".into(), "Mind".into());
    r.fields.insert("year".into(), "1950".into());
    let r = state.with_repo(|b| b.update_reference(r)).unwrap();

    let lib = state.with_repo(|b| b.create_library("CS", None)).unwrap();
    state.with_repo(|b| b.add_to_library(lib.id, r.id)).unwrap();

    let (bibtex, errors) = state
        .with_repo_read(|b| b.export_library_bibtex(lib.id))
        .unwrap();

    assert!(errors.is_empty());
    assert!(bibtex.contains("@article{turing1950,"));
    assert!(bibtex.contains("Computing Machinery and Intelligence"));
}

// ── Snapshot ──────────────────────────────────────────────────────────────────

#[test]
fn snapshot_and_discard_changes() {
    let (state, _tmp) = open_state();

    state
        .with_repo(|b| b.create_reference("snap2024", EntryType::Misc))
        .unwrap();

    let snap = state.with_repo(|b| b.create_snapshot("baseline")).unwrap();
    assert!(!snap.id.is_empty());

    let snapshots = state.with_repo_read(|b| b.list_snapshots()).unwrap();
    assert!(snapshots.iter().any(|s| s.message == "baseline"));

    // Delete the reference (uncommitted change).
    let r_id = state
        .with_repo_read(|b| b.list_references())
        .unwrap()
        .into_iter()
        .next()
        .unwrap()
        .id;
    state.with_repo(|b| b.delete_reference(r_id)).unwrap();

    assert!(state
        .with_repo_read(|b| b.has_uncommitted_changes())
        .unwrap());

    // Discard → reference comes back.
    state.with_repo(|b| b.discard_changes()).unwrap();

    let refs = state.with_repo_read(|b| b.list_references()).unwrap();
    assert_eq!(refs.len(), 1);
}
