#![cfg(feature = "libsql")]
//! Integration tests for multi-scope workspace reads using file-backed libSQL.
//!
//! Guards the PR2 contract: workspaces can read from multiple user scopes
//! while writes remain isolated to the primary scope.

use std::sync::Arc;

use ironclaw::db::Database;
use ironclaw::db::libsql::LibSqlBackend;
use ironclaw::workspace::Workspace;

async fn setup() -> (Arc<dyn Database>, tempfile::TempDir) {
    let dir = tempfile::tempdir().expect("create temp dir");
    let db_path = dir.path().join("test.db");
    let backend = LibSqlBackend::new_local(&db_path).await.expect("create db");
    backend.run_migrations().await.expect("run migrations");
    let db: Arc<dyn Database> = Arc::new(backend);
    (db, dir)
}

#[tokio::test]
async fn read_across_scopes() {
    let (db, _dir) = setup().await;

    // Write docs as the "shared" user
    let ws_shared = Workspace::new_with_db("shared", Arc::clone(&db));
    ws_shared
        .write("docs/team-standup.md", "Team standup notes from Monday")
        .await
        .expect("shared write failed");

    // Alice's workspace with "shared" as an additional read scope
    let ws_alice = Workspace::new_with_db("alice", Arc::clone(&db))
        .with_additional_read_scopes(vec!["shared".to_string()]);

    // Alice can read shared docs
    let doc = ws_alice
        .read("docs/team-standup.md")
        .await
        .expect("cross-scope read failed");
    assert_eq!(doc.content, "Team standup notes from Monday");
}

#[tokio::test]
async fn write_stays_in_primary_scope() {
    let (db, _dir) = setup().await;

    // Alice has "shared" as a read scope
    let ws_alice = Workspace::new_with_db("alice", Arc::clone(&db))
        .with_additional_read_scopes(vec!["shared".to_string()]);

    // Alice writes a personal note
    ws_alice
        .write("notes/personal.md", "Alice's private note")
        .await
        .expect("alice write failed");

    // The "shared" workspace should NOT see Alice's note
    let ws_shared = Workspace::new_with_db("shared", Arc::clone(&db));
    let result = ws_shared.read("notes/personal.md").await;
    assert!(result.is_err(), "Shared scope should not see Alice's note");
}

#[tokio::test]
async fn list_paths_merges_across_scopes() {
    let (db, _dir) = setup().await;

    // Write as alice
    let ws_alice_plain = Workspace::new_with_db("alice", Arc::clone(&db));
    ws_alice_plain
        .write("notes/personal.md", "My notes")
        .await
        .expect("alice write failed");

    // Write as shared
    let ws_shared = Workspace::new_with_db("shared", Arc::clone(&db));
    ws_shared
        .write("docs/shared-doc.md", "Shared document")
        .await
        .expect("shared write failed");

    // Alice with multi-scope should see both
    let ws_alice = Workspace::new_with_db("alice", Arc::clone(&db))
        .with_additional_read_scopes(vec!["shared".to_string()]);

    let all_paths = ws_alice.list_all().await.expect("list_all failed");
    assert!(
        all_paths.contains(&"notes/personal.md".to_string()),
        "Should contain alice's note: {:?}",
        all_paths
    );
    assert!(
        all_paths.contains(&"docs/shared-doc.md".to_string()),
        "Should contain shared doc: {:?}",
        all_paths
    );
}

#[tokio::test]
async fn list_directory_merges_across_scopes() {
    let (db, _dir) = setup().await;

    // Alice writes to docs/
    let ws_alice_plain = Workspace::new_with_db("alice", Arc::clone(&db));
    ws_alice_plain
        .write("docs/alice-doc.md", "Alice's doc")
        .await
        .expect("alice write failed");

    // Shared writes to docs/
    let ws_shared = Workspace::new_with_db("shared", Arc::clone(&db));
    ws_shared
        .write("docs/shared-doc.md", "Shared doc")
        .await
        .expect("shared write failed");

    // Alice with multi-scope lists docs/
    let ws_alice = Workspace::new_with_db("alice", Arc::clone(&db))
        .with_additional_read_scopes(vec!["shared".to_string()]);

    let entries = ws_alice.list("docs").await.expect("list failed");
    let paths: Vec<&str> = entries.iter().map(|e| e.path.as_str()).collect();
    assert!(
        paths.contains(&"docs/alice-doc.md"),
        "Should contain alice's doc: {:?}",
        paths
    );
    assert!(
        paths.contains(&"docs/shared-doc.md"),
        "Should contain shared doc: {:?}",
        paths
    );
}

#[tokio::test]
async fn search_spans_scopes() {
    let (db, _dir) = setup().await;

    // Write searchable content in shared scope
    let ws_shared = Workspace::new_with_db("shared", Arc::clone(&db));
    ws_shared
        .write(
            "docs/architecture.md",
            "The microservice architecture uses gRPC for inter-service communication",
        )
        .await
        .expect("shared write failed");

    // Write searchable content in alice scope
    let ws_alice_plain = Workspace::new_with_db("alice", Arc::clone(&db));
    ws_alice_plain
        .write("notes/ideas.md", "Consider switching to GraphQL federation")
        .await
        .expect("alice write failed");

    // Alice with multi-scope searches
    let ws_alice = Workspace::new_with_db("alice", Arc::clone(&db))
        .with_additional_read_scopes(vec!["shared".to_string()]);

    // Search for content in the shared scope
    let results = ws_alice
        .search("microservice architecture gRPC", 10)
        .await
        .expect("search failed");
    assert!(!results.is_empty(), "Should find results from shared scope");
}

#[tokio::test]
async fn read_priority_primary_first() {
    let (db, _dir) = setup().await;

    // Write same path in both scopes
    let ws_shared = Workspace::new_with_db("shared", Arc::clone(&db));
    ws_shared
        .write("config/settings.md", "Shared settings v1")
        .await
        .expect("shared write failed");

    let ws_alice_plain = Workspace::new_with_db("alice", Arc::clone(&db));
    ws_alice_plain
        .write("config/settings.md", "Alice's settings override")
        .await
        .expect("alice write failed");

    // Alice with multi-scope should get her own version (primary scope wins)
    let ws_alice = Workspace::new_with_db("alice", Arc::clone(&db))
        .with_additional_read_scopes(vec!["shared".to_string()]);

    let doc = ws_alice
        .read("config/settings.md")
        .await
        .expect("read failed");
    assert_eq!(
        doc.content, "Alice's settings override",
        "Primary scope should take priority"
    );
}

#[tokio::test]
async fn exists_spans_scopes() {
    let (db, _dir) = setup().await;

    // Write a doc as "shared"
    let ws_shared = Workspace::new_with_db("shared", Arc::clone(&db));
    ws_shared
        .write("docs/shared-only.md", "Shared content")
        .await
        .expect("shared write failed");

    // Alice without multi-scope should NOT see it
    let ws_alice_plain = Workspace::new_with_db("alice", Arc::clone(&db));
    assert!(
        !ws_alice_plain
            .exists("docs/shared-only.md")
            .await
            .expect("exists failed"),
        "Alice without multi-scope should not see shared doc"
    );

    // Alice with multi-scope should see it
    let ws_alice = Workspace::new_with_db("alice", Arc::clone(&db))
        .with_additional_read_scopes(vec!["shared".to_string()]);
    assert!(
        ws_alice
            .exists("docs/shared-only.md")
            .await
            .expect("exists failed"),
        "Alice with multi-scope should see shared doc"
    );
}

#[tokio::test]
async fn append_stays_in_primary_scope() {
    let (db, _dir) = setup().await;

    // Write a document as "shared"
    let ws_shared = Workspace::new_with_db("shared", Arc::clone(&db));
    ws_shared
        .write("notes/log.md", "shared original content")
        .await
        .expect("shared write failed");

    // Alice has "shared" as a read scope and appends to the same path
    let ws_alice = Workspace::new_with_db("alice", Arc::clone(&db))
        .with_additional_read_scopes(vec!["shared".to_string()]);
    ws_alice
        .append("notes/log.md", "alice appended line")
        .await
        .expect("alice append failed");

    // Shared document must be unchanged (write isolation)
    let shared_doc = ws_shared
        .read("notes/log.md")
        .await
        .expect("shared read failed");
    assert_eq!(
        shared_doc.content, "shared original content",
        "Append must not modify the secondary scope's document"
    );

    // Alice should have her own copy with the appended content
    let ws_alice_plain = Workspace::new_with_db("alice", Arc::clone(&db));
    let alice_doc = ws_alice_plain
        .read("notes/log.md")
        .await
        .expect("alice read failed");
    assert_eq!(
        alice_doc.content, "alice appended line",
        "Append should create a new document in alice's scope"
    );
}

#[tokio::test]
async fn append_memory_stays_in_primary_scope() {
    let (db, _dir) = setup().await;

    // Write MEMORY.md as "shared"
    let ws_shared = Workspace::new_with_db("shared", Arc::clone(&db));
    ws_shared
        .write("MEMORY.md", "shared memory baseline")
        .await
        .expect("shared write failed");

    // Alice has "shared" as a read scope and appends a memory entry
    let ws_alice = Workspace::new_with_db("alice", Arc::clone(&db))
        .with_additional_read_scopes(vec!["shared".to_string()]);
    ws_alice
        .append_memory("alice remembers this")
        .await
        .expect("alice append_memory failed");

    // Shared MEMORY.md must be unchanged
    let shared_doc = ws_shared
        .read("MEMORY.md")
        .await
        .expect("shared read failed");
    assert_eq!(
        shared_doc.content, "shared memory baseline",
        "append_memory must not modify the secondary scope's document"
    );

    // Alice should have her own MEMORY.md
    let ws_alice_plain = Workspace::new_with_db("alice", Arc::clone(&db));
    let alice_doc = ws_alice_plain
        .read("MEMORY.md")
        .await
        .expect("alice read failed");
    assert_eq!(
        alice_doc.content, "alice remembers this",
        "append_memory should create in alice's scope"
    );
}

// ==================== Identity isolation tests ====================

#[tokio::test]
async fn identity_files_not_readable_from_secondary_scope() {
    let (db, _dir) = setup().await;

    let ws_other = Workspace::new_with_db("other-user", Arc::clone(&db));
    ws_other
        .write("IDENTITY.md", "I am the other user")
        .await
        .expect("write failed");
    ws_other
        .write("SOUL.md", "Other user soul overlay")
        .await
        .expect("write failed");
    ws_other
        .write("USER.md", "Other user profile")
        .await
        .expect("write failed");
    ws_other
        .write("AGENTS.md", "Other user agent config")
        .await
        .expect("write failed");

    let ws_primary = Workspace::new_with_db("primary", Arc::clone(&db))
        .with_additional_read_scopes(vec!["other-user".to_string()]);

    for path in &["IDENTITY.md", "SOUL.md", "USER.md", "AGENTS.md"] {
        let result = ws_primary.read(path).await;
        assert!(
            result.is_err(),
            "Primary should NOT read other user's {} via secondary scope",
            path
        );
    }
}

#[tokio::test]
async fn identity_files_not_in_search_from_secondary_scope() {
    let (db, _dir) = setup().await;

    let ws_other = Workspace::new_with_db("other-user", Arc::clone(&db));
    ws_other
        .write("SOUL.md", "Other user loves xylophone music passionately")
        .await
        .expect("write failed");
    ws_other
        .write(
            "notes/music.md",
            "Other user played xylophone at the concert",
        )
        .await
        .expect("write failed");

    let ws_primary = Workspace::new_with_db("primary", Arc::clone(&db))
        .with_additional_read_scopes(vec!["other-user".to_string()]);

    let results = ws_primary
        .search("xylophone", 10)
        .await
        .expect("search failed");
    let has_concert = results.iter().any(|r| r.content.contains("concert"));
    assert!(
        has_concert,
        "Should find non-identity content from secondary scope"
    );
    let has_soul = results.iter().any(|r| r.content.contains("passionately"));
    assert!(
        !has_soul,
        "SOUL.md content from secondary scope should not appear in search results"
    );
}

#[tokio::test]
async fn identity_files_not_in_list_from_secondary_scope() {
    let (db, _dir) = setup().await;

    let ws_other = Workspace::new_with_db("other-user", Arc::clone(&db));
    ws_other
        .write("IDENTITY.md", "I am the other user")
        .await
        .expect("write failed");
    ws_other
        .write("notes/shared-note.md", "A shared note")
        .await
        .expect("write failed");

    let ws_primary = Workspace::new_with_db("primary", Arc::clone(&db))
        .with_additional_read_scopes(vec!["other-user".to_string()]);

    let paths = ws_primary.list_all().await.expect("list failed");
    assert!(
        !paths.contains(&"IDENTITY.md".to_string()),
        "IDENTITY.md from secondary scope should not appear"
    );
    assert!(
        paths.contains(&"notes/shared-note.md".to_string()),
        "Non-identity files should be listed"
    );
}

#[tokio::test]
async fn empty_read_scopes_reads_primary_only() {
    let (db, _dir) = setup().await;

    let ws_shared = Workspace::new_with_db("shared", Arc::clone(&db));
    ws_shared
        .write("docs/note.md", "Shared note")
        .await
        .expect("write failed");

    let ws_primary =
        Workspace::new_with_db("primary", Arc::clone(&db)).with_additional_read_scopes(vec![]);

    let result = ws_primary.read("docs/note.md").await;
    assert!(
        result.is_err(),
        "Empty read scopes should not grant cross-scope access"
    );
}

#[tokio::test]
async fn duplicate_read_scopes_handled() {
    let (db, _dir) = setup().await;

    let ws_shared = Workspace::new_with_db("shared", Arc::clone(&db));
    ws_shared
        .write("docs/note.md", "One note")
        .await
        .expect("write failed");

    let ws_primary = Workspace::new_with_db("primary", Arc::clone(&db))
        .with_additional_read_scopes(vec!["shared".to_string(), "shared".to_string()]);

    let doc = ws_primary.read("docs/note.md").await.expect("read failed");
    assert_eq!(doc.content, "One note");
}
