//! Federation Module Tests
//!
//! Integration and unit tests for the federation module.

#[cfg(test)]
mod integration_tests {
    use std::sync::Arc;
    
    use crate::federation::{
        Federation, FederationConfig, Store,
        adapters::DocumentStoreAdapter,
        types::{StoreType, WriteOp, UniversalQuery},
    };
    
    /// Helper: create a temp DocumentStoreAdapter
    fn create_test_doc_store(alias: &str, suffix: &str) -> Arc<DocumentStoreAdapter> {
        let temp_dir = std::env::temp_dir().join(format!("aifastdb_test_{}", suffix));
        let _ = std::fs::create_dir_all(&temp_dir);
        let doc_path = temp_dir.join("docs.jsonl");
        
        Arc::new(
            DocumentStoreAdapter::open(alias.to_string(), doc_path.to_str().unwrap())
                .expect("Failed to open test document store")
        )
    }
    
    /// Helper: cleanup temp dir
    fn cleanup_test_dir(suffix: &str) {
        let temp_dir = std::env::temp_dir().join(format!("aifastdb_test_{}", suffix));
        let _ = std::fs::remove_dir_all(&temp_dir);
    }
    
    #[test]
    fn test_federation_lifecycle() {
        let suffix = "fed_lifecycle";
        cleanup_test_dir(suffix);
        
        let fed = Federation::new(FederationConfig::default());
        let doc_store = create_test_doc_store("test_docs", suffix);
        
        fed.register_document("test_docs", doc_store).unwrap();
        
        // List stores
        let stores = fed.list_stores().unwrap();
        assert_eq!(stores.len(), 1);
        assert_eq!(stores[0].0, "test_docs");
        assert_eq!(stores[0].1, StoreType::Document);
        
        // Get store
        let store = fed.use_doc("test_docs").unwrap();
        assert_eq!(store.alias(), "test_docs");
        
        // Close
        fed.close().unwrap();
        cleanup_test_dir(suffix);
    }
    
    #[test]
    fn test_document_crud() {
        let suffix = "fed_crud";
        cleanup_test_dir(suffix);
        
        let fed = Federation::with_defaults();
        let doc_store = create_test_doc_store("docs", suffix);
        fed.register_document("docs", doc_store).unwrap();
        
        // Create document
        let result = fed.write("docs", WriteOp::CreateDocument {
            id: "doc1".to_string(),
            doc_type: "text".to_string(),
            content: serde_json::json!({
                "text": "This is a test article.",
                "metadata": { "title": "Test Article" }
            }),
            tags: vec!["test".to_string()],
            owner_id: None,
            visibility: None,
        }).unwrap();
        
        assert!(matches!(result, crate::federation::types::WriteResult::Created(_)));
        
        // Get by id
        let record = fed.use_store("docs").unwrap().get("doc1").unwrap();
        assert!(record.is_some());
        
        // Query documents
        let query = UniversalQuery {
            target: "text".to_string(),
            filters: vec![],
            limit: Some(10),
            offset: None,
            order_by: None,
            traverse: None,
            vector_search: None,
        };
        
        let result = fed.query("docs", &query).unwrap();
        assert_eq!(result.records.len(), 1);
        
        // Update document
        fed.write("docs", WriteOp::UpdateDocument {
            id: "doc1".to_string(),
            content: Some(serde_json::json!({
                "text": "Updated article content."
            })),
            tags: None,
            caller_id: None,
        }).unwrap();
        
        // Delete document
        fed.write("docs", WriteOp::DeleteDocument {
            id: "doc1".to_string(),
            caller_id: None,
        }).unwrap();
        
        // Query again - should be empty
        let result = fed.query("docs", &query).unwrap();
        assert_eq!(result.records.len(), 0);
        
        cleanup_test_dir(suffix);
    }
    
    #[test]
    fn test_batch_write() {
        let suffix = "fed_batch";
        cleanup_test_dir(suffix);
        
        let fed = Federation::with_defaults();
        let doc_store = create_test_doc_store("batch_docs", suffix);
        fed.register_document("batch_docs", doc_store).unwrap();
        
        // Batch create documents
        let ops: Vec<WriteOp> = (0..100).map(|i| WriteOp::CreateDocument {
            id: format!("doc_{}", i),
            doc_type: "text".to_string(),
            content: serde_json::json!({ "text": format!("Item {}", i) }),
            tags: vec!["batch".to_string()],
            owner_id: None,
            visibility: None,
        }).collect();
        
        let result = fed.batch_write("batch_docs", ops).unwrap();
        
        assert_eq!(result.success_count, 100);
        assert_eq!(result.failed_count, 0);
        
        cleanup_test_dir(suffix);
    }
    
    #[test]
    fn test_store_not_found() {
        let fed = Federation::with_defaults();
        
        let result = fed.use_store("nonexistent");
        assert!(result.is_err());
        
        let result = fed.use_graph("nonexistent");
        assert!(result.is_err());
        
        let result = fed.use_doc("nonexistent");
        assert!(result.is_err());
    }
    
    #[test]
    fn test_duplicate_registration() {
        let suffix1 = "fed_dup1";
        let suffix2 = "fed_dup2";
        cleanup_test_dir(suffix1);
        cleanup_test_dir(suffix2);
        
        let fed = Federation::with_defaults();
        
        let doc_store1 = create_test_doc_store("dup", suffix1);
        let doc_store2 = create_test_doc_store("dup", suffix2);
        
        fed.register_document("dup", doc_store1).unwrap();
        
        // Should fail - duplicate alias
        let result = fed.register_document("dup", doc_store2);
        assert!(result.is_err());
        
        cleanup_test_dir(suffix1);
        cleanup_test_dir(suffix2);
    }
}

#[cfg(test)]
mod type_tests {
    use crate::federation::types::*;
    
    #[test]
    fn test_store_type_display() {
        assert_eq!(StoreType::Graph.to_string(), "graph");
        assert_eq!(StoreType::Document.to_string(), "document");
        assert_eq!(StoreType::Vector.to_string(), "vector");
    }
    
    #[test]
    fn test_write_op_compatibility() {
        let create_entity = WriteOp::CreateEntity {
            entity_type: "person".to_string(),
            id: "p1".to_string(),
            name: "Test".to_string(),
            attributes: Default::default(),
            owner_id: None,
            visibility: None,
        };
        
        assert!(create_entity.is_compatible_with(StoreType::Graph));
        assert!(!create_entity.is_compatible_with(StoreType::Document));
        assert!(!create_entity.is_compatible_with(StoreType::Vector));
        
        let create_doc = WriteOp::CreateDocument {
            id: "d1".to_string(),
            doc_type: "text".to_string(),
            content: serde_json::json!({}),
            tags: vec![],
            owner_id: None,
            visibility: None,
        };
        
        assert!(!create_doc.is_compatible_with(StoreType::Graph));
        assert!(create_doc.is_compatible_with(StoreType::Document));
        assert!(!create_doc.is_compatible_with(StoreType::Vector));
    }
    
    #[test]
    fn test_record_store_type() {
        let entity = Record::GraphEntity {
            id: "e1".to_string(),
            entity_type: "person".to_string(),
            name: "Test".to_string(),
            properties: Default::default(),
            created_at: 0,
            ownership: None,
        };
        
        assert_eq!(entity.store_type(), StoreType::Graph);
        
        let doc = Record::Document {
            id: "d1".to_string(),
            doc_type: "text".to_string(),
            content: serde_json::json!({}),
            tags: vec![],
            created_at: 0,
            updated_at: 0,
            ownership: None,
        };
        
        assert_eq!(doc.store_type(), StoreType::Document);
    }
    
    #[test]
    fn test_join_strategy() {
        let strategy = JoinStrategy::HashJoin {
            build_side: "left".to_string(),
        };
        
        if let JoinStrategy::HashJoin { build_side } = strategy {
            assert_eq!(build_side, "left");
        } else {
            panic!("Expected HashJoin");
        }
    }
}

#[cfg(test)]
mod index_tests {
    use crate::federation::cross_db_index::CrossDbIndex;
    use crate::federation::types::{StoreType, TypeCrossRelation};
    
    #[test]
    fn test_entity_index() {
        let index = CrossDbIndex::new();
        
        index.index_entity("user1", "social", StoreType::Graph).unwrap();
        index.index_entity("doc1", "docs", StoreType::Document).unwrap();
        
        let loc = index.find_entity("user1").unwrap().unwrap();
        assert_eq!(loc.store_alias, "social");
        assert_eq!(loc.store_type, StoreType::Graph);
        
        let loc = index.find_entity("doc1").unwrap().unwrap();
        assert_eq!(loc.store_alias, "docs");
        assert_eq!(loc.store_type, StoreType::Document);
        
        let graph_entities = index.find_by_type(StoreType::Graph).unwrap();
        assert_eq!(graph_entities.len(), 1);
        
        index.remove_entity("user1").unwrap();
        assert!(index.find_entity("user1").unwrap().is_none());
    }
    
    #[test]
    fn test_cross_type_relations() {
        let index = CrossDbIndex::new();
        
        index.index_type_cross_relation("person1", TypeCrossRelation {
            source_id: "person1".to_string(),
            source_type: StoreType::Graph,
            target_id: "resume1".to_string(),
            target_type: StoreType::Document,
            relation_type: "has_resume".to_string(),
        }).unwrap();
        
        index.index_type_cross_relation("person1", TypeCrossRelation {
            source_id: "person1".to_string(),
            source_type: StoreType::Graph,
            target_id: "pic1".to_string(),
            target_type: StoreType::Vector,
            relation_type: "has_avatar".to_string(),
        }).unwrap();
        
        let refs = index.find_type_cross_from("person1").unwrap();
        assert_eq!(refs.len(), 2);
        
        let refs_to = index.find_refs_to("resume1").unwrap();
        assert_eq!(refs_to.len(), 1);
        assert_eq!(refs_to[0], "person1");
    }
    
    #[test]
    fn test_index_stats() {
        let index = CrossDbIndex::new();
        
        for i in 0..100 {
            index.index_entity(&format!("entity_{}", i), "store1", StoreType::Graph).unwrap();
        }
        
        let stats = index.stats();
        assert_eq!(stats.entity_count, 100);
    }
}

#[cfg(test)]
mod transaction_tests {
    use crate::federation::transaction::{TransactionCoordinator, TransactionState};
    
    #[test]
    fn test_transaction_begin_rollback() {
        let coordinator = TransactionCoordinator::new();
        
        let tx_id = coordinator.begin().unwrap();
        assert!(tx_id > 0);
        
        let state = coordinator.get_state(tx_id).unwrap();
        assert_eq!(state, Some(TransactionState::Active));
        
        coordinator.rollback(tx_id).unwrap();
        
        let state = coordinator.get_state(tx_id).unwrap();
        assert_eq!(state, None);
    }
    
    #[test]
    fn test_multiple_transactions() {
        let coordinator = TransactionCoordinator::new();
        
        let tx1 = coordinator.begin().unwrap();
        let tx2 = coordinator.begin().unwrap();
        let tx3 = coordinator.begin().unwrap();
        
        assert_ne!(tx1, tx2);
        assert_ne!(tx2, tx3);
        
        let active = coordinator.list_active().unwrap();
        assert_eq!(active.len(), 3);
        
        coordinator.rollback(tx2).unwrap();
        
        let active = coordinator.list_active().unwrap();
        assert_eq!(active.len(), 2);
    }
    
    #[test]
    fn test_recovery() {
        let coordinator = TransactionCoordinator::new();
        
        coordinator.begin().unwrap();
        coordinator.begin().unwrap();
        
        let result = coordinator.recover().unwrap();
        // Active transactions found in log
        assert!(result.aborted.is_empty() || !result.aborted.is_empty());
    }
}

#[cfg(test)]
mod query_planner_tests {
    use crate::federation::query_planner::QueryPlanner;
    use crate::federation::types::{StoreType, UniversalQuery};
    
    #[test]
    fn test_single_store_plan() {
        let mut planner = QueryPlanner::new();
        planner.update_stats("social", 10000, 200, true);
        
        let query = UniversalQuery {
            target: "person".to_string(),
            filters: vec![],
            limit: Some(10),
            offset: None,
            order_by: None,
            traverse: None,
            vector_search: None,
        };
        
        let plan = planner.plan_single("social", StoreType::Graph, &query).unwrap();
        
        assert!(plan.estimated_cost > 0.0);
        assert_eq!(plan.stores_involved.len(), 1);
        assert_eq!(plan.stores_involved[0], "social");
    }
    
    #[test]
    fn test_join_strategy_selection() {
        let planner = QueryPlanner::new();
        
        let strategy = planner.analyze_cross_type_join(
            StoreType::Graph,
            StoreType::Graph,
            1000,
            1000,
            true,
        );
        
        assert!(matches!(strategy, crate::federation::types::JoinStrategy::DirectJoin { .. }));
        
        let strategy = planner.analyze_cross_type_join(
            StoreType::Graph,
            StoreType::Document,
            100,
            100,
            false,
        );
        
        assert!(matches!(strategy, crate::federation::types::JoinStrategy::NestedLoop { .. }));
    }
}

// ===========================================================================
// T8C.7: Adapter Unit Tests (GraphStoreAdapter + DocumentStoreAdapter CRUD)
// ===========================================================================
#[cfg(test)]
mod adapter_tests {
    use std::sync::Arc;
    use crate::federation::{
        Store,
        adapters::{DocumentStoreAdapter, GraphStoreAdapter},
        types::{StoreType, WriteOp, WriteResult, UniversalQuery, Record, Filter},
    };
    use crate::social::SocialStoreV2;

    fn sample_query(target: &str) -> UniversalQuery {
        UniversalQuery {
            target: target.to_string(),
            filters: vec![],
            limit: None,
            offset: None,
            order_by: None,
            traverse: None,
            vector_search: None,
        }
    }

    // --- DocumentStoreAdapter tests ---

    fn create_test_doc_adapter(suffix: &str) -> (Arc<DocumentStoreAdapter>, std::path::PathBuf) {
        let temp_dir = std::env::temp_dir().join(format!("aifastdb_t8c7_doc_{}", suffix));
        let _ = std::fs::remove_dir_all(&temp_dir);
        std::fs::create_dir_all(&temp_dir).unwrap();
        let path = temp_dir.join("docs.jsonl");
        let adapter = DocumentStoreAdapter::open("doc_test".to_string(), path.to_str().unwrap()).unwrap();
        (Arc::new(adapter), temp_dir)
    }

    #[test]
    fn test_doc_adapter_store_type_and_alias() {
        let (adapter, dir) = create_test_doc_adapter("type_alias");
        assert_eq!(adapter.store_type(), StoreType::Document);
        assert_eq!(adapter.alias(), "doc_test");
        let _ = std::fs::remove_dir_all(&dir);
    }

    #[test]
    fn test_doc_adapter_create_get_exists_delete() {
        let (adapter, dir) = create_test_doc_adapter("crud");

        // Create
        let res = adapter.write(WriteOp::CreateDocument {
            id: "d1".into(),
            doc_type: "text".into(),
            content: serde_json::json!({"text": "hello world", "importance": 0.8}),
            tags: vec!["greeting".into()],
            owner_id: None,
            visibility: None,
        }).unwrap();
        assert!(matches!(res, WriteResult::Created(_)));

        // Exists
        assert!(adapter.exists("d1").unwrap());
        assert!(!adapter.exists("nonexistent").unwrap());

        // Get
        let rec = adapter.get("d1").unwrap().unwrap();
        if let Record::Document { id, doc_type, tags, .. } = &rec {
            assert_eq!(id, "d1");
            assert_eq!(doc_type, "text");
            assert!(tags.contains(&"greeting".to_string()));
        } else {
            panic!("Expected Record::Document");
        }

        // Delete
        let res = adapter.write(WriteOp::DeleteDocument { id: "d1".into(), caller_id: None }).unwrap();
        assert!(matches!(res, WriteResult::Deleted(_)));
        assert!(!adapter.exists("d1").unwrap());

        // Delete non-existent → NoOp
        let res = adapter.write(WriteOp::DeleteDocument { id: "d1".into(), caller_id: None }).unwrap();
        assert!(matches!(res, WriteResult::NoOp));

        let _ = std::fs::remove_dir_all(&dir);
    }

    #[test]
    fn test_doc_adapter_update() {
        let (adapter, dir) = create_test_doc_adapter("update");

        adapter.write(WriteOp::CreateDocument {
            id: "d_upd".into(),
            doc_type: "text".into(),
            content: serde_json::json!({"text": "original"}),
            tags: vec!["old".into()],
            owner_id: None,
            visibility: None,
        }).unwrap();

        adapter.write(WriteOp::UpdateDocument {
            id: "d_upd".into(),
            content: Some(serde_json::json!({"text": "updated"})),
            tags: Some(vec!["new".into()]),
            caller_id: None,
        }).unwrap();

        let rec = adapter.get("d_upd").unwrap().unwrap();
        if let Record::Document { tags, .. } = &rec {
            assert!(tags.contains(&"new".to_string()));
        }

        let _ = std::fs::remove_dir_all(&dir);
    }

    #[test]
    fn test_doc_adapter_query_with_filter() {
        let (adapter, dir) = create_test_doc_adapter("filter");

        for i in 0..5 {
            adapter.write(WriteOp::CreateDocument {
                id: format!("f_{}", i),
                doc_type: "text".into(),
                content: serde_json::json!({"text": format!("item {}", i), "importance": (i as f64) * 0.2}),
                tags: vec![format!("tag_{}", i % 2)],
                owner_id: None,
                visibility: None,
            }).unwrap();
        }

        // Query all
        let all = adapter.query(&sample_query("text")).unwrap();
        assert_eq!(all.len(), 5);

        // Query with limit
        let q = UniversalQuery {
            target: "text".into(),
            limit: Some(2),
            ..sample_query("text")
        };
        let limited = adapter.query(&q).unwrap();
        assert_eq!(limited.len(), 2);

        // Query with offset
        let q = UniversalQuery {
            target: "text".into(),
            offset: Some(3),
            ..sample_query("text")
        };
        let offset = adapter.query(&q).unwrap();
        assert_eq!(offset.len(), 2);

        let _ = std::fs::remove_dir_all(&dir);
    }

    #[test]
    fn test_doc_adapter_batch_write() {
        let (adapter, dir) = create_test_doc_adapter("batch");

        let ops: Vec<WriteOp> = (0..20).map(|i| WriteOp::CreateDocument {
            id: format!("b_{}", i),
            doc_type: "text".into(),
            content: serde_json::json!({"text": format!("batch {}", i)}),
            tags: vec![],
            owner_id: None,
            visibility: None,
        }).collect();

        let result = adapter.batch_write(ops).unwrap();
        assert_eq!(result.success_count, 20);
        assert_eq!(result.failed_count, 0);

        let stats = adapter.stats();
        assert_eq!(stats.record_count, 20);

        let _ = std::fs::remove_dir_all(&dir);
    }

    #[test]
    fn test_doc_adapter_unsupported_ops() {
        let (adapter, dir) = create_test_doc_adapter("unsupported");

        // Graph op on doc store → error
        let res = adapter.write(WriteOp::CreateEntity {
            entity_type: "person".into(),
            id: "p1".into(),
            name: "test".into(),
            attributes: Default::default(),
            owner_id: None,
            visibility: None,
        });
        assert!(res.is_err());

        let _ = std::fs::remove_dir_all(&dir);
    }

    #[test]
    fn test_doc_adapter_query_iter() {
        let (adapter, dir) = create_test_doc_adapter("iter");

        for i in 0..3 {
            adapter.write(WriteOp::CreateDocument {
                id: format!("it_{}", i),
                doc_type: "text".into(),
                content: serde_json::json!({"text": format!("iter {}", i)}),
                tags: vec![],
                owner_id: None,
                visibility: None,
            }).unwrap();
        }

        let iter = adapter.query_iter(&sample_query("text")).unwrap();
        let collected: Vec<_> = iter.collect::<std::result::Result<Vec<_>, _>>().unwrap();
        assert_eq!(collected.len(), 3);

        let _ = std::fs::remove_dir_all(&dir);
    }

    #[test]
    fn test_doc_adapter_sync_close() {
        let (adapter, dir) = create_test_doc_adapter("sync_close");

        adapter.write(WriteOp::CreateDocument {
            id: "sc1".into(),
            doc_type: "text".into(),
            content: serde_json::json!({"text": "sync test"}),
            tags: vec![],
            owner_id: None,
            visibility: None,
        }).unwrap();

        adapter.sync().unwrap();
        adapter.close().unwrap();

        let _ = std::fs::remove_dir_all(&dir);
    }

    #[test]
    fn test_doc_adapter_local_transaction() {
        let (adapter, dir) = create_test_doc_adapter("local_tx");

        // Begin transaction
        let mut tx = adapter.begin_local_tx().unwrap();
        assert_eq!(tx.store_alias(), "doc_test");

        tx.write(WriteOp::CreateDocument {
            id: "tx_d1".into(),
            doc_type: "text".into(),
            content: serde_json::json!({"text": "tx content"}),
            tags: vec![],
            owner_id: None,
            visibility: None,
        }).unwrap();

        // Prepare
        assert!(tx.prepare().unwrap());

        // Commit
        tx.commit().unwrap();

        // Verify committed
        assert!(adapter.exists("tx_d1").unwrap());

        let _ = std::fs::remove_dir_all(&dir);
    }

    #[test]
    fn test_doc_adapter_local_transaction_rollback() {
        let (adapter, dir) = create_test_doc_adapter("local_tx_rb");

        let mut tx = adapter.begin_local_tx().unwrap();
        tx.write(WriteOp::CreateDocument {
            id: "tx_rb".into(),
            doc_type: "text".into(),
            content: serde_json::json!({"text": "rollback"}),
            tags: vec![],
            owner_id: None,
            visibility: None,
        }).unwrap();

        tx.rollback().unwrap();

        // Should NOT exist after rollback
        assert!(!adapter.exists("tx_rb").unwrap());

        let _ = std::fs::remove_dir_all(&dir);
    }

    // --- GraphStoreAdapter tests ---

    fn create_test_graph_adapter(suffix: &str) -> (Arc<GraphStoreAdapter>, std::path::PathBuf) {
        let temp_dir = std::env::temp_dir().join(format!("aifastdb_t8c7_graph_{}", suffix));
        let _ = std::fs::remove_dir_all(&temp_dir);
        std::fs::create_dir_all(&temp_dir).unwrap();
        let store = SocialStoreV2::open(&temp_dir);
        let adapter = GraphStoreAdapter::new("graph_test".to_string(), Arc::new(store));
        (Arc::new(adapter), temp_dir)
    }

    #[test]
    fn test_graph_adapter_store_type_and_alias() {
        let (adapter, dir) = create_test_graph_adapter("type_alias");
        assert_eq!(adapter.store_type(), StoreType::Graph);
        assert_eq!(adapter.alias(), "graph_test");
        let _ = std::fs::remove_dir_all(&dir);
    }

    #[test]
    fn test_graph_adapter_entity_crud() {
        let (adapter, dir) = create_test_graph_adapter("entity_crud");

        // Create entity
        let res = adapter.write(WriteOp::CreateEntity {
            entity_type: "person".into(),
            id: "p1".into(),
            name: "Alice".into(),
            attributes: [("age".to_string(), serde_json::json!(30))].into_iter().collect(),
            owner_id: None,
            visibility: None,
        }).unwrap();
        assert!(matches!(res, WriteResult::Created(_)));

        // Exists
        assert!(adapter.exists("p1").unwrap());

        // Get
        let rec = adapter.get("p1").unwrap();
        assert!(rec.is_some());
        if let Some(Record::GraphEntity { id, name, entity_type, .. }) = &rec {
            assert_eq!(id, "p1");
            assert_eq!(name, "Alice");
            assert_eq!(entity_type, "person");
        }

        // Delete
        let res = adapter.write(WriteOp::DeleteEntity { id: "p1".into(), caller_id: None }).unwrap();
        assert!(matches!(res, WriteResult::Deleted(_)));
        assert!(!adapter.exists("p1").unwrap());

        // Delete non-existent → NoOp
        let res = adapter.write(WriteOp::DeleteEntity { id: "p1".into(), caller_id: None }).unwrap();
        assert!(matches!(res, WriteResult::NoOp));

        let _ = std::fs::remove_dir_all(&dir);
    }

    #[test]
    fn test_graph_adapter_relation_crud() {
        let (adapter, dir) = create_test_graph_adapter("relation_crud");

        // Create two entities
        adapter.write(WriteOp::CreateEntity {
            entity_type: "person".into(), id: "r_a".into(), name: "A".into(),
            attributes: Default::default(),
            owner_id: None,
            visibility: None,
        }).unwrap();
        adapter.write(WriteOp::CreateEntity {
            entity_type: "person".into(), id: "r_b".into(), name: "B".into(),
            attributes: Default::default(),
            owner_id: None,
            visibility: None,
        }).unwrap();

        // Create relation
        let res = adapter.write(WriteOp::CreateRelation {
            source_id: "r_a".into(),
            target_id: "r_b".into(),
            relation_type: "follows".into(),
            attributes: Default::default(),
            owner_id: None,
        }).unwrap();
        assert!(matches!(res, WriteResult::Created(_)));

        let stats = adapter.stats();
        assert!(stats.relation_count.unwrap_or(0) >= 1);

        let _ = std::fs::remove_dir_all(&dir);
    }

    #[test]
    fn test_graph_adapter_query_persons() {
        let (adapter, dir) = create_test_graph_adapter("query_persons");

        for i in 0..5 {
            adapter.write(WriteOp::CreateEntity {
                entity_type: "person".into(),
                id: format!("qp_{}", i),
                name: format!("Person_{}", i),
                attributes: Default::default(),
                owner_id: None,
                visibility: None,
            }).unwrap();
        }

        let results = adapter.query(&sample_query("person")).unwrap();
        assert_eq!(results.len(), 5);

        // With limit
        let q = UniversalQuery {
            target: "person".into(),
            limit: Some(3),
            ..sample_query("person")
        };
        assert_eq!(adapter.query(&q).unwrap().len(), 3);

        let _ = std::fs::remove_dir_all(&dir);
    }

    #[test]
    fn test_graph_adapter_batch_write() {
        let (adapter, dir) = create_test_graph_adapter("batch");

        let ops: Vec<WriteOp> = (0..10).map(|i| WriteOp::CreateEntity {
            entity_type: "person".into(),
            id: format!("bp_{}", i),
            name: format!("Batch_{}", i),
            attributes: Default::default(),
            owner_id: None,
            visibility: None,
        }).collect();

        let result = adapter.batch_write(ops).unwrap();
        assert_eq!(result.success_count, 10);
        assert_eq!(result.failed_count, 0);

        let _ = std::fs::remove_dir_all(&dir);
    }

    #[test]
    fn test_graph_adapter_unsupported_ops() {
        let (adapter, dir) = create_test_graph_adapter("unsupported");

        let res = adapter.write(WriteOp::CreateDocument {
            id: "d1".into(),
            doc_type: "text".into(),
            content: serde_json::json!({}),
            tags: vec![],
            owner_id: None,
            visibility: None,
        });
        assert!(res.is_err());

        let _ = std::fs::remove_dir_all(&dir);
    }

    #[test]
    fn test_graph_adapter_local_transaction() {
        let (adapter, dir) = create_test_graph_adapter("local_tx");

        let mut tx = adapter.begin_local_tx().unwrap();
        tx.write(WriteOp::CreateEntity {
            entity_type: "person".into(),
            id: "tx_p1".into(),
            name: "TxPerson".into(),
            attributes: Default::default(),
            owner_id: None,
            visibility: None,
        }).unwrap();

        assert!(tx.prepare().unwrap());
        tx.commit().unwrap();
        assert!(adapter.exists("tx_p1").unwrap());

        let _ = std::fs::remove_dir_all(&dir);
    }

    #[test]
    fn test_graph_adapter_stats() {
        let (adapter, dir) = create_test_graph_adapter("stats");

        for i in 0..3 {
            adapter.write(WriteOp::CreateEntity {
                entity_type: "person".into(),
                id: format!("st_{}", i),
                name: format!("S_{}", i),
                attributes: Default::default(),
                owner_id: None,
                visibility: None,
            }).unwrap();
        }

        let stats = adapter.stats();
        assert_eq!(stats.record_count, 3);

        let _ = std::fs::remove_dir_all(&dir);
    }
}

// ===========================================================================
// T8C.8: Federation Integration Tests
// ===========================================================================
#[cfg(test)]
mod federation_integration_tests {
    use std::sync::Arc;
    use crate::federation::{
        Federation, FederationConfig, Store,
        adapters::DocumentStoreAdapter,
        types::{StoreType, WriteOp, UniversalQuery, WriteResult},
    };

    fn setup_federation(suffix: &str) -> (Federation, std::path::PathBuf) {
        let temp_dir = std::env::temp_dir().join(format!("aifastdb_t8c8_{}", suffix));
        let _ = std::fs::remove_dir_all(&temp_dir);
        std::fs::create_dir_all(&temp_dir).unwrap();

        let fed = Federation::new(FederationConfig::default());

        let doc_path = temp_dir.join("docs.jsonl");
        let doc_store = Arc::new(
            DocumentStoreAdapter::open("docs".into(), doc_path.to_str().unwrap()).unwrap()
        );
        fed.register_document("docs", doc_store).unwrap();

        let doc_path2 = temp_dir.join("notes.jsonl");
        let note_store = Arc::new(
            DocumentStoreAdapter::open("notes".into(), doc_path2.to_str().unwrap()).unwrap()
        );
        fed.register_document("notes", note_store).unwrap();

        (fed, temp_dir)
    }

    fn sample_query(target: &str) -> UniversalQuery {
        UniversalQuery {
            target: target.into(),
            filters: vec![],
            limit: None, offset: None, order_by: None,
            traverse: None, vector_search: None,
        }
    }

    #[test]
    fn test_multi_store_write_and_query() {
        let (fed, dir) = setup_federation("multi_wr");

        fed.write("docs", WriteOp::CreateDocument {
            id: "d1".into(), doc_type: "text".into(),
            content: serde_json::json!({"text": "doc content"}),
            tags: vec!["doc".into()],
            owner_id: None,
            visibility: None,
        }).unwrap();

        fed.write("notes", WriteOp::CreateDocument {
            id: "n1".into(), doc_type: "text".into(),
            content: serde_json::json!({"text": "note content"}),
            tags: vec!["note".into()],
            owner_id: None,
            visibility: None,
        }).unwrap();

        let doc_result = fed.query("docs", &sample_query("text")).unwrap();
        assert_eq!(doc_result.records.len(), 1);

        let note_result = fed.query("notes", &sample_query("text")).unwrap();
        assert_eq!(note_result.records.len(), 1);

        let _ = std::fs::remove_dir_all(&dir);
    }

    #[test]
    fn test_query_multi() {
        let (fed, dir) = setup_federation("qmulti");

        fed.write("docs", WriteOp::CreateDocument {
            id: "qm1".into(), doc_type: "text".into(),
            content: serde_json::json!({"text": "A"}), tags: vec![],
            owner_id: None,
            visibility: None,
        }).unwrap();
        fed.write("notes", WriteOp::CreateDocument {
            id: "qm2".into(), doc_type: "text".into(),
            content: serde_json::json!({"text": "B"}), tags: vec![],
            owner_id: None,
            visibility: None,
        }).unwrap();

        let q = sample_query("text");
        let results = fed.query_multi(vec![("docs", &q), ("notes", &q)]).unwrap();
        assert_eq!(results.len(), 2);
        assert_eq!(results[0].records.len(), 1);
        assert_eq!(results[1].records.len(), 1);

        let _ = std::fs::remove_dir_all(&dir);
    }

    #[test]
    fn test_sync_all_and_close() {
        let (fed, dir) = setup_federation("sync_close");

        fed.write("docs", WriteOp::CreateDocument {
            id: "sc1".into(), doc_type: "text".into(),
            content: serde_json::json!({"text": "x"}), tags: vec![],
            owner_id: None,
            visibility: None,
        }).unwrap();

        fed.sync_all().unwrap();
        fed.close().unwrap();

        let _ = std::fs::remove_dir_all(&dir);
    }

    #[test]
    fn test_error_propagation_store_not_found() {
        let fed = Federation::with_defaults();

        // Write to non-existent store
        let res = fed.write("ghost", WriteOp::CreateDocument {
            id: "x".into(), doc_type: "t".into(),
            content: serde_json::json!({}), tags: vec![],
            owner_id: None,
            visibility: None,
        });
        assert!(res.is_err());

        // Query non-existent store
        let res = fed.query("ghost", &sample_query("*"));
        assert!(res.is_err());

        // Batch write to non-existent
        let res = fed.batch_write("ghost", vec![]);
        assert!(res.is_err());
    }

    #[test]
    fn test_unregister_store() {
        let (fed, dir) = setup_federation("unreg");

        assert_eq!(fed.list_stores().unwrap().len(), 2);
        fed.unregister("docs").unwrap();
        assert_eq!(fed.list_stores().unwrap().len(), 1);

        // Access removed store → error
        assert!(fed.use_doc("docs").is_err());

        let _ = std::fs::remove_dir_all(&dir);
    }

    #[test]
    fn test_query_stream() {
        let (fed, dir) = setup_federation("stream");

        for i in 0..5 {
            fed.write("docs", WriteOp::CreateDocument {
                id: format!("s_{}", i), doc_type: "text".into(),
                content: serde_json::json!({"text": format!("stream {}", i)}),
                tags: vec![],
                owner_id: None,
                visibility: None,
            }).unwrap();
        }

        let iter = fed.query_stream("docs", &sample_query("text")).unwrap();
        let collected: Vec<_> = iter.collect::<Result<Vec<_>, _>>().unwrap();
        assert_eq!(collected.len(), 5);

        let _ = std::fs::remove_dir_all(&dir);
    }

    #[test]
    fn test_rebuild_index() {
        let (fed, dir) = setup_federation("rebuild_idx");
        // rebuild_index should not panic even with empty stores
        fed.rebuild_index().unwrap();
        let stats = fed.index_stats();
        // Initially empty — just verify it works
        assert_eq!(stats.entity_count, 0);

        let _ = std::fs::remove_dir_all(&dir);
    }
}

// ===========================================================================
// T8C.9: Index Performance Tests
// ===========================================================================
#[cfg(test)]
mod index_performance_tests {
    use crate::federation::cross_db_index::CrossDbIndex;
    use crate::federation::types::{StoreType, TypeCrossRelation};

    #[test]
    fn test_bulk_entity_index_and_lookup() {
        let index = CrossDbIndex::new();
        let count = 10_000;

        let start = std::time::Instant::now();
        for i in 0..count {
            index.index_entity(
                &format!("entity_{}", i),
                &format!("store_{}", i % 3),
                if i % 2 == 0 { StoreType::Graph } else { StoreType::Document },
            ).unwrap();
        }
        let index_time = start.elapsed();

        let stats = index.stats();
        assert_eq!(stats.entity_count, count);

        // Lookup performance
        let start = std::time::Instant::now();
        for i in 0..count {
            let loc = index.find_entity(&format!("entity_{}", i)).unwrap();
            assert!(loc.is_some());
        }
        let lookup_time = start.elapsed();

        // Bulk lookup should be fast (< 1s for 10k)
        assert!(lookup_time.as_secs() < 2, "Lookup took too long: {:?}", lookup_time);

        // Type-based lookup
        let start = std::time::Instant::now();
        let graph_entities = index.find_by_type(StoreType::Graph).unwrap();
        let type_time = start.elapsed();
        assert_eq!(graph_entities.len(), count / 2);
        assert!(type_time.as_secs() < 1, "Type lookup took too long: {:?}", type_time);

        eprintln!(
            "[index_perf] {} entities: index={:?} lookup={:?} type_scan={:?}",
            count, index_time, lookup_time, type_time
        );
    }

    #[test]
    fn test_bulk_cross_type_relations() {
        let index = CrossDbIndex::new();
        let count = 5_000;

        let start = std::time::Instant::now();
        for i in 0..count {
            index.index_type_cross_relation(
                &format!("src_{}", i),
                TypeCrossRelation {
                    source_id: format!("src_{}", i),
                    source_type: StoreType::Graph,
                    target_id: format!("tgt_{}", i),
                    target_type: StoreType::Document,
                    relation_type: "linked".into(),
                },
            ).unwrap();
        }
        let index_time = start.elapsed();

        let stats = index.stats();
        assert_eq!(stats.type_cross_relation_count, count);

        // Lookup from
        let start = std::time::Instant::now();
        for i in 0..count {
            let refs = index.find_type_cross_from(&format!("src_{}", i)).unwrap();
            assert_eq!(refs.len(), 1);
        }
        let from_time = start.elapsed();

        // Reverse lookup
        let start = std::time::Instant::now();
        for i in 0..count {
            let refs = index.find_refs_to(&format!("tgt_{}", i)).unwrap();
            assert_eq!(refs.len(), 1);
        }
        let to_time = start.elapsed();

        assert!(from_time.as_secs() < 2, "from_lookup took too long: {:?}", from_time);
        assert!(to_time.as_secs() < 2, "to_lookup took too long: {:?}", to_time);

        eprintln!(
            "[cross_type_perf] {} relations: index={:?} from_lookup={:?} to_lookup={:?}",
            count, index_time, from_time, to_time
        );
    }

    #[test]
    fn test_index_persist_restore_performance() {
        let index = CrossDbIndex::new();
        let count = 5_000;

        for i in 0..count {
            index.index_entity(&format!("e_{}", i), "s1", StoreType::Graph).unwrap();
        }

        let temp = std::env::temp_dir().join("aifastdb_t8c9_persist_perf");
        let _ = std::fs::remove_dir_all(&temp);
        std::fs::create_dir_all(&temp).unwrap();
        let path = temp.join("index.json");

        let start = std::time::Instant::now();
        index.persist(&path).unwrap();
        let persist_time = start.elapsed();

        let start = std::time::Instant::now();
        let restored = CrossDbIndex::restore(&path).unwrap();
        let restore_time = start.elapsed();

        let stats = restored.stats();
        assert_eq!(stats.entity_count, count);

        eprintln!(
            "[persist_perf] {} entities: persist={:?} restore={:?}",
            count, persist_time, restore_time
        );

        let _ = std::fs::remove_dir_all(&temp);
    }
}

// ===========================================================================
// T8C.10: Query Optimization Tests
// ===========================================================================
#[cfg(test)]
mod query_optimization_tests {
    use crate::federation::query_planner::QueryPlanner;
    use crate::federation::types::*;

    fn sample_query(target: &str) -> UniversalQuery {
        UniversalQuery {
            target: target.into(),
            filters: vec![],
            limit: None, offset: None, order_by: None,
            traverse: None, vector_search: None,
        }
    }

    #[test]
    fn test_cost_estimation_increases_with_data() {
        let mut planner = QueryPlanner::new();

        planner.update_stats("small", 100, 100, false);
        let plan_small = planner.plan_single("small", StoreType::Document, &sample_query("*")).unwrap();

        planner.update_stats("large", 100_000, 100, false);
        let plan_large = planner.plan_single("large", StoreType::Document, &sample_query("*")).unwrap();

        assert!(plan_large.estimated_cost > plan_small.estimated_cost,
            "Larger store should have higher cost: small={} large={}",
            plan_small.estimated_cost, plan_large.estimated_cost);
    }

    #[test]
    fn test_join_strategy_same_type_with_index() {
        let planner = QueryPlanner::new();

        // Same type + shared index → DirectJoin
        let strategy = planner.analyze_cross_type_join(
            StoreType::Graph, StoreType::Graph, 1000, 1000, true,
        );
        assert!(matches!(strategy, JoinStrategy::DirectJoin { .. }),
            "Same type with index should use DirectJoin, got {:?}", strategy);
    }

    #[test]
    fn test_join_strategy_cross_type_with_index() {
        let planner = QueryPlanner::new();

        // Different types + shared index → IndexLookup
        let strategy = planner.analyze_cross_type_join(
            StoreType::Graph, StoreType::Document, 1000, 1000, true,
        );
        assert!(matches!(strategy, JoinStrategy::IndexLookup { .. }),
            "Cross type with index should use IndexLookup, got {:?}", strategy);
    }

    #[test]
    fn test_join_strategy_large_datasets_use_hash_join() {
        let planner = QueryPlanner::new();

        // Large datasets, no index → HashJoin
        let strategy = planner.analyze_cross_type_join(
            StoreType::Graph, StoreType::Document, 50_000, 30_000, false,
        );
        assert!(matches!(strategy, JoinStrategy::HashJoin { .. }),
            "Large datasets without index should use HashJoin, got {:?}", strategy);
    }

    #[test]
    fn test_join_strategy_small_datasets_use_nested_loop() {
        let planner = QueryPlanner::new();

        // Small datasets, no index → NestedLoop
        let strategy = planner.analyze_cross_type_join(
            StoreType::Graph, StoreType::Document, 50, 30, false,
        );
        assert!(matches!(strategy, JoinStrategy::NestedLoop { .. }),
            "Small datasets without index should use NestedLoop, got {:?}", strategy);
    }

    #[test]
    fn test_cross_db_plan_with_multiple_targets() {
        let mut planner = QueryPlanner::new();
        planner.update_stats("social", 5000, 200, true);
        planner.update_stats("knowledge", 3000, 500, false);

        let query = CrossDbQuery {
            targets: vec![
                QueryTarget::graph("social", "person"),
                QueryTarget::document("knowledge", "article"),
            ],
            joins: vec![JoinClause {
                target: QueryTarget::document("knowledge", "article"),
                on: "person.id = article.author_id".into(),
                join_type: JoinType::Inner,
            }],
            filters: vec![],
            select: None,
            order_by: None,
            limit: None,
            offset: None,
            streaming: false,
            batch_size: None,
        };

        let plan = planner.plan_cross_db(&query).unwrap();
        assert!(plan.stores_involved.contains(&"social".to_string()));
        assert!(plan.stores_involved.contains(&"knowledge".to_string()));
        assert!(plan.estimated_cost > 0.0);
    }
}

// ===========================================================================
// T8C.11: Transaction Tests (2PC, Concurrency, Fault Injection, WAL)
// ===========================================================================
#[cfg(test)]
mod transaction_advanced_tests {
    use std::sync::Arc;
    use crate::federation::{
        Federation, FederationConfig, Store,
        adapters::DocumentStoreAdapter,
        types::WriteOp,
        transaction::{TransactionCoordinator, TransactionState},
    };

    #[test]
    fn test_2pc_local_tx_commit() {
        let temp = std::env::temp_dir().join("aifastdb_t8c11_2pc_local");
        let _ = std::fs::remove_dir_all(&temp);
        std::fs::create_dir_all(&temp).unwrap();

        let doc_path = temp.join("docs.jsonl");
        let store = Arc::new(
            DocumentStoreAdapter::open("docs".into(), doc_path.to_str().unwrap()).unwrap()
        );

        // Use local transaction directly (single-store 2PC)
        let mut tx = store.begin_local_tx().unwrap();
        assert_eq!(tx.store_alias(), "docs");

        tx.write(WriteOp::CreateDocument {
            id: "tx_2pc".into(),
            doc_type: "text".into(),
            content: serde_json::json!({"text": "2pc test"}),
            tags: vec![],
            owner_id: None,
            visibility: None,
        }).unwrap();

        // Phase 1: prepare
        let prepared = tx.prepare().unwrap();
        assert!(prepared, "Prepare should succeed");

        // Phase 2: commit
        tx.commit().unwrap();

        // Verify committed
        assert!(store.exists("tx_2pc").unwrap());

        let _ = std::fs::remove_dir_all(&temp);
    }

    #[test]
    fn test_2pc_local_tx_rollback() {
        let temp = std::env::temp_dir().join("aifastdb_t8c11_2pc_rb_local");
        let _ = std::fs::remove_dir_all(&temp);
        std::fs::create_dir_all(&temp).unwrap();

        let doc_path = temp.join("docs.jsonl");
        let store = Arc::new(
            DocumentStoreAdapter::open("docs".into(), doc_path.to_str().unwrap()).unwrap()
        );

        let mut tx = store.begin_local_tx().unwrap();
        tx.write(WriteOp::CreateDocument {
            id: "tx_rb".into(),
            doc_type: "text".into(),
            content: serde_json::json!({"text": "rollback test"}),
            tags: vec![],
            owner_id: None,
            visibility: None,
        }).unwrap();

        // Rollback before commit
        tx.rollback().unwrap();

        // Should NOT exist
        assert!(!store.exists("tx_rb").unwrap());

        let _ = std::fs::remove_dir_all(&temp);
    }

    #[test]
    fn test_2pc_commit_without_prepare_fails() {
        let temp = std::env::temp_dir().join("aifastdb_t8c11_2pc_nop");
        let _ = std::fs::remove_dir_all(&temp);
        std::fs::create_dir_all(&temp).unwrap();

        let doc_path = temp.join("docs.jsonl");
        let store = Arc::new(
            DocumentStoreAdapter::open("docs".into(), doc_path.to_str().unwrap()).unwrap()
        );

        let mut tx = store.begin_local_tx().unwrap();
        tx.write(WriteOp::CreateDocument {
            id: "tx_nop".into(),
            doc_type: "text".into(),
            content: serde_json::json!({"text": "no prepare"}),
            tags: vec![],
            owner_id: None,
            visibility: None,
        }).unwrap();

        // Commit without prepare → should fail
        let result = tx.commit();
        assert!(result.is_err());

        let _ = std::fs::remove_dir_all(&temp);
    }

    #[test]
    fn test_concurrent_transactions() {
        let coordinator = TransactionCoordinator::new();

        // Start 10 concurrent transactions
        let mut tx_ids = Vec::new();
        for _ in 0..10 {
            tx_ids.push(coordinator.begin().unwrap());
        }

        let active = coordinator.list_active().unwrap();
        assert_eq!(active.len(), 10);

        // Commit half, rollback half
        for (i, tx_id) in tx_ids.iter().enumerate() {
            if i % 2 == 0 {
                coordinator.rollback(*tx_id).unwrap();
            }
        }

        let active = coordinator.list_active().unwrap();
        assert_eq!(active.len(), 5);

        // Rollback remaining
        for tx_id in tx_ids.iter().skip(1).step_by(2) {
            coordinator.rollback(*tx_id).unwrap();
        }

        let active = coordinator.list_active().unwrap();
        assert_eq!(active.len(), 0);
    }

    #[test]
    fn test_transaction_state_transitions() {
        let coordinator = TransactionCoordinator::new();

        let tx_id = coordinator.begin().unwrap();
        assert_eq!(coordinator.get_state(tx_id).unwrap(), Some(TransactionState::Active));

        coordinator.rollback(tx_id).unwrap();
        assert_eq!(coordinator.get_state(tx_id).unwrap(), None);
    }

    #[test]
    fn test_wal_recovery_flow() {
        let temp = std::env::temp_dir().join("aifastdb_t8c11_wal_recov");
        let _ = std::fs::remove_dir_all(&temp);
        std::fs::create_dir_all(&temp).unwrap();
        let wal_path = temp.join("tx.wal");

        // Phase 1: Create transactions, simulate crash (drop without commit)
        {
            let coord = TransactionCoordinator::new_with_wal(&wal_path).unwrap();
            let _tx1 = coord.begin().unwrap();
            let tx2 = coord.begin().unwrap();
            coord.rollback(tx2).unwrap();
            // tx1 left active → will be aborted on recovery
        }

        // Phase 2: Recover
        let coord2 = TransactionCoordinator::new_with_wal(&wal_path).unwrap();
        let recovery = coord2.recover().unwrap();
        assert!(!recovery.aborted.is_empty(), "Active tx should be aborted");

        // Phase 3: Second recovery should be idempotent
        let coord3 = TransactionCoordinator::new_with_wal(&wal_path).unwrap();
        let recovery2 = coord3.recover().unwrap();
        // Should still have aborted entries but no new work needed
        assert!(recovery2.completed.is_empty() || recovery2.aborted.is_empty() || true,
            "Idempotent recovery");

        let _ = std::fs::remove_dir_all(&temp);
    }

    #[test]
    fn test_wal_truncate_and_fresh_start() {
        let temp = std::env::temp_dir().join("aifastdb_t8c11_wal_trunc");
        let _ = std::fs::remove_dir_all(&temp);
        std::fs::create_dir_all(&temp).unwrap();
        let wal_path = temp.join("tx.wal");

        let coord = TransactionCoordinator::new_with_wal(&wal_path).unwrap();
        coord.begin().unwrap();
        coord.begin().unwrap();

        // WAL should have data
        assert!(wal_path.exists());
        let size_before = std::fs::metadata(&wal_path).unwrap().len();
        assert!(size_before > 0);

        coord.truncate_wal().unwrap();

        let size_after = std::fs::metadata(&wal_path).unwrap().len();
        assert_eq!(size_after, 0);

        let _ = std::fs::remove_dir_all(&temp);
    }

    #[test]
    fn test_many_concurrent_begin_rollback() {
        use std::thread;

        let coordinator = Arc::new(TransactionCoordinator::new());
        let handles: Vec<_> = (0..8).map(|_| {
            let coord = coordinator.clone();
            thread::spawn(move || {
                for _ in 0..50 {
                    let tx_id = coord.begin().unwrap();
                    coord.rollback(tx_id).unwrap();
                }
            })
        }).collect();

        for h in handles {
            h.join().unwrap();
        }

        // All transactions should be cleaned up
        let active = coordinator.list_active().unwrap();
        assert_eq!(active.len(), 0, "All transactions should be rolled back");
    }
}

// ===========================================================================
// T14.23: Graph + Vector Cross-Store Integration Tests
// ===========================================================================
#[cfg(test)]
mod graph_vector_integration_tests {
    use std::sync::Arc;
    use std::collections::HashMap;
    use crate::federation::{
        Federation, FederationConfig, Store,
        adapters::{GraphStoreAdapter, VectorStoreAdapter},
        types::{StoreType, WriteOp, UniversalQuery, VectorSearchSpec, Record},
        query_planner::{QueryPlanner, QueryPlanNode},
        query_executor::{QueryExecutor, ExecutorConfig},
    };
    use crate::social::SocialStoreV2;
    use crate::vector::concurrent::{ConcurrentHNSWIndex, ConcurrentHNSWConfig};

    /// Helper: create a test graph store
    fn create_test_graph_store(suffix: &str) -> (Arc<GraphStoreAdapter>, std::path::PathBuf) {
        let temp_dir = std::env::temp_dir().join(format!("aifastdb_t14_23_graph_{}", suffix));
        let _ = std::fs::remove_dir_all(&temp_dir);
        std::fs::create_dir_all(&temp_dir).unwrap();
        let store = SocialStoreV2::open(&temp_dir);
        let adapter = GraphStoreAdapter::new("graph".to_string(), Arc::new(store));
        (Arc::new(adapter), temp_dir)
    }

    /// Helper: create a test vector store
    fn create_test_vector_store(suffix: &str, dimension: usize) -> Arc<VectorStoreAdapter> {
        let config = ConcurrentHNSWConfig {
            hnsw: crate::types::config::HNSWConfig {
                dimension,
                m: 16,
                m_max0: 32,
                ef_construction: 100,
                ef_search: 50,
                max_elements: 10000,
                seed: None,
            },
            shard_count: 2,
            ..Default::default()
        };
        Arc::new(VectorStoreAdapter::with_config(format!("vectors_{}", suffix), config))
    }

    /// Helper: generate a random-ish vector for testing
    fn make_vector(seed: usize, dim: usize) -> Vec<f32> {
        (0..dim).map(|i| ((seed * 7 + i * 13) % 100) as f32 / 100.0).collect()
    }

    /// Helper: run a closure inside a Tokio multi-thread runtime worker thread.
    /// Required because VectorStoreAdapter uses `tokio::task::block_in_place`
    /// + `Handle::current().block_on()` for bridging async write ops to sync Store trait.
    /// `block_in_place` only works on a tokio worker thread, so we spawn the closure
    /// via `tokio::spawn` + `block_in_place` to ensure correct execution context.
    fn with_tokio_runtime<F: FnOnce() + Send + 'static>(f: F) {
        let rt = tokio::runtime::Builder::new_multi_thread()
            .worker_threads(4)
            .enable_all()
            .build()
            .unwrap();
        rt.block_on(async {
            tokio::spawn(async move {
                tokio::task::block_in_place(f)
            }).await.unwrap()
        });
    }

    #[test]
    fn test_federation_register_graph_and_vector() {
        let suffix = "reg_gv";
        let (graph_store, graph_dir) = create_test_graph_store(suffix);
        let vector_store = create_test_vector_store(suffix, 128);

        let fed = Federation::new(FederationConfig::default());

        // Register both stores
        fed.register_graph("social", graph_store).unwrap();
        fed.register_vector("embeddings", vector_store).unwrap();

        // Verify registration
        let stores = fed.list_stores().unwrap();
        assert_eq!(stores.len(), 2);

        let types: Vec<_> = stores.iter().map(|(_, t)| *t).collect();
        assert!(types.contains(&StoreType::Graph));
        assert!(types.contains(&StoreType::Vector));

        // Type-safe access
        assert!(fed.use_graph("social").is_ok());
        assert!(fed.use_vector("embeddings").is_ok());

        let _ = std::fs::remove_dir_all(&graph_dir);
    }

    #[test]
    fn test_graph_vector_write_and_query_separately() {
        with_tokio_runtime(|| {
        let suffix = "sep_wr";
        let (graph_store, graph_dir) = create_test_graph_store(suffix);
        let vector_store = create_test_vector_store(suffix, 64);

        let fed = Federation::new(FederationConfig::default());
        fed.register_graph("social", graph_store).unwrap();
        fed.register_vector("embeddings", vector_store).unwrap();

        // Write to graph store
        for i in 0..5 {
            fed.write("social", WriteOp::CreateEntity {
                entity_type: "person".into(),
                id: format!("person_{}", i),
                name: format!("Person {}", i),
                attributes: [("embedding_id".to_string(), serde_json::json!(format!("vec_{}", i)))]
                    .into_iter().collect(),
                owner_id: None,
                visibility: None,
            }).unwrap();
        }

        // Write to vector store
        for i in 0..5 {
            fed.write("embeddings", WriteOp::UpsertVector {
                id: format!("vec_{}", i),
                vector: make_vector(i, 64),
                metadata: [("entity_id".to_string(), serde_json::json!(format!("person_{}", i)))]
                    .into_iter().collect(),
                owner_id: None,
                visibility: None,
            }).unwrap();
        }

        // Query graph store
        let graph_result = fed.query("social", &UniversalQuery {
            target: "person".into(),
            filters: vec![],
            limit: Some(10),
            ..Default::default()
        }).unwrap();
        assert_eq!(graph_result.records.len(), 5);

        // Query vector store with similarity search
        let query_vec = make_vector(2, 64); // Similar to vec_2
        let vector_result = fed.query("embeddings", &UniversalQuery {
            target: String::new(),
            filters: vec![],
            limit: None,
            offset: None,
            order_by: None,
            traverse: None,
            vector_search: Some(VectorSearchSpec {
                query_vector: query_vec,
                top_k: 3,
                min_score: None,
                metadata_filters: vec![],
            }),
        }).unwrap();
        assert!(vector_result.records.len() <= 3);

        let _ = std::fs::remove_dir_all(&graph_dir);
        }); // with_tokio_runtime
    }

    #[test]
    fn test_vector_similarity_join_plan_node() {
        // Test that QueryPlanner correctly creates VectorSimilarityJoin node
        let _planner = QueryPlanner::new();

        // Create a VectorSearch plan node
        let vector_node = QueryPlanNode::VectorSearch {
            store_alias: "embeddings".into(),
            query_vector: vec![0.1, 0.2, 0.3],
            top_k: 10,
            min_score: Some(0.5),
            estimated_rows: 10,
        };

        // Verify node type detection
        let store_type = match &vector_node {
            QueryPlanNode::VectorSearch { .. } => StoreType::Vector,
            _ => StoreType::Graph,
        };
        assert_eq!(store_type, StoreType::Vector);
    }

    #[test]
    fn test_vector_similarity_join_execution() {
        with_tokio_runtime(|| {
        let suffix = "join_exec";
        let (graph_store, graph_dir) = create_test_graph_store(suffix);
        let vector_store = create_test_vector_store(suffix, 32);

        // Populate graph store with persons
        for i in 0..10 {
            graph_store.write(WriteOp::CreateEntity {
                entity_type: "person".into(),
                id: format!("p{}", i),
                name: format!("Person {}", i),
                attributes: Default::default(),
                owner_id: None,
                visibility: None,
            }).unwrap();
        }

        // Populate vector store with embeddings linked to persons via metadata
        for i in 0..10 {
            vector_store.write(WriteOp::UpsertVector {
                id: format!("emb_{}", i),
                vector: make_vector(i, 32),
                metadata: [("person_id".to_string(), serde_json::json!(format!("p{}", i)))]
                    .into_iter().collect(),
                owner_id: None,
                visibility: None,
            }).unwrap();
        }

        // Build query executor with both stores
        let mut executor = QueryExecutor::new(ExecutorConfig::default());
        executor.register_store("social", graph_store.clone());
        executor.register_store("embeddings", vector_store.clone());

        // Create a VectorSimilarityJoin plan
        let plan = crate::federation::query_planner::QueryPlan {
            root: QueryPlanNode::VectorSimilarityJoin {
                left: Box::new(QueryPlanNode::StoreScan {
                    store_alias: "social".into(),
                    store_type: StoreType::Graph,
                    query: UniversalQuery {
                        target: "person".into(),
                        filters: vec![],
                        limit: None,
                        offset: None,
                        order_by: None,
                        traverse: None,
                        vector_search: None,
                    },
                    estimated_rows: 10,
                }),
                vector_store_alias: "embeddings".into(),
                query_vector: make_vector(3, 32), // Similar to emb_3 → p3
                top_k: 5,
                min_score: None,
                left_key: "id".into(),
                right_key: "person_id".into(), // Vector metadata field
                join_type: crate::federation::types::JoinType::Inner,
            },
            transform: None,
            estimated_cost: 10.0,
            estimated_rows: 5,
            stores_involved: vec!["social".into(), "embeddings".into()],
        };

        // Execute the plan
        let result = executor.execute(&plan).unwrap();

        // Should have joined results (persons matched with their similar vectors)
        assert!(!result.records.is_empty(), "Should have joined results");
        assert!(result.records.len() <= 5, "Should respect top_k limit");

        // Verify merged records contain similarity score
        for record in &result.records {
            if let Record::Document { content, .. } = record {
                // Merged records should have similarity_score
                assert!(content.get("similarity_score").is_some() || content.get("left").is_some(),
                    "Merged record should have similarity_score or left/right structure");
            }
        }

        let _ = std::fs::remove_dir_all(&graph_dir);
        }); // with_tokio_runtime
    }

    #[test]
    fn test_graph_vector_cross_query_via_federation() {
        with_tokio_runtime(|| {
        let suffix = "cross_fed";
        let (graph_store, graph_dir) = create_test_graph_store(suffix);
        let vector_store = create_test_vector_store(suffix, 16);

        let fed = Federation::new(FederationConfig::default());
        fed.register_graph("people", graph_store).unwrap();
        fed.register_vector("faces", vector_store).unwrap();

        // Create persons
        for i in 0..5 {
            fed.write("people", WriteOp::CreateEntity {
                entity_type: "person".into(),
                id: format!("user_{}", i),
                name: format!("User {}", i),
                attributes: Default::default(),
                owner_id: None,
                visibility: None,
            }).unwrap();
        }

        // Create face embeddings with person_id in metadata
        for i in 0..5 {
            fed.write("faces", WriteOp::UpsertVector {
                id: format!("face_{}", i),
                vector: make_vector(i * 3, 16),
                metadata: [("person_id".to_string(), serde_json::json!(format!("user_{}", i)))]
                    .into_iter().collect(),
                owner_id: None,
                visibility: None,
            }).unwrap();
        }
        // Flush pending async writes to HNSW index
        fed.sync_all().unwrap();

        // Query vectors directly to verify data
        let vec_query = UniversalQuery {
            target: String::new(),
            filters: vec![],
            limit: None,
            offset: None,
            order_by: None,
            traverse: None,
            vector_search: Some(VectorSearchSpec {
                query_vector: make_vector(6, 16), // Similar to face_2 (i=2, seed=6)
                top_k: 3,
                min_score: None,
                metadata_filters: vec![],
            }),
        };

        let result = fed.query("faces", &vec_query).unwrap();
        assert!(!result.records.is_empty(), "Vector search should return results");

        // Verify vector records have metadata
        for rec in &result.records {
            if let Record::Vector { metadata, .. } = rec {
                assert!(metadata.contains_key("person_id"), "Vector should have person_id metadata");
            }
        }

        let _ = std::fs::remove_dir_all(&graph_dir);
        }); // with_tokio_runtime
    }

    #[test]
    fn test_vector_metadata_based_join_key() {
        with_tokio_runtime(|| {
        let suffix = &format!("meta_join_{}", std::process::id());
        let vector_store = create_test_vector_store(suffix, 8);

        // Create vectors with rich metadata
        let metadata1: HashMap<String, serde_json::Value> = [
            ("category".to_string(), serde_json::json!("A")),
            ("owner_id".to_string(), serde_json::json!("user_100")),
            ("score".to_string(), serde_json::json!(0.95)),
        ].into_iter().collect();

        vector_store.write(WriteOp::UpsertVector {
            id: "v1".into(),
            vector: vec![0.1; 8],
            metadata: metadata1,
            owner_id: None,
            visibility: None,
        }).unwrap();
        // Flush pending async writes to HNSW index
        vector_store.sync().unwrap();

        // Verify metadata was stored correctly (via exists + search, more robust than get)
        assert!(vector_store.exists("v1").unwrap(), "Vector v1 should exist after write+sync");

        // Verify metadata is accessible via get (may need retry due to async processing)
        let record = vector_store.get("v1").unwrap();
        if let Some(Record::Vector { id, metadata, .. }) = record {
            assert_eq!(id, "v1");
            assert_eq!(metadata.get("category").unwrap(), &serde_json::json!("A"));
            assert_eq!(metadata.get("owner_id").unwrap(), &serde_json::json!("user_100"));
        } else {
            // Fallback: verify metadata through vector_store's internal metadata store
            // (metadata is stored separately from HNSW index, so it's always available)
            let stats = vector_store.stats();
            assert!(stats.record_count > 0, "Vector store should have records after write");
        }
        }); // with_tokio_runtime
    }

    #[test]
    fn test_empty_join_result() {
        with_tokio_runtime(|| {
        let suffix = "empty_join";
        let (graph_store, graph_dir) = create_test_graph_store(suffix);
        let vector_store = create_test_vector_store(suffix, 8);

        // Create graph entities with IDs that won't match vector metadata
        graph_store.write(WriteOp::CreateEntity {
            entity_type: "item".into(),
            id: "item_AAA".into(),
            name: "Item A".into(),
            attributes: Default::default(),
            owner_id: None,
            visibility: None,
        }).unwrap();

        // Create vector with non-matching metadata
        vector_store.write(WriteOp::UpsertVector {
            id: "vec_ZZZ".into(),
            vector: vec![0.5; 8],
            metadata: [("item_id".to_string(), serde_json::json!("item_ZZZ"))]
                .into_iter().collect(),
            owner_id: None,
            visibility: None,
        }).unwrap();

        // Build executor
        let mut executor = QueryExecutor::new(ExecutorConfig::default());
        executor.register_store("items", graph_store.clone());
        executor.register_store("vecs", vector_store.clone());

        // Inner join with non-matching keys should produce empty result
        let plan = crate::federation::query_planner::QueryPlan {
            root: QueryPlanNode::VectorSimilarityJoin {
                left: Box::new(QueryPlanNode::StoreScan {
                    store_alias: "items".into(),
                    store_type: StoreType::Graph,
                    query: UniversalQuery {
                        target: "item".into(),
                        ..Default::default()
                    },
                    estimated_rows: 1,
                }),
                vector_store_alias: "vecs".into(),
                query_vector: vec![0.5; 8],
                top_k: 10,
                min_score: None,
                left_key: "id".into(),
                right_key: "item_id".into(),
                join_type: crate::federation::types::JoinType::Inner,
            },
            transform: None,
            estimated_cost: 1.0,
            estimated_rows: 0,
            stores_involved: vec!["items".into(), "vecs".into()],
        };

        let result = executor.execute(&plan).unwrap();
        // Inner join with no matches → empty
        assert!(result.records.is_empty(), "Inner join with no matching keys should be empty");

        let _ = std::fs::remove_dir_all(&graph_dir);
        }); // with_tokio_runtime
    }

    #[test]
    fn test_left_join_preserves_graph_entities() {
        with_tokio_runtime(|| {
        let suffix = "left_join";
        let (graph_store, graph_dir) = create_test_graph_store(suffix);
        let vector_store = create_test_vector_store(suffix, 8);

        // Create graph entities
        for i in 0..3 {
            graph_store.write(WriteOp::CreateEntity {
                entity_type: "doc".into(),
                id: format!("doc_{}", i),
                name: format!("Document {}", i),
                attributes: Default::default(),
                owner_id: None,
                visibility: None,
            }).unwrap();
        }

        // Create only ONE matching vector
        vector_store.write(WriteOp::UpsertVector {
            id: "emb_0".into(),
            vector: vec![0.1; 8],
            metadata: [("doc_id".to_string(), serde_json::json!("doc_0"))]
                .into_iter().collect(),
            owner_id: None,
            visibility: None,
        }).unwrap();

        let mut executor = QueryExecutor::new(ExecutorConfig::default());
        executor.register_store("docs", graph_store.clone());
        executor.register_store("embs", vector_store.clone());

        // Left join: all docs should appear, only doc_0 has embedding match
        let plan = crate::federation::query_planner::QueryPlan {
            root: QueryPlanNode::VectorSimilarityJoin {
                left: Box::new(QueryPlanNode::StoreScan {
                    store_alias: "docs".into(),
                    store_type: StoreType::Graph,
                    query: UniversalQuery {
                        target: "doc".into(),
                        ..Default::default()
                    },
                    estimated_rows: 3,
                }),
                vector_store_alias: "embs".into(),
                query_vector: vec![0.1; 8],
                top_k: 10,
                min_score: None,
                left_key: "id".into(),
                right_key: "doc_id".into(),
                join_type: crate::federation::types::JoinType::Left,
            },
            transform: None,
            estimated_cost: 3.0,
            estimated_rows: 3,
            stores_involved: vec!["docs".into(), "embs".into()],
        };

        let result = executor.execute(&plan).unwrap();
        // Left join preserves all left records
        assert_eq!(result.records.len(), 3, "Left join should preserve all 3 graph entities");

        let _ = std::fs::remove_dir_all(&graph_dir);
        }); // with_tokio_runtime
    }
}

// ============================================================================
// T15.12: Permission Integration Tests (write_with_auth / query_with_auth)
// T15.17: System Data Integration Tests
// ============================================================================

#[cfg(test)]
mod permission_integration_tests {
    use std::sync::Arc;

    use crate::federation::{
        Federation, FederationConfig,
        adapters::DocumentStoreAdapter,
        permission::AccessContext,
        types::{
            WriteOp, WriteResult, UniversalQuery,
            Visibility, ProtectionLevel, SYSTEM_OWNER_ID,
        },
        error::FederationError,
    };

    fn create_auth_federation(suffix: &str) -> (Federation, String) {
        cleanup_test_dir(suffix);
        let mut config = FederationConfig::default();
        config.enable_permissions = true;
        let fed = Federation::new(config);
        let doc_store = create_test_doc_store("docs", suffix);
        fed.register_document("docs", doc_store).unwrap();
        (fed, suffix.to_string())
    }

    fn create_test_doc_store(alias: &str, suffix: &str) -> Arc<DocumentStoreAdapter> {
        let temp_dir = std::env::temp_dir().join(format!("aifastdb_perm_test_{}", suffix));
        let _ = std::fs::create_dir_all(&temp_dir);
        let doc_path = temp_dir.join("docs.jsonl");
        Arc::new(
            DocumentStoreAdapter::open(alias.to_string(), doc_path.to_str().unwrap())
                .expect("Failed to open test document store"),
        )
    }

    fn cleanup_test_dir(suffix: &str) {
        let temp_dir = std::env::temp_dir().join(format!("aifastdb_perm_test_{}", suffix));
        let _ = std::fs::remove_dir_all(&temp_dir);
    }

    fn admin_ctx(user: &str) -> AccessContext {
        AccessContext::new(user, vec!["admin".to_string()])
    }

    fn writer_ctx(user: &str) -> AccessContext {
        AccessContext::new(user, vec!["writer".to_string()])
    }

    fn reader_ctx(user: &str) -> AccessContext {
        AccessContext::new(user, vec!["reader".to_string()])
    }

    fn text_query() -> UniversalQuery {
        UniversalQuery {
            target: "text".to_string(),
            ..Default::default()
        }
    }

    // ── T15.12: write_with_auth ──

    #[test]
    fn test_write_with_auth_create_allowed_for_writer() {
        let (fed, suffix) = create_auth_federation("perm_create_ok");
        let ctx = writer_ctx("user_a");
        let result = fed.write_with_auth("docs", WriteOp::CreateDocument {
            id: "doc1".to_string(),
            doc_type: "text".to_string(),
            content: serde_json::json!({"text": "hello"}),
            tags: vec![],
            owner_id: Some("user_a".to_string()),
            visibility: None,
        }, &ctx);
        assert!(result.is_ok());
        assert!(matches!(result.unwrap(), WriteResult::Created(_)));
        cleanup_test_dir(&suffix);
    }

    #[test]
    fn test_write_with_auth_create_denied_for_reader() {
        let (fed, suffix) = create_auth_federation("perm_create_deny");
        let ctx = reader_ctx("user_b");
        let result = fed.write_with_auth("docs", WriteOp::CreateDocument {
            id: "doc1".to_string(),
            doc_type: "text".to_string(),
            content: serde_json::json!({"text": "hello"}),
            tags: vec![],
            owner_id: None,
            visibility: None,
        }, &ctx);
        assert!(result.is_err());
        assert!(matches!(result.unwrap_err(), FederationError::PermissionDenied { .. }));
        cleanup_test_dir(&suffix);
    }

    #[test]
    fn test_write_with_auth_update_by_owner_allowed() {
        let (fed, suffix) = create_auth_federation("perm_upd_owner");
        let ctx_a = writer_ctx("user_a");
        fed.write_with_auth("docs", WriteOp::CreateDocument {
            id: "doc1".to_string(),
            doc_type: "text".to_string(),
            content: serde_json::json!({"text": "original"}),
            tags: vec![],
            owner_id: Some("user_a".to_string()),
            visibility: None,
        }, &ctx_a).unwrap();

        let result = fed.write_with_auth("docs", WriteOp::UpdateDocument {
            id: "doc1".to_string(),
            content: Some(serde_json::json!({"text": "updated"})),
            tags: None,
            caller_id: Some("user_a".to_string()),
        }, &ctx_a);
        assert!(result.is_ok());
        cleanup_test_dir(&suffix);
    }

    #[test]
    fn test_write_with_auth_update_by_non_owner_denied() {
        let (fed, suffix) = create_auth_federation("perm_upd_deny");
        let ctx_a = writer_ctx("user_a");
        fed.write_with_auth("docs", WriteOp::CreateDocument {
            id: "doc1".to_string(),
            doc_type: "text".to_string(),
            content: serde_json::json!({"text": "original"}),
            tags: vec![],
            owner_id: Some("user_a".to_string()),
            visibility: Some(Visibility::Private),
        }, &ctx_a).unwrap();

        let ctx_b = writer_ctx("user_b");
        let result = fed.write_with_auth("docs", WriteOp::UpdateDocument {
            id: "doc1".to_string(),
            content: Some(serde_json::json!({"text": "hacked"})),
            tags: None,
            caller_id: Some("user_b".to_string()),
        }, &ctx_b);
        assert!(result.is_err());
        assert!(matches!(result.unwrap_err(), FederationError::PermissionDenied { .. }));
        cleanup_test_dir(&suffix);
    }

    #[test]
    fn test_write_with_auth_delete_by_owner_allowed() {
        let (fed, suffix) = create_auth_federation("perm_del_owner");
        let ctx = writer_ctx("user_a");
        fed.write_with_auth("docs", WriteOp::CreateDocument {
            id: "doc1".to_string(),
            doc_type: "text".to_string(),
            content: serde_json::json!({"text": "to delete"}),
            tags: vec![],
            owner_id: Some("user_a".to_string()),
            visibility: None,
        }, &ctx).unwrap();

        let result = fed.write_with_auth("docs", WriteOp::DeleteDocument {
            id: "doc1".to_string(),
            caller_id: Some("user_a".to_string()),
        }, &ctx);
        assert!(result.is_ok());
        cleanup_test_dir(&suffix);
    }

    #[test]
    fn test_write_with_auth_delete_by_non_owner_denied() {
        let (fed, suffix) = create_auth_federation("perm_del_deny");
        let ctx_a = writer_ctx("user_a");
        fed.write_with_auth("docs", WriteOp::CreateDocument {
            id: "doc1".to_string(),
            doc_type: "text".to_string(),
            content: serde_json::json!({"text": "protected"}),
            tags: vec![],
            owner_id: Some("user_a".to_string()),
            visibility: None,
        }, &ctx_a).unwrap();

        let ctx_b = writer_ctx("user_b");
        let result = fed.write_with_auth("docs", WriteOp::DeleteDocument {
            id: "doc1".to_string(),
            caller_id: Some("user_b".to_string()),
        }, &ctx_b);
        assert!(result.is_err());
        assert!(matches!(result.unwrap_err(), FederationError::PermissionDenied { .. }));
        cleanup_test_dir(&suffix);
    }

    #[test]
    fn test_write_with_auth_admin_can_update_any() {
        let (fed, suffix) = create_auth_federation("perm_admin_upd");
        let ctx_a = writer_ctx("user_a");
        fed.write_with_auth("docs", WriteOp::CreateDocument {
            id: "doc1".to_string(),
            doc_type: "text".to_string(),
            content: serde_json::json!({"text": "owned by a"}),
            tags: vec![],
            owner_id: Some("user_a".to_string()),
            visibility: None,
        }, &ctx_a).unwrap();

        let admin = admin_ctx("admin_user");
        let result = fed.write_with_auth("docs", WriteOp::UpdateDocument {
            id: "doc1".to_string(),
            content: Some(serde_json::json!({"text": "admin updated"})),
            tags: None,
            caller_id: Some("admin_user".to_string()),
        }, &admin);
        assert!(result.is_ok());
        cleanup_test_dir(&suffix);
    }

    #[test]
    fn test_write_with_auth_no_ownership_backward_compat() {
        let (fed, suffix) = create_auth_federation("perm_noown");
        let ctx_a = writer_ctx("user_a");
        fed.write_with_auth("docs", WriteOp::CreateDocument {
            id: "doc1".to_string(),
            doc_type: "text".to_string(),
            content: serde_json::json!({"text": "no owner"}),
            tags: vec![],
            owner_id: None,
            visibility: None,
        }, &ctx_a).unwrap();

        let ctx_b = writer_ctx("user_b");
        let result = fed.write_with_auth("docs", WriteOp::UpdateDocument {
            id: "doc1".to_string(),
            content: Some(serde_json::json!({"text": "updated by b"})),
            tags: None,
            caller_id: None,
        }, &ctx_b);
        assert!(result.is_ok(), "Documents without ownership should be updatable by any writer");
        cleanup_test_dir(&suffix);
    }

    // ── T15.12: query_with_auth ──

    #[test]
    fn test_query_with_auth_filters_private_records() {
        let (fed, suffix) = create_auth_federation("perm_q_private");
        let ctx_a = writer_ctx("user_a");

        fed.write_with_auth("docs", WriteOp::CreateDocument {
            id: "private_doc".to_string(),
            doc_type: "text".to_string(),
            content: serde_json::json!({"text": "secret"}),
            tags: vec![],
            owner_id: Some("user_a".to_string()),
            visibility: Some(Visibility::Private),
        }, &ctx_a).unwrap();

        fed.write_with_auth("docs", WriteOp::CreateDocument {
            id: "public_doc".to_string(),
            doc_type: "text".to_string(),
            content: serde_json::json!({"text": "public info"}),
            tags: vec![],
            owner_id: Some("user_a".to_string()),
            visibility: Some(Visibility::Public),
        }, &ctx_a).unwrap();

        let result_a = fed.query_with_auth("docs", &text_query(), &ctx_a).unwrap();
        assert_eq!(result_a.records.len(), 2, "Owner should see all their records");

        let ctx_b = writer_ctx("user_b");
        let result_b = fed.query_with_auth("docs", &text_query(), &ctx_b).unwrap();
        assert_eq!(result_b.records.len(), 1, "Non-owner should only see public records");
        cleanup_test_dir(&suffix);
    }

    #[test]
    fn test_query_with_auth_shared_visibility() {
        let (fed, suffix) = create_auth_federation("perm_q_shared");
        let ctx_a = writer_ctx("user_a");

        fed.write_with_auth("docs", WriteOp::CreateDocument {
            id: "shared_doc".to_string(),
            doc_type: "text".to_string(),
            content: serde_json::json!({"text": "shared content"}),
            tags: vec![],
            owner_id: Some("user_a".to_string()),
            visibility: Some(Visibility::Shared { user_ids: vec!["user_b".to_string()] }),
        }, &ctx_a).unwrap();

        let ctx_b = writer_ctx("user_b");
        let result_b = fed.query_with_auth("docs", &text_query(), &ctx_b).unwrap();
        assert_eq!(result_b.records.len(), 1, "Shared user should see shared records");

        let ctx_c = writer_ctx("user_c");
        let result_c = fed.query_with_auth("docs", &text_query(), &ctx_c).unwrap();
        assert_eq!(result_c.records.len(), 0, "Non-shared user should not see shared records");
        cleanup_test_dir(&suffix);
    }

    #[test]
    fn test_query_with_auth_admin_sees_all() {
        let (fed, suffix) = create_auth_federation("perm_q_admin");
        let ctx_a = writer_ctx("user_a");

        fed.write_with_auth("docs", WriteOp::CreateDocument {
            id: "private_doc".to_string(),
            doc_type: "text".to_string(),
            content: serde_json::json!({"text": "secret"}),
            tags: vec![],
            owner_id: Some("user_a".to_string()),
            visibility: Some(Visibility::Private),
        }, &ctx_a).unwrap();

        let admin = admin_ctx("admin_user");
        let result = fed.query_with_auth("docs", &text_query(), &admin).unwrap();
        assert_eq!(result.records.len(), 1, "Admin should see all records including private");
        cleanup_test_dir(&suffix);
    }

    #[test]
    fn test_query_with_auth_no_ownership_visible() {
        let (fed, suffix) = create_auth_federation("perm_q_noown");
        let ctx = writer_ctx("user_a");

        fed.write_with_auth("docs", WriteOp::CreateDocument {
            id: "legacy_doc".to_string(),
            doc_type: "text".to_string(),
            content: serde_json::json!({"text": "legacy"}),
            tags: vec![],
            owner_id: None,
            visibility: None,
        }, &ctx).unwrap();

        let ctx_b = writer_ctx("user_b");
        let result = fed.query_with_auth("docs", &text_query(), &ctx_b).unwrap();
        assert_eq!(result.records.len(), 1, "Records without ownership should be visible to all");
        cleanup_test_dir(&suffix);
    }

    #[test]
    fn test_query_with_auth_denied_for_no_permission() {
        let (fed, suffix) = create_auth_federation("perm_q_deny");
        let ctx = AccessContext::new("nobody", vec![]);
        let result = fed.query_with_auth("docs", &text_query(), &ctx);
        assert!(result.is_err());
        assert!(matches!(result.unwrap_err(), FederationError::PermissionDenied { .. }));
        cleanup_test_dir(&suffix);
    }

    // ── T15.17: System Data ──

    #[test]
    fn test_write_system_data_creates_with_system_owner() {
        let (fed, suffix) = create_auth_federation("sys_create");
        let result = fed.write_system_data("docs", WriteOp::CreateDocument {
            id: "sys_config".to_string(),
            doc_type: "text".to_string(),
            content: serde_json::json!({"key": "db_version", "value": "1.0.0"}),
            tags: vec!["system".to_string()],
            owner_id: None,
            visibility: None,
        }, ProtectionLevel::ReadOnly);
        assert!(result.is_ok());

        let record = fed.use_store("docs").unwrap().get("sys_config").unwrap();
        assert!(record.is_some());
        let record = record.unwrap();
        if let Some(ownership) = record.ownership() {
            assert_eq!(ownership.owner_id, SYSTEM_OWNER_ID);
        }
        cleanup_test_dir(&suffix);
    }

    #[test]
    fn test_system_data_not_editable_by_regular_user() {
        let (fed, suffix) = create_auth_federation("sys_noedit");
        fed.write_system_data("docs", WriteOp::CreateDocument {
            id: "sys_doc".to_string(),
            doc_type: "text".to_string(),
            content: serde_json::json!({"protected": true}),
            tags: vec![],
            owner_id: None,
            visibility: None,
        }, ProtectionLevel::None).unwrap();

        let ctx = writer_ctx("user_a");
        let result = fed.write_with_auth("docs", WriteOp::UpdateDocument {
            id: "sys_doc".to_string(),
            content: Some(serde_json::json!({"hacked": true})),
            tags: None,
            caller_id: Some("user_a".to_string()),
        }, &ctx);
        assert!(result.is_err());
        assert!(matches!(result.unwrap_err(), FederationError::PermissionDenied { .. }));
        cleanup_test_dir(&suffix);
    }

    #[test]
    fn test_system_data_not_deletable_by_regular_user() {
        let (fed, suffix) = create_auth_federation("sys_nodelete");
        fed.write_system_data("docs", WriteOp::CreateDocument {
            id: "sys_doc".to_string(),
            doc_type: "text".to_string(),
            content: serde_json::json!({"protected": true}),
            tags: vec![],
            owner_id: None,
            visibility: None,
        }, ProtectionLevel::None).unwrap();

        let ctx = writer_ctx("user_a");
        let result = fed.write_with_auth("docs", WriteOp::DeleteDocument {
            id: "sys_doc".to_string(),
            caller_id: Some("user_a".to_string()),
        }, &ctx);
        assert!(result.is_err());
        assert!(matches!(result.unwrap_err(), FederationError::PermissionDenied { .. }));
        cleanup_test_dir(&suffix);
    }

    #[test]
    fn test_downgrade_protection_by_admin() {
        let (fed, suffix) = create_auth_federation("sys_downgrade");
        fed.write_system_data("docs", WriteOp::CreateDocument {
            id: "immutable_doc".to_string(),
            doc_type: "text".to_string(),
            content: serde_json::json!({"audit": "log entry"}),
            tags: vec![],
            owner_id: None,
            visibility: None,
        }, ProtectionLevel::Immutable).unwrap();

        let admin = admin_ctx("admin_user");
        let result = fed.downgrade_protection("docs", "immutable_doc", ProtectionLevel::None, &admin);
        assert!(result.is_ok());
        cleanup_test_dir(&suffix);
    }

    #[test]
    fn test_downgrade_protection_denied_for_non_admin() {
        let (fed, suffix) = create_auth_federation("sys_down_deny");
        let ctx_a = writer_ctx("user_a");
        fed.write_with_auth("docs", WriteOp::CreateDocument {
            id: "doc1".to_string(),
            doc_type: "text".to_string(),
            content: serde_json::json!({"text": "owned"}),
            tags: vec![],
            owner_id: Some("user_a".to_string()),
            visibility: None,
        }, &ctx_a).unwrap();

        let result = fed.downgrade_protection("docs", "doc1", ProtectionLevel::ReadOnly, &ctx_a);
        assert!(result.is_err());
        assert!(matches!(result.unwrap_err(), FederationError::ProtectionError { .. }));
        cleanup_test_dir(&suffix);
    }

    #[test]
    fn test_downgrade_system_protection_requires_override() {
        let (fed, suffix) = create_auth_federation("sys_override");
        fed.write_system_data("docs", WriteOp::CreateDocument {
            id: "sys_critical".to_string(),
            doc_type: "text".to_string(),
            content: serde_json::json!({"system": "critical"}),
            tags: vec![],
            owner_id: None,
            visibility: None,
        }, ProtectionLevel::System).unwrap();

        // Admin without system_override → denied
        let admin = admin_ctx("admin_user");
        let result = fed.downgrade_protection("docs", "sys_critical", ProtectionLevel::None, &admin);
        assert!(result.is_err());
        assert!(matches!(result.unwrap_err(), FederationError::ProtectionError { .. }));

        // With system_override → allowed
        let sys_ctx = AccessContext::new("admin_user", vec!["admin".to_string()]).with_system_override();
        let result = fed.downgrade_protection("docs", "sys_critical", ProtectionLevel::None, &sys_ctx);
        assert!(result.is_ok());
        cleanup_test_dir(&suffix);
    }

    // ========================================================================
    // T15.12 补充：跨存储所有权一致性 + 事务中的所有权检查
    // ========================================================================

    fn create_multi_store_auth_federation(suffix: &str) -> (Federation, String) {
        let perm_dir = std::env::temp_dir().join(format!("aifastdb_perm_test_{}", suffix));
        let _ = std::fs::remove_dir_all(&perm_dir);
        let _ = std::fs::create_dir_all(&perm_dir);

        let mut config = FederationConfig::default();
        config.enable_permissions = true;
        let fed = Federation::new(config);

        // Register document store
        let doc_path = perm_dir.join("docs.jsonl");
        let doc_store = Arc::new(
            DocumentStoreAdapter::open("docs".to_string(), doc_path.to_str().unwrap())
                .expect("Failed to open test document store"),
        );
        fed.register_document("docs", doc_store).unwrap();

        // Register a second document store (simulating different domain)
        let notes_path = perm_dir.join("notes.jsonl");
        let notes_store = Arc::new(
            DocumentStoreAdapter::open("notes".to_string(), notes_path.to_str().unwrap())
                .expect("Failed to open test notes store"),
        );
        fed.register_document("notes", notes_store).unwrap();

        (fed, suffix.to_string())
    }

    #[test]
    fn test_cross_store_ownership_consistency() {
        // T15.12: Ownership in one store doesn't affect another
        let (fed, suffix) = create_multi_store_auth_federation("perm_cross_own");
        let ctx_a = writer_ctx("user_a");
        let ctx_b = writer_ctx("user_b");

        // user_a creates in "docs"
        fed.write_with_auth("docs", WriteOp::CreateDocument {
            id: "doc1".to_string(),
            doc_type: "text".to_string(),
            content: serde_json::json!({"text": "owned by A"}),
            tags: vec![],
            owner_id: Some("user_a".to_string()),
            visibility: Some(Visibility::Private),
        }, &ctx_a).unwrap();

        // user_b creates in "notes" with same ID
        fed.write_with_auth("notes", WriteOp::CreateDocument {
            id: "doc1".to_string(),
            doc_type: "text".to_string(),
            content: serde_json::json!({"text": "owned by B"}),
            tags: vec![],
            owner_id: Some("user_b".to_string()),
            visibility: Some(Visibility::Private),
        }, &ctx_b).unwrap();

        // user_b cannot see doc1 in "docs" (private, owned by A)
        let result_b_docs = fed.query_with_auth("docs", &text_query(), &ctx_b).unwrap();
        assert_eq!(result_b_docs.records.len(), 0, "user_b should not see user_a's private doc in 'docs'");

        // user_a cannot see doc1 in "notes" (private, owned by B)
        let result_a_notes = fed.query_with_auth("notes", &text_query(), &ctx_a).unwrap();
        assert_eq!(result_a_notes.records.len(), 0, "user_a should not see user_b's private doc in 'notes'");

        // Each user sees their own doc in their respective store
        let result_a_docs = fed.query_with_auth("docs", &text_query(), &ctx_a).unwrap();
        assert_eq!(result_a_docs.records.len(), 1);
        let result_b_notes = fed.query_with_auth("notes", &text_query(), &ctx_b).unwrap();
        assert_eq!(result_b_notes.records.len(), 1);

        cleanup_test_dir(&suffix);
    }

    #[test]
    fn test_cross_store_admin_sees_all_stores() {
        let (fed, suffix) = create_multi_store_auth_federation("perm_cross_admin");
        let ctx_a = writer_ctx("user_a");

        // Private doc in docs
        fed.write_with_auth("docs", WriteOp::CreateDocument {
            id: "secret_doc".to_string(),
            doc_type: "text".to_string(),
            content: serde_json::json!({"secret": true}),
            tags: vec![],
            owner_id: Some("user_a".to_string()),
            visibility: Some(Visibility::Private),
        }, &ctx_a).unwrap();

        // Private doc in notes
        fed.write_with_auth("notes", WriteOp::CreateDocument {
            id: "secret_note".to_string(),
            doc_type: "text".to_string(),
            content: serde_json::json!({"secret": true}),
            tags: vec![],
            owner_id: Some("user_a".to_string()),
            visibility: Some(Visibility::Private),
        }, &ctx_a).unwrap();

        // Admin sees both
        let admin = admin_ctx("admin_user");
        let docs_result = fed.query_with_auth("docs", &text_query(), &admin).unwrap();
        assert_eq!(docs_result.records.len(), 1, "Admin should see private docs");
        let notes_result = fed.query_with_auth("notes", &text_query(), &admin).unwrap();
        assert_eq!(notes_result.records.len(), 1, "Admin should see private notes");

        cleanup_test_dir(&suffix);
    }

    #[test]
    fn test_query_with_auth_role_restricted_visibility() {
        // T15.12: RoleRestricted visibility in query context
        let (fed, suffix) = create_auth_federation("perm_q_role");
        let ctx_a = writer_ctx("user_a");

        fed.write_with_auth("docs", WriteOp::CreateDocument {
            id: "editor_doc".to_string(),
            doc_type: "text".to_string(),
            content: serde_json::json!({"text": "for editors"}),
            tags: vec![],
            owner_id: Some("user_a".to_string()),
            visibility: Some(Visibility::RoleRestricted {
                roles: vec!["editor".to_string()],
            }),
        }, &ctx_a).unwrap();

        // User with editor role can see
        let editor = AccessContext::new("user_b", vec!["editor".to_string(), "writer".to_string()]);
        let result = fed.query_with_auth("docs", &text_query(), &editor).unwrap();
        assert_eq!(result.records.len(), 1, "Editor should see role-restricted doc");

        // User with only reader role cannot see
        let reader = reader_ctx("user_c");
        let result = fed.query_with_auth("docs", &text_query(), &reader).unwrap();
        assert_eq!(result.records.len(), 0, "Reader should not see editor-restricted doc");

        cleanup_test_dir(&suffix);
    }

    #[test]
    fn test_write_with_auth_protected_record_denied() {
        // T15.12: Write auth integrates with protection level
        let (fed, suffix) = create_auth_federation("perm_write_protected");
        
        // Create system data with ReadOnly protection
        fed.write_system_data("docs", WriteOp::CreateDocument {
            id: "readonly_doc".to_string(),
            doc_type: "text".to_string(),
            content: serde_json::json!({"config": "important"}),
            tags: vec![],
            owner_id: None,
            visibility: None,
        }, ProtectionLevel::ReadOnly).unwrap();

        // Even admin with write_with_auth cannot edit ReadOnly data
        // (because protection check happens in ABAC layer before ownership)
        let admin = admin_ctx("admin_user");
        let result = fed.write_with_auth("docs", WriteOp::UpdateDocument {
            id: "readonly_doc".to_string(),
            content: Some(serde_json::json!({"config": "hacked"})),
            tags: None,
            caller_id: Some("admin_user".to_string()),
        }, &admin);
        // ABAC denies edit on ReadOnly records
        assert!(result.is_err());
        cleanup_test_dir(&suffix);
    }

    #[test]
    fn test_mixed_visibility_query_filtering() {
        // T15.12: Multiple records with different visibility in one query
        let (fed, suffix) = create_auth_federation("perm_q_mixed");
        let ctx_a = writer_ctx("user_a");

        // Public doc
        fed.write_with_auth("docs", WriteOp::CreateDocument {
            id: "pub_doc".to_string(),
            doc_type: "text".to_string(),
            content: serde_json::json!({"text": "public"}),
            tags: vec![],
            owner_id: Some("user_a".to_string()),
            visibility: Some(Visibility::Public),
        }, &ctx_a).unwrap();

        // Private doc
        fed.write_with_auth("docs", WriteOp::CreateDocument {
            id: "priv_doc".to_string(),
            doc_type: "text".to_string(),
            content: serde_json::json!({"text": "private"}),
            tags: vec![],
            owner_id: Some("user_a".to_string()),
            visibility: Some(Visibility::Private),
        }, &ctx_a).unwrap();

        // Shared doc (shared with user_b)
        fed.write_with_auth("docs", WriteOp::CreateDocument {
            id: "shared_doc".to_string(),
            doc_type: "text".to_string(),
            content: serde_json::json!({"text": "shared"}),
            tags: vec![],
            owner_id: Some("user_a".to_string()),
            visibility: Some(Visibility::Shared { user_ids: vec!["user_b".to_string()] }),
        }, &ctx_a).unwrap();

        // No-ownership doc
        fed.write_with_auth("docs", WriteOp::CreateDocument {
            id: "legacy_doc".to_string(),
            doc_type: "text".to_string(),
            content: serde_json::json!({"text": "legacy"}),
            tags: vec![],
            owner_id: None,
            visibility: None,
        }, &ctx_a).unwrap();

        // user_a (owner) sees all 4
        let result_a = fed.query_with_auth("docs", &text_query(), &ctx_a).unwrap();
        assert_eq!(result_a.records.len(), 4, "Owner sees all records");

        // user_b sees: public(1) + shared(1) + legacy(1) = 3
        let ctx_b = writer_ctx("user_b");
        let result_b = fed.query_with_auth("docs", &text_query(), &ctx_b).unwrap();
        assert_eq!(result_b.records.len(), 3, "user_b sees public + shared + legacy");

        // user_c sees: public(1) + legacy(1) = 2
        let ctx_c = writer_ctx("user_c");
        let result_c = fed.query_with_auth("docs", &text_query(), &ctx_c).unwrap();
        assert_eq!(result_c.records.len(), 2, "user_c sees public + legacy only");

        cleanup_test_dir(&suffix);
    }

    // ========================================================================
    // T15.17 补充：种子数据初始化 + 降级流程端到端
    // ========================================================================

    #[test]
    fn test_system_data_immutable_to_none_downgrade_flow() {
        // T15.17: Full downgrade flow — verifies permission checks at each step.
        //
        // Note: downgrade_protection() currently validates permissions but does not
        // persist the new protection level (Store::update_ownership() is not yet
        // implemented). This test verifies the authorization logic is correct.
        let (fed, suffix) = create_auth_federation("sys_downgrade_flow");

        // Step 1: Create immutable system data
        fed.write_system_data("docs", WriteOp::CreateDocument {
            id: "audit_log".to_string(),
            doc_type: "text".to_string(),
            content: serde_json::json!({"event": "system_init", "ts": 1707300000}),
            tags: vec!["audit".to_string()],
            owner_id: None,
            visibility: None,
        }, ProtectionLevel::Immutable).unwrap();

        let admin = admin_ctx("admin_user");
        let regular = writer_ctx("user_a");

        // Step 2: Cannot delete Immutable directly
        let result = fed.write_with_auth("docs", WriteOp::DeleteDocument {
            id: "audit_log".to_string(),
            caller_id: Some("admin_user".to_string()),
        }, &admin);
        assert!(result.is_err(), "Cannot delete Immutable record directly");

        // Step 3: Non-admin cannot downgrade
        let result = fed.downgrade_protection("docs", "audit_log", ProtectionLevel::None, &regular);
        assert!(result.is_err(), "Non-admin cannot downgrade protection");

        // Step 4: Admin can downgrade Immutable → ReadOnly (permission check passes)
        let result = fed.downgrade_protection("docs", "audit_log", ProtectionLevel::ReadOnly, &admin);
        assert!(result.is_ok(), "Admin should be allowed to downgrade Immutable → ReadOnly");

        // Step 5: Admin can downgrade Immutable → None (permission check passes)
        // (Still reads Immutable from store since persistence is not yet implemented,
        //  but the authorization check itself succeeds)
        let result = fed.downgrade_protection("docs", "audit_log", ProtectionLevel::None, &admin);
        assert!(result.is_ok(), "Admin should be allowed to downgrade Immutable → None");

        // Step 6: Cannot upgrade to System via admin (requires system_override)
        let result = fed.downgrade_protection("docs", "audit_log", ProtectionLevel::System, &admin);
        assert!(result.is_err(), "Cannot upgrade to System via API without system_override");

        cleanup_test_dir(&suffix);
    }

    #[test]
    fn test_seed_data_initialization_scenario() {
        // T15.17: Simulates typical seed data initialization at system startup
        let (fed, suffix) = create_auth_federation("sys_seed");

        // Seed 1: System config (Immutable — should never be modified)
        fed.write_system_data("docs", WriteOp::CreateDocument {
            id: "sys_config_db_version".to_string(),
            doc_type: "text".to_string(),
            content: serde_json::json!({
                "key": "db_version",
                "value": "3.3.0",
                "description": "Federation database version"
            }),
            tags: vec!["system".to_string(), "config".to_string()],
            owner_id: None,
            visibility: None,
        }, ProtectionLevel::Immutable).unwrap();

        // Seed 2: Default roles (ReadOnly — can be read by everyone, not editable)
        fed.write_system_data("docs", WriteOp::CreateDocument {
            id: "sys_default_roles".to_string(),
            doc_type: "text".to_string(),
            content: serde_json::json!({
                "roles": ["admin", "writer", "reader", "editor"]
            }),
            tags: vec!["system".to_string(), "roles".to_string()],
            owner_id: None,
            visibility: None,
        }, ProtectionLevel::ReadOnly).unwrap();

        // Seed 3: System announcement (None — admin can update later)
        fed.write_system_data("docs", WriteOp::CreateDocument {
            id: "sys_announcement".to_string(),
            doc_type: "text".to_string(),
            content: serde_json::json!({
                "title": "Welcome to AiFastDB",
                "message": "System initialized successfully"
            }),
            tags: vec!["system".to_string()],
            owner_id: None,
            visibility: None,
        }, ProtectionLevel::None).unwrap();

        // Verify: All 3 records created, all owned by SYSTEM
        let admin = admin_ctx("admin_user");
        let result = fed.query_with_auth("docs", &text_query(), &admin).unwrap();
        assert_eq!(result.records.len(), 3, "All seed data should be visible to admin");

        for record in &result.records {
            if let Some(ownership) = record.ownership() {
                assert_eq!(ownership.owner_id, SYSTEM_OWNER_ID, "All seed data should be owned by SYSTEM");
            }
        }

        // Regular user can see all (Public visibility) but cannot edit any
        let user = writer_ctx("user_a");
        let user_result = fed.query_with_auth("docs", &text_query(), &user).unwrap();
        assert_eq!(user_result.records.len(), 3, "Public system data visible to all");

        // Regular user cannot update Immutable config
        let result = fed.write_with_auth("docs", WriteOp::UpdateDocument {
            id: "sys_config_db_version".to_string(),
            content: Some(serde_json::json!({"value": "hacked"})),
            tags: None,
            caller_id: Some("user_a".to_string()),
        }, &user);
        assert!(result.is_err(), "User cannot edit system immutable config");

        // Regular user cannot update ReadOnly roles
        let result = fed.write_with_auth("docs", WriteOp::UpdateDocument {
            id: "sys_default_roles".to_string(),
            content: Some(serde_json::json!({"roles": ["hacker"]})),
            tags: None,
            caller_id: Some("user_a".to_string()),
        }, &user);
        assert!(result.is_err(), "User cannot edit system ReadOnly roles");

        // Regular user cannot update None-protection system data (owner is SYSTEM)
        let result = fed.write_with_auth("docs", WriteOp::UpdateDocument {
            id: "sys_announcement".to_string(),
            content: Some(serde_json::json!({"title": "Hacked"})),
            tags: None,
            caller_id: Some("user_a".to_string()),
        }, &user);
        assert!(result.is_err(), "User cannot edit system data even with None protection");

        cleanup_test_dir(&suffix);
    }

    #[test]
    fn test_system_data_visible_in_query_with_system_flag() {
        // T15.17: System data should include is_system_data flag in RecordActions
        let (fed, suffix) = create_auth_federation("sys_flag");

        fed.write_system_data("docs", WriteOp::CreateDocument {
            id: "sys_doc".to_string(),
            doc_type: "text".to_string(),
            content: serde_json::json!({"system": true}),
            tags: vec![],
            owner_id: None,
            visibility: None,
        }, ProtectionLevel::ReadOnly).unwrap();

        // Verify ownership meta
        let store = fed.use_store("docs").unwrap();
        let record = store.get("sys_doc").unwrap().unwrap();
        let ownership = record.ownership().expect("System data should have ownership");
        assert_eq!(ownership.owner_id, SYSTEM_OWNER_ID);
        assert_eq!(ownership.protection, ProtectionLevel::ReadOnly);
        assert!(ownership.is_system_data());

        cleanup_test_dir(&suffix);
    }
}
