mod common;

use std::sync::{
    atomic::{AtomicBool, Ordering},
    Arc,
};

// By importing every single public item in the crate, we can ensure
// that no mucking around or cleaning up is accidentally causing breakages
// to import paths
//
// WARNING: if deprecating something new, move it to the second `use` in this function, which has
// `#[expect(deprecated)]`. Do not put `#[allow(deprecated)]` on this test
#[test]
#[allow(unused_imports)]
fn ensure_v4_no_broken_imports() {
    // This block shows where the proper public home for each item is
    #[rustfmt::skip]
    use dittolive_ditto::{
        disk_usage::{
            self,
            DiskUsage,
            DiskUsageCallback,
            DiskUsageItem,
            DiskUsageObserver,
            DiskUsageObserverHandle,
            FileSystemType,
        },
        dql::{
            self,
            QueryResult,
            QueryResultItem,
        },
        error::{
            self,
            CoreApiErrorKind,
            DittoError,
            ErrorKind,
            Result,
        },
        fs::{
            self,
            DittoRoot,
            PersistentRoot,
            TempRoot,
        },
        identity::{
            self,
            AuthenticationClientFeedback,
            DittoAuthenticator,
            DittoAuthenticationEventHandler,
            OfflinePlayground,
            OnlinePlayground,
            OnlineWithAuthentication,
            SharedKey,
        },
        logger::{
            self,
            DittoLogger,
        },
        prelude::{
            self,
            identity as _,
            DatabaseId as _,
            BoxedDitto,
            BoxedDocument,
            CLogLevel,
            CborValue,
            CborValueGetters,
            ConnectionRequest as _,
            ConnectionRequestAuthorization as _,
            DiskUsage as _,
            DiskUsageItem as _,
            DiskUsageObserverHandle as _,
            FileSystemType as _,
            Ditto as _,
            DittoAttachment as _,
            DittoAttachmentFetchEvent as _,
            DittoAttachmentFetcher as _,
            DittoAttachmentToken as _,
            DittoAuthenticationEventHandler as _,
            DittoAuthenticator as _,
            DittoError as _,
            DittoLogger as _,
            DittoRoot as _,
            DittoSmallPeerInfoSyncScope as _,
            HttpListenConfig as _,
            Identity,
            LogLevel as _,
            OfflinePlayground as _,
            OnlinePlayground as _,
            OnlineWithAuthentication as _,
            PersistentRoot as _,
            Presence as _,
            PresenceObserver as _,
            SharedKey as _,
            Store as _,
            StringPrimitiveFormat,
            TcpListenConfig as _,
            TempRoot as _,
            TransportConfig as _,
        },
        presence::{
            self,
            Connection,
            ConnectionRequest,
            ConnectionRequestAuthorization,
            ConnectionType,
            JsonObject,
            Peer,
            Presence,
            PresenceGraph,
            PresenceObserver,
            PresenceOs,
        },
        small_peer_info::{
            self,
            DittoSmallPeerInfoSyncScope,
            SmallPeerInfo,
        },
        store::{
            self,
            attachment::{
                self,
                DittoAttachment,
                DittoAttachmentFetchEvent,
                DittoAttachmentFetcher,
                DittoAttachmentToken,
                DittoAttachmentTokenLike,
                FetcherVersion,
            },

            ChangeHandler,
            ChangeHandlerWithSignalNext,
            SignalNext,
            SortDirection,
            Store,
            StoreObserver,
        },
        sync::{
            self,
            Sync,
            SyncSubscription,
        },
        transport::{
            self,
            BluetoothLEConfig,
            Connect,
            Global,
            HttpListenConfig,
            LanConfig,
            Listen,
            PeerToPeer,
            TcpListenConfig,
            TransportConfig,
        },
        DatabaseId,
        Ditto,
        LogLevel,
    };
    // TODO(NEXT BREAKING UPDGRADE): Remove deprecated items
    // This block is made of items that are deprecated or exported from a
    // location that is deprecated. This test ensures that those items are still
    // reachable (so as to not break callers), but at the next breaking upgrade
    // we should remove these items
    #[rustfmt::skip]
    #[allow(deprecated)]
    use dittolive_ditto::{
        // TODO: Add re-exports for any items that have been deprecated
        // This gives us a tidy list for removal and also checks that soft-deprecated
        // items are still available from outside the crate.
    };
}

#[test]
fn ensure_activated_before_starting_sync() {
    let ditto = common::get_inactive_builder_ditto(None).unwrap();
    ensure_activated_before_starting_sync_impl(ditto);

    let ditto = common::get_inactive_revamp_ditto(None).unwrap();
    ensure_activated_before_starting_sync_impl(ditto);
}

fn ensure_activated_before_starting_sync_impl(ditto: dittolive_ditto::prelude::Ditto) {
    let res = ditto.sync().start();
    assert!(res.is_err());
    let err = res.err().unwrap();
    assert_eq!(err.to_string(), "Sync could not be started because Ditto has not yet been activated. This can be achieved with a successful call to `set_license_token`. If you need to obtain a license token then please visit https://portal.ditto.live.");
}

#[test]
fn ensure_online_is_activated_before_starting_sync() {
    let ditto = common::get_online_ditto().unwrap();
    assert!(ditto.is_activated());
    // We go through this extra `Result`-wrapping closure layer to get
    // the corresponding API doc Rust snippet to feature a more idiomatic `?`
    let res = (|| {
        //@ditto/snippet-start sync-basic
        ditto.sync().start()?;
        //@ditto/snippet-end
        Ok::<(), ::dittolive_ditto::error::DittoError>(())
    })();
    assert!(res.is_ok());
}

/// A basic smoke test which tests that the FFI bindings are wired up
/// correctly and nothing horrible happens.
#[test]
fn test_garbage_collection() {
    let ditto = common::get_builder_ditto(None).unwrap();
    test_garbage_collection_impl(ditto);

    let ditto = common::get_revamp_ditto(None).unwrap();
    test_garbage_collection_impl(ditto);

    let ditto = common::get_revamp_ditto_async_sync(None).unwrap();
    test_garbage_collection_impl(ditto);

    let ditto = common::get_revamp_ditto_async_sync(None).unwrap();
    test_garbage_collection_impl(ditto);
}

fn test_garbage_collection_impl(ditto: dittolive_ditto::prelude::Ditto) {
    ditto.run_garbage_collection();
}

/// Test that setting a device name is working correctly
#[test]
fn test_set_device_name() {
    let ditto = common::get_builder_ditto(None).unwrap();
    test_set_device_name_impl(ditto);

    let ditto = common::get_revamp_ditto(None).unwrap();
    test_set_device_name_impl(ditto);

    let ditto = common::get_revamp_ditto_async_sync(None).unwrap();
    test_set_device_name_impl(ditto);

    let ditto = common::get_revamp_ditto_async_sync(None).unwrap();
    test_set_device_name_impl(ditto);
}

fn test_set_device_name_impl(ditto: dittolive_ditto::prelude::Ditto) {
    ditto.set_device_name("my_custom_device_name");

    let finished = Arc::new(AtomicBool::new(false));
    let finished_1 = Arc::clone(&finished);

    let _presence_observer = ditto.presence().register_observer(move |presence| {
        let local_peer = &presence.local_peer;

        assert_eq!(&local_peer.device_name, "my_custom_device_name");
        finished_1.store(true, Ordering::SeqCst);
    });

    while !finished.load(Ordering::SeqCst) {
        std::thread::yield_now();
    }
}

/// Test that monitoring disk usage works
#[test]
fn test_observe_disk_usage() {
    let ditto = common::get_builder_ditto(None).unwrap();
    test_observe_disk_usage_impl(ditto);

    let ditto = common::get_revamp_ditto(None).unwrap();
    test_observe_disk_usage_impl(ditto);

    let ditto = common::get_revamp_ditto_async_sync(None).unwrap();
    test_observe_disk_usage_impl(ditto);

    let ditto = common::get_revamp_ditto_async_sync(None).unwrap();
    test_observe_disk_usage_impl(ditto);
}

fn test_observe_disk_usage_impl(ditto: dittolive_ditto::prelude::Ditto) {
    let finished = Arc::new(AtomicBool::new(false));
    let finished_1 = Arc::clone(&finished);

    let _observer = ditto.disk_usage().observe(move |_disk_usage_tree| {
        finished_1.store(true, Ordering::SeqCst);
    });
    let mut data_path = ditto.absolute_persistence_directory();
    data_path.push("random_file_name");
    std::fs::File::create(data_path).unwrap();

    while !finished.load(Ordering::SeqCst) {
        std::thread::yield_now();
    }
}

#[test]
fn test_logout() {
    let ditto = common::get_online_ditto().unwrap();
    ditto.auth().unwrap().logout(|_| {}).unwrap();
}
