eidetica 0.2.0

Decentralized DB. Remember Everything. Everywhere. All At Once.
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
//!
//! Provides the main database structures (`Instance` and `Database`).
//!
//! `Instance` manages multiple `Database` instances and interacts with the storage `Database`.
//! `Database` represents a single, independent history of data entries, analogous to a table or branch.

use std::{
    collections::HashMap,
    sync::{Arc, Mutex, Weak},
};

use ed25519_dalek::VerifyingKey;
use handle_trait::Handle;

use crate::{
    Database, Entry, Result, auth::crypto::format_public_key, backend::BackendImpl, entry::ID,
    sync::Sync, user::User,
};

pub mod backend;
pub mod errors;
pub mod legacy_ops;
pub mod settings_merge;

// Re-export main types for easier access
use backend::Backend;
pub use errors::InstanceError;
pub use legacy_ops::LegacyInstanceOps;

/// Private constants for device identity management
const DEVICE_KEY_NAME: &str = "_device_key";

/// Indicates whether an entry write originated locally or from a remote source (e.g., sync).
///
/// This distinction allows different callbacks to be triggered based on the write source,
/// enabling behaviors like "only trigger sync for local writes" or "only update UI for remote writes".
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum WriteSource {
    /// Write originated from a local transaction commit
    Local,
    /// Write originated from a remote source (e.g., sync, replication)
    Remote,
}

/// Callback function trait for write operations.
///
/// Receives the entry that was written, the database it was written to, and the instance.
/// Used for both local and remote write callbacks.
///
/// This trait alias can be used both as a trait bound (e.g., `F: WriteCallback`) and as a
/// trait object type for storage (e.g., `Arc<dyn WriteCallback>`).
pub trait WriteCallback:
    Fn(&Entry, &Database, &Instance) -> Result<()> + Send + std::marker::Sync
{
}

// Blanket implementation: any type that satisfies the bounds automatically implements WriteCallback
impl<T> WriteCallback for T where
    T: Fn(&Entry, &Database, &Instance) -> Result<()> + Send + std::marker::Sync
{
}

/// Type alias for a collection of write callbacks
type CallbackVec = Vec<Arc<dyn WriteCallback>>;

/// Type alias for the per-database callback map key
type CallbackKey = (WriteSource, ID);

/// Internal state for Instance
///
/// This structure holds the actual implementation data for Instance.
/// Instance itself is just a cheap-to-clone handle wrapping Arc<InstanceInternal>.
pub(crate) struct InstanceInternal {
    /// The database storage backend
    backend: Backend,
    /// Synchronization module for this database instance
    /// TODO: Overengineered, Sync can be created by default but disabled
    sync: std::sync::OnceLock<Arc<Sync>>,
    /// Root ID of the _users system database
    users_db_id: ID,
    /// Root ID of the _databases system database
    databases_db_id: ID,
    /// Per-database callbacks keyed by (WriteSource, tree_id)
    write_callbacks: Mutex<HashMap<CallbackKey, CallbackVec>>,
    /// Global callbacks keyed by WriteSource (triggered regardless of database)
    global_write_callbacks: Mutex<HashMap<WriteSource, CallbackVec>>,
}

impl std::fmt::Debug for InstanceInternal {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("InstanceInternal")
            .field("backend", &"<BackendDB>")
            .field("sync", &self.sync)
            .field("users_db_id", &self.users_db_id)
            .field("databases_db_id", &self.databases_db_id)
            .field(
                "write_callbacks",
                &format!(
                    "<{} per-db callbacks>",
                    self.write_callbacks.lock().unwrap().len()
                ),
            )
            .field(
                "global_write_callbacks",
                &format!(
                    "<{} global callbacks>",
                    self.global_write_callbacks.lock().unwrap().len()
                ),
            )
            .finish()
    }
}
/// Database implementation on top of the storage backend.
///
/// Instance manages infrastructure only:
/// - Backend storage and device identity (_device_key)
/// - System databases (_users, _databases, _sync)
/// - User account management (create, login, list)
///
/// All database creation and key operations happen through User after login.
///
/// Instance is a cheap-to-clone handle around `Arc<InstanceInternal>`.
///
/// ## Example
///
/// ```
/// # use eidetica::{backend::database::InMemory, Instance, crdt::Doc};
/// let instance = Instance::open(Box::new(InMemory::new()))?;
///
/// // Create passwordless user
/// instance.create_user("alice", None)?;
/// let mut user = instance.login_user("alice", None)?;
///
/// // Use User API for operations
/// let mut settings = Doc::new();
/// settings.set_string("name", "my_database");
/// let default_key = user.get_default_key()?;
/// let db = user.create_database(settings, &default_key)?;
/// # Ok::<(), eidetica::Error>(())
/// ```
#[derive(Clone, Debug, Handle)]
pub struct Instance {
    inner: Arc<InstanceInternal>,
}

/// Weak reference to an Instance.
///
/// This is a weak handle that does not prevent the Instance from being dropped.
/// Dependent objects (Database, Sync, BackgroundSync) hold weak references to avoid
/// circular reference cycles that would leak memory.
///
/// Use `upgrade()` to convert to a strong `Instance` reference.
#[derive(Clone, Debug, Handle)]
pub struct WeakInstance {
    inner: Weak<InstanceInternal>,
}

impl Instance {
    /// Load an existing Instance or create a new one (recommended).
    ///
    /// This is the recommended method for initializing an Instance. It automatically detects
    /// whether the backend contains existing system state (device key and system databases)
    /// and loads them, or creates new ones if starting fresh.
    ///
    /// Instance manages infrastructure only:
    /// - Backend storage and device identity (_device_key)
    /// - System databases (_users, _databases, _sync)
    /// - User account management (create, login, list)
    ///
    /// All database creation and key operations require explicit User login.
    ///
    /// # Arguments
    /// * `backend` - The storage backend to use
    ///
    /// # Returns
    /// A Result containing the configured Instance
    ///
    /// # Example
    /// ```
    /// # use eidetica::{backend::database::InMemory, Instance, crdt::Doc};
    /// let backend = InMemory::new();
    /// let instance = Instance::open(Box::new(backend))?;
    ///
    /// // Create and login user explicitly
    /// instance.create_user("alice", None)?;
    /// let mut user = instance.login_user("alice", None)?;
    ///
    /// // Use User API for operations
    /// let mut settings = Doc::new();
    /// settings.set_string("name", "my_database");
    /// let default_key = user.get_default_key()?;
    /// let db = user.create_database(settings, &default_key)?;
    /// # Ok::<(), eidetica::Error>(())
    /// ```
    pub fn open(backend: Box<dyn BackendImpl>) -> Result<Self> {
        use crate::constants::{DATABASES, USERS};

        let backend: Arc<dyn BackendImpl> = Arc::from(backend);

        // Load device_key first
        let _device_key = match backend.get_private_key(DEVICE_KEY_NAME)? {
            Some(key) => key,
            None => {
                // New backend: initialize like create()
                return Self::create_internal(backend);
            }
        };

        // Existing backend: load system databases
        let all_roots = backend.all_roots()?;

        // Find system databases by name
        let mut users_db_root = None;
        let mut databases_db_root = None;

        for root_id in all_roots {
            // FIXME(security): handle the security and loading of these databases in a better way
            // Use open_readonly temporarily to check name without setting up auth
            // Note: We can't use self.clone() here because self doesn't exist yet during construction
            // So we create a temporary Instance just for this lookup
            //
            // SAFETY: The temporary instance has empty users_db_id and databases_db_id placeholders.
            // This is safe because:
            // 1. We only use it for Database::open_readonly() which doesn't access these fields
            // 2. The Database only calls get_name() which reads from the settings store
            // 3. The temporary instance is dropped immediately after name lookup
            // 4. No other code paths will access the invalid system database IDs
            let temp_instance = Self {
                inner: Arc::new(InstanceInternal {
                    backend: Backend::new(Arc::clone(&backend)),
                    sync: std::sync::OnceLock::new(),
                    users_db_id: ID::from(""), // Placeholder - not accessed during name lookup
                    databases_db_id: ID::from(""), // Placeholder - not accessed during name lookup
                    write_callbacks: Mutex::new(HashMap::new()),
                    global_write_callbacks: Mutex::new(HashMap::new()),
                }),
            };
            let temp_db = Database::open_readonly(root_id.clone(), &temp_instance)?;
            if let Ok(name) = temp_db.get_name() {
                match name.as_str() {
                    USERS => {
                        if users_db_root.is_some() {
                            panic!(
                                "CRITICAL SECURITY ERROR: Multiple {USERS} databases found in backend. \
                                     This indicates database corruption or a potential security breach. \
                                     Backend integrity compromised."
                            );
                        }
                        users_db_root = Some(root_id);
                    }
                    DATABASES => {
                        if databases_db_root.is_some() {
                            panic!(
                                "CRITICAL SECURITY ERROR: Multiple {DATABASES} databases found in backend. \
                                     This indicates database corruption or a potential security breach. \
                                     Backend integrity compromised."
                            );
                        }
                        databases_db_root = Some(root_id);
                    }
                    _ => {} // Ignore other databases
                }
            }

            // Stop searching if we found both
            if users_db_root.is_some() && databases_db_root.is_some() {
                break;
            }
        }

        // Verify we found both system databases
        let users_db_root = users_db_root.ok_or(InstanceError::SystemDatabaseNotFound {
            database_name: USERS.to_string(),
        })?;
        let databases_db_root = databases_db_root.ok_or(InstanceError::SystemDatabaseNotFound {
            database_name: DATABASES.to_string(),
        })?;

        let inner = Arc::new(InstanceInternal {
            backend: Backend::new(backend),
            sync: std::sync::OnceLock::new(),
            users_db_id: users_db_root,
            databases_db_id: databases_db_root,
            write_callbacks: Mutex::new(HashMap::new()),
            global_write_callbacks: Mutex::new(HashMap::new()),
        });

        Ok(Self { inner })
    }

    /// Create a new Instance on a fresh backend (strict creation).
    ///
    /// This method creates a new Instance and fails if the backend is already initialized
    /// (contains a device key and system databases). Use this when you want to ensure
    /// you're creating a fresh instance.
    ///
    /// Instance manages infrastructure only:
    /// - Backend storage and device identity (_device_key)
    /// - System databases (_users, _databases, _sync)
    /// - User account management (create, login, list)
    ///
    /// All database creation and key operations require explicit User login.
    ///
    /// For most use cases, prefer `Instance::open()` which automatically handles both
    /// new and existing backends.
    ///
    /// # Arguments
    /// * `backend` - The storage backend to use (must be uninitialized)
    ///
    /// # Returns
    /// A Result containing the configured Instance, or InstanceAlreadyExists error
    /// if the backend is already initialized.
    ///
    /// # Example
    /// ```
    /// # use eidetica::{backend::database::InMemory, Instance, crdt::Doc};
    /// let backend = InMemory::new();
    /// let instance = Instance::create(Box::new(backend))?;
    ///
    /// // Create and login user explicitly
    /// instance.create_user("alice", None)?;
    /// let mut user = instance.login_user("alice", None)?;
    ///
    /// // Use User API for operations
    /// let mut settings = Doc::new();
    /// settings.set_string("name", "my_database");
    /// let default_key = user.get_default_key()?;
    /// let db = user.create_database(settings, &default_key)?;
    /// # Ok::<(), eidetica::Error>(())
    /// ```
    pub fn create(backend: Box<dyn BackendImpl>) -> Result<Self> {
        let backend: Arc<dyn BackendImpl> = Arc::from(backend);

        // Check if already initialized
        if backend.get_private_key(DEVICE_KEY_NAME)?.is_some() {
            return Err(InstanceError::InstanceAlreadyExists.into());
        }

        // Create new instance
        Self::create_internal(backend)
    }

    /// Internal implementation of new that works with Arc<dyn BackendImpl>
    pub(crate) fn create_internal(backend: Arc<dyn BackendImpl>) -> Result<Self> {
        use crate::{
            auth::crypto::{format_public_key, generate_keypair},
            user::system_databases::{create_databases_tracking, create_users_database},
        };

        // 1. Generate and store instance device key (_device_key)
        let (device_key, device_pubkey) = generate_keypair();
        let device_pubkey_str = format_public_key(&device_pubkey);
        backend.store_private_key(DEVICE_KEY_NAME, device_key.clone())?;

        // 2. Create system databases with device_key passed directly
        // Create a temporary Instance for database creation (databases will store full IDs later)
        //
        // SAFETY: The temporary instance has empty users_db_id and databases_db_id placeholders.
        // This is safe because:
        // 1. We only use it to create new system databases via Database::create()
        // 2. Database::create() doesn't access the instance's system database IDs
        // 3. The system databases don't exist yet, so their IDs can't be referenced
        // 4. The temporary instance is only used during initial setup and discarded
        // 5. The real instance is constructed afterward with the correct database IDs
        let temp_instance = Self {
            inner: Arc::new(InstanceInternal {
                backend: Backend::new(Arc::clone(&backend)),
                sync: std::sync::OnceLock::new(),
                users_db_id: ID::from(""), // Placeholder - system DBs don't exist yet
                databases_db_id: ID::from(""), // Placeholder - system DBs don't exist yet
                write_callbacks: Mutex::new(HashMap::new()),
                global_write_callbacks: Mutex::new(HashMap::new()),
            }),
        };
        let users_db = create_users_database(&temp_instance, &device_key, &device_pubkey_str)?;
        let databases_db =
            create_databases_tracking(&temp_instance, &device_key, &device_pubkey_str)?;

        // 3. Store root IDs and return instance
        let inner = Arc::new(InstanceInternal {
            backend: Backend::new(backend),
            sync: std::sync::OnceLock::new(),
            users_db_id: users_db.root_id().clone(),
            databases_db_id: databases_db.root_id().clone(),
            write_callbacks: Mutex::new(HashMap::new()),
            global_write_callbacks: Mutex::new(HashMap::new()),
        });

        Ok(Self { inner })
    }

    /// Get a reference to the backend
    pub fn backend(&self) -> &Backend {
        &self.inner.backend
    }

    // === Backend pass-through methods (pub(crate) for internal use) ===

    /// Get an entry from the backend
    pub(crate) fn get(&self, id: &crate::entry::ID) -> Result<crate::entry::Entry> {
        self.inner.backend.get(id)
    }

    /// Put an entry into the backend
    pub(crate) fn put(
        &self,
        verification_status: crate::backend::VerificationStatus,
        entry: crate::entry::Entry,
    ) -> Result<()> {
        self.inner.backend.put(verification_status, entry)
    }

    /// Get tips for a tree
    pub(crate) fn get_tips(&self, tree: &crate::entry::ID) -> Result<Vec<crate::entry::ID>> {
        self.inner.backend.get_tips(tree)
    }

    // === System database accessors ===

    /// Get the _users database
    ///
    /// This constructs a Database instance on-the-fly to avoid circular references.
    pub(crate) fn users_db(&self) -> Result<Database> {
        let device_key = self
            .inner
            .backend
            .get_private_key(DEVICE_KEY_NAME)?
            .ok_or(InstanceError::DeviceKeyNotFound)?;

        Database::open(
            self.clone(),
            &self.inner.users_db_id,
            device_key,
            "_device_key".to_string(),
        )
    }

    // === User Management ===

    /// Create a new user account with flexible password handling.
    ///
    /// Creates a user with or without password protection. Passwordless users are appropriate
    /// for embedded applications where filesystem access = database access.
    ///
    /// # Arguments
    /// * `user_id` - Unique user identifier (username)
    /// * `password` - Optional password. If None, user is passwordless (instant login, no encryption)
    ///
    /// # Returns
    /// A Result containing the user's UUID (stable internal identifier)
    pub fn create_user(&self, user_id: &str, password: Option<&str>) -> Result<String> {
        use crate::user::system_databases::create_user;

        let users_db = self.users_db()?;
        let (user_uuid, _user_info) = create_user(&users_db, self, user_id, password)?;
        Ok(user_uuid)
    }

    /// Login a user with flexible password handling.
    ///
    /// Returns a User session object that provides access to user operations.
    /// For password-protected users, provide the password. For passwordless users, pass None.
    ///
    /// # Arguments
    /// * `user_id` - User identifier (username)
    /// * `password` - Optional password. None for passwordless users.
    ///
    /// # Returns
    /// A Result containing the User session
    pub fn login_user(&self, user_id: &str, password: Option<&str>) -> Result<User> {
        use crate::user::system_databases::login_user;

        let users_db = self.users_db()?;
        login_user(&users_db, self, user_id, password)
    }

    /// List all user IDs.
    ///
    /// # Returns
    /// A Result containing a vector of user IDs
    pub fn list_users(&self) -> Result<Vec<String>> {
        use crate::user::system_databases::list_users;

        let users_db = self.users_db()?;
        list_users(&users_db)
    }

    // === User-Sync Integration ===

    // === Device Identity Management ===
    //
    // The Instance's device identity (_device_key) is stored in the backend.

    /// Get the device ID (public key).
    ///
    /// The device key (_device_key) is stored in the backend.
    ///
    /// # Returns
    /// A `Result` containing the device's public key (device ID).
    pub fn device_id(&self) -> Result<VerifyingKey> {
        let device_key = self
            .inner
            .backend
            .get_private_key(DEVICE_KEY_NAME)?
            .ok_or_else(|| crate::Error::from(InstanceError::DeviceKeyNotFound))?;
        Ok(device_key.verifying_key())
    }

    /// Get the device ID as a formatted string.
    ///
    /// This is a convenience method that returns the device ID (public key)
    /// in a standard formatted string representation.
    ///
    /// # Returns
    /// A `Result` containing the formatted device ID string.
    pub fn device_id_string(&self) -> Result<String> {
        let device_key = self.device_id()?;
        Ok(format_public_key(&device_key))
    }

    /// Load an existing database from the backend by its root ID.
    ///
    /// # Arguments
    /// * `root_id` - The content-addressable ID of the root `Entry` of the database to load.
    ///
    /// # Returns
    /// A `Result` containing the loaded `Database` or an error if the root ID is not found.
    pub fn load_database(&self, root_id: &ID) -> Result<Database> {
        // First validate the root_id exists in the backend
        // Make sure the entry exists
        self.inner.backend.get(root_id)?;

        // Create a database object with the given root_id
        let database = Database::open_readonly(root_id.clone(), self)?;
        Ok(database)
    }

    /// Load all databases stored in the backend.
    ///
    /// This retrieves all known root entry IDs from the backend and constructs
    /// `Database` instances for each.
    ///
    /// # Returns
    /// A `Result` containing a vector of all `Database` instances or an error.
    pub fn all_databases(&self) -> Result<Vec<Database>> {
        let root_ids = self.inner.backend.all_roots()?;
        let mut databases = Vec::new();

        for root_id in root_ids {
            let database = Database::open_readonly(root_id.clone(), self)?;
            databases.push(database);
        }

        Ok(databases)
    }

    /// Find databases by their assigned name.
    ///
    /// Searches through all databases in the backend and returns those whose "name"
    /// setting matches the provided name.
    ///
    /// # Arguments
    /// * `name` - The name to search for.
    ///
    /// # Returns
    /// A `Result` containing a vector of `Database` instances whose name matches,
    /// or an error.
    ///
    /// # Errors
    /// Returns `InstanceError::DatabaseNotFound` if no databases with the specified name are found.
    pub fn find_database(&self, name: impl AsRef<str>) -> Result<Vec<Database>> {
        let name = name.as_ref();
        let all_databases = self.all_databases()?;
        let mut matching_databases = Vec::new();

        for database in all_databases {
            // Attempt to get the name from the database's settings
            if let Ok(database_name) = database.get_name()
                && database_name == name
            {
                matching_databases.push(database);
            }
            // Ignore databases where getting the name fails or doesn't match
        }

        if matching_databases.is_empty() {
            Err(InstanceError::DatabaseNotFound {
                name: name.to_string(),
            }
            .into())
        } else {
            Ok(matching_databases)
        }
    }

    // === Authentication Key Management ===

    /// List all private key IDs.
    ///
    /// # Returns
    /// A `Result` containing a vector of key IDs or an error.
    pub fn list_private_keys(&self) -> Result<Vec<String>> {
        // List keys from backend storage
        self.inner.backend.list_private_keys()
    }

    // === Synchronization Management ===
    //
    // These methods provide access to the Sync module for managing synchronization
    // settings and state for this database instance.

    /// Initializes the Sync module for this instance.
    ///
    /// Enables synchronization operations for this instance. This method is idempotent;
    /// calling it multiple times has no effect.
    ///
    /// # Errors
    /// Returns an error if the sync settings database cannot be created or if device key
    /// generation/storage fails.
    pub fn enable_sync(&self) -> Result<()> {
        // Check if there is an existing Sync database already configured
        if self.inner.sync.get().is_some() {
            return Ok(());
        }
        let sync = Sync::new(self.clone())?;
        let sync_arc = Arc::new(sync);

        // Register global callback for automatic sync on local writes
        let sync_for_callback = Arc::clone(&sync_arc);
        self.register_global_write_callback(
            WriteSource::Local,
            move |entry, database, instance| {
                sync_for_callback.on_local_write(entry, database, instance)
            },
        )?;

        let _ = self.inner.sync.set(sync_arc);
        Ok(())
    }

    /// Get a reference to the Sync module.
    ///
    /// Returns a cheap-to-clone Arc handle to the Sync module. The Sync module
    /// uses interior mutability (AtomicBool and OnceLock) so &self methods are sufficient.
    ///
    /// # Returns
    /// An `Option` containing an `Arc<Sync>` if the Sync module is initialized.
    pub fn sync(&self) -> Option<Arc<Sync>> {
        self.inner.sync.get().map(Arc::clone)
    }

    // === Entry Write Coordination ===
    //
    // All entry writes go through Instance::put_entry() which handles backend storage
    // and callback dispatch. This centralizes write coordination and ensures hooks fire.

    /// Register a callback to be invoked when entries are written to a database.
    ///
    /// The callback receives the entry, database, and instance as parameters.
    ///
    /// # Arguments
    /// * `source` - The write source to monitor (Local or Remote)
    /// * `tree_id` - The root ID of the database tree to monitor
    /// * `callback` - Function to invoke on writes
    ///
    /// # Returns
    /// A Result indicating success or failure
    pub(crate) fn register_write_callback<F>(
        &self,
        source: WriteSource,
        tree_id: ID,
        callback: F,
    ) -> Result<()>
    where
        F: Fn(&Entry, &Database, &Instance) -> Result<()> + Send + std::marker::Sync + 'static,
    {
        let mut callbacks = self.inner.write_callbacks.lock().unwrap();
        callbacks
            .entry((source, tree_id))
            .or_default()
            .push(Arc::new(callback));
        Ok(())
    }

    /// Register a global callback to be invoked on all writes of a specific source.
    ///
    /// Global callbacks are invoked for all writes of the specified source across all databases.
    /// This is useful for system-wide operations like synchronization that need to track
    /// changes across all databases.
    ///
    /// # Arguments
    /// * `source` - The write source to monitor (Local or Remote)
    /// * `callback` - Function to invoke on all writes
    ///
    /// # Returns
    /// A Result indicating success or failure
    pub(crate) fn register_global_write_callback<F>(
        &self,
        source: WriteSource,
        callback: F,
    ) -> Result<()>
    where
        F: Fn(&Entry, &Database, &Instance) -> Result<()> + Send + std::marker::Sync + 'static,
    {
        let mut callbacks = self.inner.global_write_callbacks.lock().unwrap();
        callbacks
            .entry(source)
            .or_default()
            .push(Arc::new(callback));
        Ok(())
    }

    /// Write an entry to the backend and dispatch callbacks.
    ///
    /// This is the central coordination point for all entry writes in the system.
    /// All writes must go through this method to ensure:
    /// - Entries are persisted to the backend
    /// - Appropriate callbacks are triggered based on write source
    /// - Hooks have full context (entry, database, instance)
    ///
    /// # Arguments
    /// * `tree_id` - The root ID of the database being written to
    /// * `verification` - Authentication verification status of the entry
    /// * `entry` - The entry to write
    /// * `source` - Whether this is a local or remote write
    ///
    /// # Returns
    /// A Result indicating success or failure
    pub fn put_entry(
        &self,
        tree_id: &ID,
        verification: crate::backend::VerificationStatus,
        entry: Entry,
        source: WriteSource,
    ) -> Result<()> {
        // 1. Persist to backend storage
        self.backend().put(verification, entry.clone())?;

        // 2. Look up and execute callbacks based on write source
        // Clone the callbacks to avoid holding the lock while executing callbacks.
        let per_db_callbacks = self
            .inner
            .write_callbacks
            .lock()
            .unwrap()
            .get(&(source, tree_id.clone()))
            .cloned();

        let global_callbacks = self
            .inner
            .global_write_callbacks
            .lock()
            .unwrap()
            .get(&source)
            .cloned();

        // 3. Execute callbacks if any are registered
        let has_callbacks = per_db_callbacks.is_some() || global_callbacks.is_some();
        if has_callbacks {
            // Create a Database handle for the callbacks
            // Use open_readonly since we only need it for callback context
            let database = Database::open_readonly(tree_id.clone(), self)?;

            // Execute per-database callbacks
            if let Some(callbacks) = per_db_callbacks {
                for callback in callbacks {
                    if let Err(e) = callback(&entry, &database, self) {
                        tracing::error!(
                            tree_id = %tree_id,
                            entry_id = %entry.id(),
                            source = ?source,
                            "Per-database callback failed: {}", e
                        );
                        // Continue executing other callbacks even if one fails
                    }
                }
            }

            // Execute global callbacks
            if let Some(callbacks) = global_callbacks {
                for callback in callbacks {
                    if let Err(e) = callback(&entry, &database, self) {
                        tracing::error!(
                            tree_id = %tree_id,
                            entry_id = %entry.id(),
                            source = ?source,
                            "Global callback failed: {}", e
                        );
                        // Continue executing other callbacks even if one fails
                    }
                }
            }
        }

        Ok(())
    }

    /// Downgrade to a weak reference.
    ///
    /// Creates a weak reference that does not prevent the Instance from being dropped.
    /// This is useful for preventing circular reference cycles in dependent objects.
    ///
    /// # Returns
    /// A `WeakInstance` that can be upgraded back to a strong reference.
    pub fn downgrade(&self) -> WeakInstance {
        WeakInstance {
            inner: Arc::downgrade(&self.inner),
        }
    }
}

impl WeakInstance {
    /// Upgrade to a strong reference.
    ///
    /// Attempts to upgrade this weak reference to a strong `Instance` reference.
    /// Returns `None` if the Instance has already been dropped.
    ///
    /// # Returns
    /// `Some(Instance)` if the Instance still exists, `None` otherwise.
    ///
    /// # Example
    /// ```
    /// # use eidetica::{backend::database::InMemory, Instance};
    /// let instance = Instance::open(Box::new(InMemory::new()))?;
    /// let weak = instance.downgrade();
    ///
    /// // Upgrade works while instance exists
    /// assert!(weak.upgrade().is_some());
    ///
    /// drop(instance);
    /// // Upgrade fails after instance is dropped
    /// assert!(weak.upgrade().is_none());
    /// # Ok::<(), eidetica::Error>(())
    /// ```
    pub fn upgrade(&self) -> Option<Instance> {
        self.inner.upgrade().map(|inner| Instance { inner })
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::{Error, backend::database::InMemory, crdt::Doc, instance::LegacyInstanceOps};

    #[test]
    fn test_create_user() -> Result<(), Error> {
        let backend = InMemory::new();
        let instance = Instance::open(Box::new(backend))?;

        // Create user with password
        let user_uuid = instance.create_user("alice", Some("password123")).unwrap();

        assert!(!user_uuid.is_empty());

        // Verify user appears in list
        let users = instance.list_users().unwrap();
        assert_eq!(users.len(), 1);
        assert_eq!(users[0], "alice");
        Ok(())
    }

    #[test]
    fn test_login_user() -> Result<(), Error> {
        let backend = InMemory::new();
        let instance = Instance::open(Box::new(backend))?;

        // Create user
        instance.create_user("alice", Some("password123")).unwrap();

        // Login user
        let user = instance.login_user("alice", Some("password123")).unwrap();
        assert_eq!(user.username(), "alice");

        // Invalid password should fail
        let result = instance.login_user("alice", Some("wrong_password"));
        assert!(result.is_err());
        Ok(())
    }

    #[test]
    fn test_new_database() {
        let backend = InMemory::new();
        let instance = Instance::open(Box::new(backend)).expect("Failed to create test instance");

        // Create database with deprecated API
        let mut settings = Doc::new();
        settings.set_string("name", "test_db");

        let database = instance.new_database(settings, "_device_key").unwrap();
        assert_eq!(database.get_name().unwrap(), "test_db");
    }

    #[test]
    fn test_new_database_default() {
        let backend = InMemory::new();
        let instance = Instance::open(Box::new(backend)).expect("Failed to create test instance");

        // Create database with default settings
        let database = instance.new_database_default("_device_key").unwrap();
        let settings = database.get_settings().unwrap();

        // Should have auto-generated database_id
        assert!(settings.get_string("database_id").is_ok());
    }

    #[test]
    fn test_new_database_without_key_fails() -> Result<(), Error> {
        let backend = InMemory::new();
        let instance = Instance::open(Box::new(backend))?;

        // Create database requires a signing key
        let mut settings = Doc::new();
        settings.set_string("name", "test_db");

        // This will succeed if a valid key is provided, but we're testing without a valid key
        let result = instance.new_database(settings, "nonexistent_key");
        assert!(result.is_err());
        Ok(())
    }

    #[test]
    fn test_load_database() {
        let backend = InMemory::new();
        let instance = Instance::open(Box::new(backend)).expect("Failed to create test instance");

        // Create a database
        let mut settings = Doc::new();
        settings.set_string("name", "test_db");
        let database = instance.new_database(settings, "_device_key").unwrap();
        let root_id = database.root_id().clone();

        // Load the database
        let loaded_database = instance.load_database(&root_id).unwrap();
        assert_eq!(loaded_database.get_name().unwrap(), "test_db");
    }

    #[test]
    fn test_all_databases() {
        let backend = InMemory::new();
        let instance = Instance::open(Box::new(backend)).expect("Failed to create test instance");

        // Create multiple databases
        let mut settings1 = Doc::new();
        settings1.set_string("name", "db1");
        instance.new_database(settings1, "_device_key").unwrap();

        let mut settings2 = Doc::new();
        settings2.set_string("name", "db2");
        instance.new_database(settings2, "_device_key").unwrap();

        // Get all databases (should include system databases + user databases)
        let databases = instance.all_databases().unwrap();
        assert!(databases.len() >= 2); // At least our 2 databases + system databases
    }

    #[test]
    fn test_find_database() {
        let backend = InMemory::new();
        let instance = Instance::open(Box::new(backend)).expect("Failed to create test instance");

        // Create database with name
        let mut settings = Doc::new();
        settings.set_string("name", "my_special_db");
        instance.new_database(settings, "_device_key").unwrap();

        // Find by name
        let found = instance.find_database("my_special_db").unwrap();
        assert_eq!(found.len(), 1);
        assert_eq!(found[0].get_name().unwrap(), "my_special_db");

        // Not found
        let result = instance.find_database("nonexistent");
        assert!(result.is_err());
    }

    #[test]
    fn test_instance_load_new_backend() -> Result<(), Error> {
        // Test that Instance::load() creates new system state for empty backend
        let backend = InMemory::new();
        let instance = Instance::open(Box::new(backend))?;

        // Verify device key was created
        assert!(instance.device_id().is_ok());

        // Verify we can create and login a user
        instance.create_user("alice", None)?;
        let user = instance.login_user("alice", None)?;
        assert_eq!(user.username(), "alice");

        Ok(())
    }

    #[test]
    fn test_instance_load_existing_backend() -> Result<(), Error> {
        // Use a temporary file path for testing
        let temp_dir = std::env::temp_dir();
        let path = temp_dir.join("eidetica_test_instance_load.json");

        // Create an instance and user, then save the backend
        let backend1 = InMemory::new();
        let instance1 = Instance::open(Box::new(backend1))?;
        instance1.create_user("bob", None)?;
        let mut user1 = instance1.login_user("bob", None)?;

        // Get the default key (earliest created key)
        let default_key = user1.get_default_key()?;

        // Create a user database to verify it persists
        let mut settings = Doc::new();
        settings.set_string("name", "bob_database");
        user1.create_database(settings, &default_key)?;

        // Save the backend to file
        let backend_guard = instance1.backend();
        if let Some(in_memory) = backend_guard.as_any().downcast_ref::<InMemory>() {
            in_memory.save_to_file(&path)?;
        }

        // Drop the first instance
        drop(instance1);
        drop(user1);

        // Load a new backend from the saved file
        let backend2 = InMemory::load_from_file(&path)?;
        let instance2 = Instance::open(Box::new(backend2))?;

        // Verify the user still exists
        let users = instance2.list_users()?;
        assert_eq!(users.len(), 1);
        assert_eq!(users[0], "bob");

        // Verify we can login the existing user
        let user2 = instance2.login_user("bob", None)?;
        assert_eq!(user2.username(), "bob");

        // Clean up the temporary file
        if path.exists() {
            std::fs::remove_file(&path).ok();
        }

        Ok(())
    }

    #[test]
    fn test_instance_load_device_id_persistence() -> Result<(), Error> {
        // Test that device_id remains the same across reloads
        let temp_dir = std::env::temp_dir();
        let path = temp_dir.join("eidetica_test_device_id.json");

        // Create instance and get device_id
        let backend1 = InMemory::new();
        let instance1 = Instance::open(Box::new(backend1))?;
        let device_id1 = instance1.device_id_string()?;

        // Save backend
        let backend_guard = instance1.backend();
        if let Some(in_memory) = backend_guard.as_any().downcast_ref::<InMemory>() {
            in_memory.save_to_file(&path)?;
        }
        drop(instance1);

        // Load backend and verify device_id is the same
        let backend2 = InMemory::load_from_file(&path)?;
        let instance2 = Instance::open(Box::new(backend2))?;
        let device_id2 = instance2.device_id_string()?;

        assert_eq!(
            device_id1, device_id2,
            "Device ID should persist across reloads"
        );

        // Clean up
        if path.exists() {
            std::fs::remove_file(&path).ok();
        }

        Ok(())
    }

    #[test]
    fn test_instance_load_with_password_protected_users() -> Result<(), Error> {
        // Test that password-protected users work correctly after reload
        let temp_dir = std::env::temp_dir();
        let path = temp_dir.join("eidetica_test_password_users.json");

        // Create instance with password-protected user
        let backend1 = InMemory::new();
        let instance1 = Instance::open(Box::new(backend1))?;
        instance1.create_user("secure_alice", Some("secret123"))?;
        let user1 = instance1.login_user("secure_alice", Some("secret123"))?;
        assert_eq!(user1.username(), "secure_alice");
        drop(user1);

        // Save backend
        let backend_guard = instance1.backend();
        if let Some(in_memory) = backend_guard.as_any().downcast_ref::<InMemory>() {
            in_memory.save_to_file(&path)?;
        }
        drop(instance1);

        // Reload and verify password still works
        let backend2 = InMemory::load_from_file(&path)?;
        let instance2 = Instance::open(Box::new(backend2))?;

        // Correct password should work
        let user2 = instance2.login_user("secure_alice", Some("secret123"))?;
        assert_eq!(user2.username(), "secure_alice");

        // Wrong password should fail
        let result = instance2.login_user("secure_alice", Some("wrong_password"));
        assert!(result.is_err(), "Login with wrong password should fail");

        // No password should fail
        let result = instance2.login_user("secure_alice", None);
        assert!(
            result.is_err(),
            "Login without password should fail for password-protected user"
        );

        // Clean up
        if path.exists() {
            std::fs::remove_file(&path).ok();
        }

        Ok(())
    }

    #[test]
    fn test_instance_load_multiple_users() -> Result<(), Error> {
        // Test that multiple users persist correctly
        let temp_dir = std::env::temp_dir();
        let path = temp_dir.join("eidetica_test_multiple_users.json");

        // Create instance with multiple users (mix of passwordless and password-protected)
        let backend1 = InMemory::new();
        let instance1 = Instance::open(Box::new(backend1))?;

        instance1.create_user("alice", None)?;
        instance1.create_user("bob", Some("bobpass"))?;
        instance1.create_user("charlie", None)?;
        instance1.create_user("diana", Some("dianapass"))?;

        // Verify all users can login
        instance1.login_user("alice", None)?;
        instance1.login_user("bob", Some("bobpass"))?;
        instance1.login_user("charlie", None)?;
        instance1.login_user("diana", Some("dianapass"))?;

        // Save backend
        let backend_guard = instance1.backend();
        if let Some(in_memory) = backend_guard.as_any().downcast_ref::<InMemory>() {
            in_memory.save_to_file(&path)?;
        }
        drop(instance1);

        // Reload and verify all users still exist and can login
        let backend2 = InMemory::load_from_file(&path)?;
        let instance2 = Instance::open(Box::new(backend2))?;

        let users = instance2.list_users()?;
        assert_eq!(users.len(), 4, "All 4 users should be present after reload");
        assert!(users.contains(&"alice".to_string()));
        assert!(users.contains(&"bob".to_string()));
        assert!(users.contains(&"charlie".to_string()));
        assert!(users.contains(&"diana".to_string()));

        // Verify login still works for all users
        instance2.login_user("alice", None)?;
        instance2.login_user("bob", Some("bobpass"))?;
        instance2.login_user("charlie", None)?;
        instance2.login_user("diana", Some("dianapass"))?;

        // Clean up
        if path.exists() {
            std::fs::remove_file(&path).ok();
        }

        Ok(())
    }

    #[test]
    fn test_instance_load_user_databases_persist() -> Result<(), Error> {
        // Test that user-created databases persist across reloads
        let temp_dir = std::env::temp_dir();
        let path = temp_dir.join("eidetica_test_user_dbs.json");

        // Create instance, user, and multiple databases
        let backend1 = InMemory::new();
        let instance1 = Instance::open(Box::new(backend1))?;
        instance1.create_user("eve", None)?;
        let mut user1 = instance1.login_user("eve", None)?;

        // Get the default key (earliest created key)
        let default_key = user1.get_default_key()?;

        // Create multiple databases
        let mut settings1 = Doc::new();
        settings1.set_string("name", "database_one");
        settings1.set_string("purpose", "testing");
        let db1 = user1.create_database(settings1, &default_key)?;
        let db1_root = db1.root_id().clone();

        let mut settings2 = Doc::new();
        settings2.set_string("name", "database_two");
        settings2.set_string("purpose", "production");
        let db2 = user1.create_database(settings2, &default_key)?;
        let db2_root = db2.root_id().clone();

        drop(db1);
        drop(db2);
        drop(user1);

        // Save backend
        let backend_guard = instance1.backend();
        if let Some(in_memory) = backend_guard.as_any().downcast_ref::<InMemory>() {
            in_memory.save_to_file(&path)?;
        }
        drop(instance1);

        // Reload and verify databases still exist
        let backend2 = InMemory::load_from_file(&path)?;
        let instance2 = Instance::open(Box::new(backend2))?;
        let _user2 = instance2.login_user("eve", None)?;

        // Load databases by root_id and verify their settings
        let loaded_db1 = instance2.load_database(&db1_root)?;
        assert_eq!(loaded_db1.get_name()?, "database_one");
        let settings1_doc = loaded_db1.get_settings()?;
        assert_eq!(settings1_doc.get_string("purpose")?, "testing");

        let loaded_db2 = instance2.load_database(&db2_root)?;
        assert_eq!(loaded_db2.get_name()?, "database_two");
        let settings2_doc = loaded_db2.get_settings()?;
        assert_eq!(settings2_doc.get_string("purpose")?, "production");

        // Clean up
        if path.exists() {
            std::fs::remove_file(&path).ok();
        }

        Ok(())
    }

    #[test]
    fn test_instance_load_idempotency() -> Result<(), Error> {
        // Test that loading the same backend multiple times gives consistent results
        let temp_dir = std::env::temp_dir();
        let path = temp_dir.join("eidetica_test_idempotency.json");

        // Create and save initial state
        let backend1 = InMemory::new();
        let instance1 = Instance::open(Box::new(backend1))?;
        instance1.create_user("frank", None)?;
        let device_id1 = instance1.device_id_string()?;

        let backend_guard = instance1.backend();
        if let Some(in_memory) = backend_guard.as_any().downcast_ref::<InMemory>() {
            in_memory.save_to_file(&path)?;
        }
        drop(instance1);

        // Load the same backend multiple times and verify consistency
        for i in 0..3 {
            let backend = InMemory::load_from_file(&path)?;
            let instance = Instance::open(Box::new(backend))?;

            // Device ID should be the same every time
            let device_id = instance.device_id_string()?;
            assert_eq!(
                device_id, device_id1,
                "Device ID should be consistent on reload {i}"
            );

            // User list should be the same
            let users = instance.list_users()?;
            assert_eq!(users.len(), 1);
            assert_eq!(users[0], "frank");

            // Should be able to login
            let user = instance.login_user("frank", None)?;
            assert_eq!(user.username(), "frank");

            drop(user);
            drop(instance);
        }

        // Clean up
        if path.exists() {
            std::fs::remove_file(&path).ok();
        }

        Ok(())
    }

    #[test]
    fn test_instance_load_new_vs_existing() -> Result<(), Error> {
        // Test the difference between loading new and existing backends
        let temp_dir = std::env::temp_dir();
        let path = temp_dir.join("eidetica_test_new_vs_existing.json");

        // Create first instance (new backend)
        let backend1 = InMemory::new();
        let instance1 = Instance::open(Box::new(backend1))?;
        let device_id1 = instance1.device_id_string()?;
        instance1.create_user("grace", None)?;

        let backend_guard = instance1.backend();
        if let Some(in_memory) = backend_guard.as_any().downcast_ref::<InMemory>() {
            in_memory.save_to_file(&path)?;
        }
        drop(instance1);

        // Load existing backend
        let backend2 = InMemory::load_from_file(&path)?;
        let instance2 = Instance::open(Box::new(backend2))?;
        let device_id2 = instance2.device_id_string()?;

        // Device ID should match (existing backend)
        assert_eq!(device_id1, device_id2);

        // User should exist (existing backend)
        let users = instance2.list_users()?;
        assert_eq!(users.len(), 1);
        assert_eq!(users[0], "grace");
        drop(instance2);

        // Create completely new instance (different backend)
        let backend3 = InMemory::new();
        let instance3 = Instance::open(Box::new(backend3))?;
        let device_id3 = instance3.device_id_string()?;

        // Device ID should be different (new backend)
        assert_ne!(device_id1, device_id3);

        // No users should exist (new backend)
        let users = instance3.list_users()?;
        assert_eq!(users.len(), 0);

        // Clean up
        if path.exists() {
            std::fs::remove_file(&path).ok();
        }

        Ok(())
    }

    #[test]
    fn test_instance_create_strict_fails_on_existing() -> Result<(), Error> {
        // Test that Instance::create() fails on already-initialized backend
        let temp_dir = std::env::temp_dir();
        let path = temp_dir.join("eidetica_test_create_strict.json");

        // Create first instance
        let backend1 = InMemory::new();
        let instance1 = Instance::create(Box::new(backend1))?;
        instance1.create_user("alice", None)?;

        // Save backend
        let backend_guard = instance1.backend();
        if let Some(in_memory) = backend_guard.as_any().downcast_ref::<InMemory>() {
            in_memory.save_to_file(&path)?;
        }
        drop(instance1);

        // Try to create() on the existing backend - should fail
        let backend2 = InMemory::load_from_file(&path)?;
        let result = Instance::create(Box::new(backend2));
        assert!(result.is_err(), "create() should fail on existing backend");

        // Verify error type
        if let Err(err) = result {
            if let crate::Error::Instance(instance_err) = err {
                assert!(
                    instance_err.is_already_exists(),
                    "Error should be InstanceAlreadyExists"
                );
            } else {
                panic!("Expected Instance error");
            }
        }

        // Verify open() still works
        let backend3 = InMemory::load_from_file(&path)?;
        let instance3 = Instance::open(Box::new(backend3))?;
        let users = instance3.list_users()?;
        assert_eq!(users.len(), 1);
        assert_eq!(users[0], "alice");

        // Clean up
        if path.exists() {
            std::fs::remove_file(&path).ok();
        }

        Ok(())
    }

    #[test]
    fn test_instance_create_on_fresh_backend() -> Result<(), Error> {
        // Test that Instance::create() succeeds on fresh backend
        let backend = InMemory::new();
        let instance = Instance::create(Box::new(backend))?;

        // Verify instance is properly initialized
        assert!(instance.device_id().is_ok());

        // Verify we can create users
        instance.create_user("bob", None)?;
        let user = instance.login_user("bob", None)?;
        assert_eq!(user.username(), "bob");

        Ok(())
    }
}