use aldrin_core::{ObjectUuid, ServiceUuid};
use aldrin_test::aldrin::low_level::{Service, ServiceInfo};
use aldrin_test::aldrin::{Discoverer, DiscovererEvent, DiscovererEventKind, Object};
use aldrin_test::tokio::TestBroker;
use std::fmt::Debug;
use std::hash::Hash;

#[track_caller]
fn test_created<Key>(
    discoverer: &Discoverer<Key>,
    event: DiscovererEvent<Key>,
    key: Key,
    object: &Object,
    service1: Option<&Service>,
    service2: Option<&Service>,
) where
    Key: Copy + Eq + Hash + Debug,
{
    assert_eq!(
        discoverer.object_id(key, object.id().uuid),
        Some(object.id())
    );
    assert!(discoverer.contains(key, object.id().uuid));
    assert!(discoverer.contains_any(key));

    assert_eq!(event.kind(), DiscovererEventKind::Created);
    assert_eq!(event.key(), key);
    assert_eq!(event.object_id(), object.id());

    let entry = discoverer.entry(key);
    assert_eq!(entry.key(), key);
    assert_eq!(entry.object_id(object.id().uuid), Some(object.id()));
    assert!(entry.contains(object.id().uuid));
    assert!(entry.contains_any());

    let mut iter = discoverer.iter();
    let iter_entry1 = iter.next().unwrap();
    assert_eq!(iter_entry1.key(), key);
    assert_eq!(iter_entry1.object_id(), object.id());

    let mut entry_iter = entry.iter();
    let iter_entry2 = entry_iter.next().unwrap();
    assert_eq!(iter_entry2.key(), key);
    assert_eq!(iter_entry2.object_id(), object.id());

    if let Some(service1) = service1 {
        assert_eq!(
            discoverer.service_id(key, object.id().uuid, service1.id().uuid),
            Some(service1.id())
        );

        assert_eq!(
            discoverer.service_ids(key, object.id().uuid, [service1.id().uuid]),
            Some(vec![service1.id()])
        );

        assert_eq!(
            discoverer.service_ids_n(key, object.id().uuid, &[service1.id().uuid]),
            Some([service1.id()])
        );

        assert_eq!(
            event.service_id(discoverer, service1.id().uuid),
            service1.id()
        );

        assert_eq!(
            event.service_ids(discoverer, [service1.id().uuid]),
            vec![service1.id()]
        );

        assert_eq!(
            event.service_ids_n(discoverer, &[service1.id().uuid]),
            [service1.id()]
        );

        assert_eq!(
            entry.service_id(object.id().uuid, service1.id().uuid),
            Some(service1.id())
        );

        assert_eq!(
            entry.service_ids(object.id().uuid, [service1.id().uuid]),
            Some(vec![service1.id()])
        );

        assert_eq!(
            entry.service_ids_n(object.id().uuid, &[service1.id().uuid]),
            Some([service1.id()])
        );

        assert_eq!(iter_entry1.service_id(service1.id().uuid), service1.id());

        assert_eq!(
            iter_entry1.service_ids([service1.id().uuid]),
            vec![service1.id()]
        );

        assert_eq!(
            iter_entry1.service_ids_n(&[service1.id().uuid]),
            [service1.id()]
        );

        assert_eq!(iter_entry2.service_id(service1.id().uuid), service1.id());

        assert_eq!(
            iter_entry2.service_ids([service1.id().uuid]),
            vec![service1.id()]
        );

        assert_eq!(
            iter_entry2.service_ids_n(&[service1.id().uuid]),
            [service1.id()]
        );
    }

    if let Some(service2) = service2 {
        assert_eq!(
            discoverer.service_id(key, object.id().uuid, service2.id().uuid),
            Some(service2.id())
        );

        assert_eq!(
            discoverer.service_ids(key, object.id().uuid, [service2.id().uuid]),
            Some(vec![service2.id()])
        );

        assert_eq!(
            discoverer.service_ids_n(key, object.id().uuid, &[service2.id().uuid]),
            Some([service2.id()])
        );

        assert_eq!(
            event.service_id(discoverer, service2.id().uuid),
            service2.id()
        );

        assert_eq!(
            event.service_ids(discoverer, [service2.id().uuid]),
            vec![service2.id()]
        );

        assert_eq!(
            event.service_ids_n(discoverer, &[service2.id().uuid]),
            [service2.id()]
        );

        assert_eq!(
            entry.service_id(object.id().uuid, service2.id().uuid),
            Some(service2.id())
        );

        assert_eq!(
            entry.service_ids(object.id().uuid, [service2.id().uuid]),
            Some(vec![service2.id()])
        );

        assert_eq!(
            entry.service_ids_n(object.id().uuid, &[service2.id().uuid]),
            Some([service2.id()])
        );

        assert_eq!(iter_entry1.service_id(service2.id().uuid), service2.id());

        assert_eq!(
            iter_entry1.service_ids([service2.id().uuid]),
            vec![service2.id()]
        );

        assert_eq!(
            iter_entry1.service_ids_n(&[service2.id().uuid]),
            [service2.id()]
        );

        assert_eq!(iter_entry2.service_id(service2.id().uuid), service2.id());

        assert_eq!(
            iter_entry2.service_ids([service2.id().uuid]),
            vec![service2.id()]
        );

        assert_eq!(
            iter_entry2.service_ids_n(&[service2.id().uuid]),
            [service2.id()]
        );
    }

    if let (Some(service1), Some(service2)) = (service1, service2) {
        assert_eq!(
            discoverer.service_ids(
                key,
                object.id().uuid,
                [service1.id().uuid, service2.id().uuid]
            ),
            Some(vec![service1.id(), service2.id()])
        );

        assert_eq!(
            discoverer.service_ids_n(
                key,
                object.id().uuid,
                &[service1.id().uuid, service2.id().uuid]
            ),
            Some([service1.id(), service2.id()])
        );

        assert_eq!(
            event.service_ids(discoverer, [service1.id().uuid, service2.id().uuid]),
            vec![service1.id(), service2.id()]
        );

        assert_eq!(
            event.service_ids_n(discoverer, &[service1.id().uuid, service2.id().uuid]),
            [service1.id(), service2.id()]
        );

        assert_eq!(
            entry.service_ids(object.id().uuid, [service1.id().uuid, service2.id().uuid]),
            Some(vec![service1.id(), service2.id()])
        );

        assert_eq!(
            entry.service_ids_n(object.id().uuid, &[service1.id().uuid, service2.id().uuid]),
            Some([service1.id(), service2.id()])
        );

        assert_eq!(
            iter_entry1.service_ids([service1.id().uuid, service2.id().uuid]),
            vec![service1.id(), service2.id()]
        );

        assert_eq!(
            iter_entry1.service_ids_n(&[service1.id().uuid, service2.id().uuid]),
            [service1.id(), service2.id()]
        );

        assert_eq!(
            iter_entry2.service_ids([service1.id().uuid, service2.id().uuid]),
            vec![service1.id(), service2.id()]
        );

        assert_eq!(
            iter_entry2.service_ids_n(&[service1.id().uuid, service2.id().uuid]),
            [service1.id(), service2.id()]
        );
    }

    assert!(iter.next().is_none());
    assert!(entry_iter.next().is_none());
}

fn test_destroyed<Key>(
    discoverer: &Discoverer<Key>,
    event: DiscovererEvent<Key>,
    key: Key,
    object: &Object,
    service1: Option<&Service>,
    service2: Option<&Service>,
) where
    Key: Copy + Eq + Hash + Debug,
{
    assert_eq!(event.kind(), DiscovererEventKind::Destroyed);
    assert_eq!(event.key(), key);
    assert_eq!(event.object_id(), object.id());

    let entry = discoverer.entry(key);
    assert_eq!(entry.key(), key);
    assert_eq!(entry.object_id(object.id().uuid), None);

    assert!(discoverer.iter().next().is_none());
    assert!(entry.iter().next().is_none());

    if let Some(service1) = service1 {
        assert_eq!(entry.service_id(object.id().uuid, service1.id().uuid), None);
    }

    if let Some(service2) = service2 {
        assert_eq!(entry.service_id(object.id().uuid, service2.id().uuid), None);
    }
}

#[tokio::test]
async fn any_object_no_services() {
    let mut broker = TestBroker::new();
    let mut client = broker.add_client().await;

    let mut discoverer = client
        .create_discoverer()
        .add(0, None, None)
        .build()
        .await
        .unwrap();

    let obj = client.create_object(ObjectUuid::new_v4()).await.unwrap();

    assert!(!discoverer.is_finished());
    assert!(!discoverer.contains(0, obj.id().uuid));
    assert!(!discoverer.contains_any(0));
    let ev = discoverer.next_event().await.unwrap();
    test_created(&discoverer, ev, 0, &obj, None, None);

    assert!(!discoverer.is_finished());
    obj.destroy().await.unwrap();
    let ev = discoverer.next_event().await.unwrap();
    test_destroyed(&discoverer, ev, 0, &obj, None, None);

    assert!(!discoverer.is_finished());
    client.join().await;
    broker.join().await;
}

#[tokio::test]
async fn specific_object_no_services() {
    let mut broker = TestBroker::new();
    let mut client = broker.add_client().await;

    let _obj = client.create_object(ObjectUuid::new_v4()).await.unwrap();
    let obj = client.create_object(ObjectUuid::new_v4()).await.unwrap();

    let mut discoverer = client
        .create_discoverer()
        .bare_object(0, obj.id().uuid)
        .build()
        .await
        .unwrap();

    assert!(!discoverer.is_finished());
    let ev = discoverer.next_event().await.unwrap();
    test_created(&discoverer, ev, 0, &obj, None, None);

    assert!(!discoverer.is_finished());
    obj.destroy().await.unwrap();
    let ev = discoverer.next_event().await.unwrap();
    test_destroyed(&discoverer, ev, 0, &obj, None, None);

    assert!(!discoverer.is_finished());
    client.join().await;
    broker.join().await;
}

#[tokio::test]
async fn specific_object() {
    let mut broker = TestBroker::new();
    let mut client = broker.add_client().await;

    let obj = client.create_object(ObjectUuid::new_v4()).await.unwrap();
    let info = ServiceInfo::new(0);
    let svc1 = obj
        .create_service(ServiceUuid::new_v4(), info)
        .await
        .unwrap();
    let svc2 = obj
        .create_service(ServiceUuid::new_v4(), info)
        .await
        .unwrap();

    let mut discoverer = client
        .create_discoverer()
        .object_with_services(0, obj.id().uuid, [svc1.id().uuid, svc2.id().uuid])
        .build()
        .await
        .unwrap();

    assert!(!discoverer.is_finished());
    let ev = discoverer.next_event().await.unwrap();
    test_created(&discoverer, ev, 0, &obj, Some(&svc1), Some(&svc2));

    assert!(!discoverer.is_finished());
    obj.destroy().await.unwrap();
    let ev = discoverer.next_event().await.unwrap();
    test_destroyed(&discoverer, ev, 0, &obj, Some(&svc1), Some(&svc2));

    assert!(!discoverer.is_finished());
    client.join().await;
    broker.join().await;
}

#[tokio::test]
async fn any_object() {
    let mut broker = TestBroker::new();
    let mut client = broker.add_client().await;

    let obj = client.create_object(ObjectUuid::new_v4()).await.unwrap();
    let info = ServiceInfo::new(0);
    let svc1 = obj
        .create_service(ServiceUuid::new_v4(), info)
        .await
        .unwrap();
    let svc2 = obj
        .create_service(ServiceUuid::new_v4(), info)
        .await
        .unwrap();

    let mut discoverer = client
        .create_discoverer()
        .any_object_with_services(0, [svc1.id().uuid, svc2.id().uuid])
        .build()
        .await
        .unwrap();

    assert!(!discoverer.is_finished());
    let ev = discoverer.next_event().await.unwrap();
    test_created(&discoverer, ev, 0, &obj, Some(&svc1), Some(&svc2));

    assert!(!discoverer.is_finished());
    obj.destroy().await.unwrap();
    let ev = discoverer.next_event().await.unwrap();
    test_destroyed(&discoverer, ev, 0, &obj, Some(&svc1), Some(&svc2));

    let obj = client.create_object(ObjectUuid::new_v4()).await.unwrap();
    let info = ServiceInfo::new(0);
    let svc1 = obj.create_service(svc1.id().uuid, info).await.unwrap();
    let svc2 = obj.create_service(svc2.id().uuid, info).await.unwrap();

    assert!(!discoverer.is_finished());
    let ev = discoverer.next_event().await.unwrap();
    test_created(&discoverer, ev, 0, &obj, Some(&svc1), Some(&svc2));

    assert!(!discoverer.is_finished());
    obj.destroy().await.unwrap();
    let ev = discoverer.next_event().await.unwrap();
    test_destroyed(&discoverer, ev, 0, &obj, Some(&svc1), Some(&svc2));

    assert!(!discoverer.is_finished());
    client.join().await;
    broker.join().await;
}

#[tokio::test]
async fn empty() {
    let mut broker = TestBroker::new();
    let mut client = broker.add_client().await;

    let mut discoverer = client.create_discoverer::<()>().build().await.unwrap();
    assert!(discoverer.next_event().await.is_none());
    assert!(discoverer.is_finished());
    assert!(discoverer.iter().next().is_none());

    client.join().await;
    broker.join().await;
}

#[tokio::test]
async fn current_only() {
    let mut broker = TestBroker::new();
    let mut client = broker.add_client().await;

    let obj = client.create_object(ObjectUuid::new_v4()).await.unwrap();
    let info = ServiceInfo::new(0);
    let svc1 = obj
        .create_service(ServiceUuid::new_v4(), info)
        .await
        .unwrap();
    let svc2 = obj
        .create_service(ServiceUuid::new_v4(), info)
        .await
        .unwrap();

    let mut discoverer = client
        .create_discoverer()
        .any_object_with_services(0, [svc1.id().uuid, svc2.id().uuid])
        .build_current_only()
        .await
        .unwrap();

    let _obj = client.create_object(ObjectUuid::new_v4()).await.unwrap();
    let info = ServiceInfo::new(0);
    let _svc1 = obj
        .create_service(ServiceUuid::new_v4(), info)
        .await
        .unwrap();
    let _svc2 = obj
        .create_service(ServiceUuid::new_v4(), info)
        .await
        .unwrap();

    assert!(!discoverer.is_finished());
    let ev = discoverer.next_event().await.unwrap();
    test_created(&discoverer, ev, 0, &obj, Some(&svc1), Some(&svc2));

    assert!(discoverer.next_event().await.is_none());
    assert!(discoverer.is_finished());

    client.join().await;
    broker.join().await;
}

#[tokio::test]
async fn restart_any() {
    let mut broker = TestBroker::new();
    let mut client = broker.add_client().await;

    let obj = client.create_object(ObjectUuid::new_v4()).await.unwrap();
    let info = ServiceInfo::new(0);
    let svc = obj
        .create_service(ServiceUuid::new_v4(), info)
        .await
        .unwrap();

    let mut discoverer = client
        .create_discoverer()
        .any_object_with_services(0, [svc.id().uuid])
        .build()
        .await
        .unwrap();

    assert!(!discoverer.is_finished());
    let ev = discoverer.next_event().await.unwrap();
    test_created(&discoverer, ev, 0, &obj, Some(&svc), None);

    assert!(!discoverer.is_finished());
    discoverer.restart().await.unwrap();

    assert!(!discoverer.is_finished());
    let ev = discoverer.next_event().await.unwrap();
    test_created(&discoverer, ev, 0, &obj, Some(&svc), None);

    assert!(!discoverer.is_finished());
    client.join().await;
    broker.join().await;
}

#[tokio::test]
async fn restart_specific() {
    let mut broker = TestBroker::new();
    let mut client = broker.add_client().await;

    let obj = client.create_object(ObjectUuid::new_v4()).await.unwrap();
    let info = ServiceInfo::new(0);
    let svc = obj
        .create_service(ServiceUuid::new_v4(), info)
        .await
        .unwrap();

    let mut discoverer = client
        .create_discoverer()
        .object_with_services(0, obj.id().uuid, [svc.id().uuid])
        .build()
        .await
        .unwrap();

    assert!(!discoverer.is_finished());
    let ev = discoverer.next_event().await.unwrap();
    test_created(&discoverer, ev, 0, &obj, Some(&svc), None);

    assert!(!discoverer.is_finished());
    discoverer.restart().await.unwrap();

    assert!(!discoverer.is_finished());
    let ev = discoverer.next_event().await.unwrap();
    test_created(&discoverer, ev, 0, &obj, Some(&svc), None);

    assert!(!discoverer.is_finished());
    client.join().await;
    broker.join().await;
}

#[tokio::test]
async fn restart_current_only_any() {
    let mut broker = TestBroker::new();
    let mut client = broker.add_client().await;

    let uuid = ServiceUuid::new_v4();

    let mut discoverer = client
        .create_discoverer()
        .any_object_with_services(0, [uuid])
        .build_current_only()
        .await
        .unwrap();

    let obj = client.create_object(ObjectUuid::new_v4()).await.unwrap();
    let info = ServiceInfo::new(0);
    let svc = obj.create_service(uuid, info).await.unwrap();

    assert!(!discoverer.is_finished());
    assert!(discoverer.next_event().await.is_none());
    assert!(discoverer.is_finished());

    discoverer.restart_current_only().await.unwrap();

    assert!(!discoverer.is_finished());
    let ev = discoverer.next_event().await.unwrap();
    test_created(&discoverer, ev, 0, &obj, Some(&svc), None);

    assert!(discoverer.next_event().await.is_none());
    assert!(discoverer.is_finished());

    client.join().await;
    broker.join().await;
}

#[tokio::test]
async fn restart_current_only_specific() {
    let mut broker = TestBroker::new();
    let mut client = broker.add_client().await;

    let obj_uuid = ObjectUuid::new_v4();
    let svc_uuid = ServiceUuid::new_v4();

    let mut discoverer = client
        .create_discoverer()
        .object_with_services(0, obj_uuid, [svc_uuid])
        .build_current_only()
        .await
        .unwrap();

    let obj = client.create_object(obj_uuid).await.unwrap();
    let info = ServiceInfo::new(0);
    let svc = obj.create_service(svc_uuid, info).await.unwrap();

    assert!(!discoverer.is_finished());
    assert!(discoverer.next_event().await.is_none());
    assert!(discoverer.is_finished());

    discoverer.restart_current_only().await.unwrap();

    assert!(!discoverer.is_finished());
    let ev = discoverer.next_event().await.unwrap();
    test_created(&discoverer, ev, 0, &obj, Some(&svc), None);

    assert!(discoverer.next_event().await.is_none());
    assert!(discoverer.is_finished());

    client.join().await;
    broker.join().await;
}

#[tokio::test]
async fn finish_on_shutdown() {
    let mut broker = TestBroker::new();
    let mut client = broker.add_client().await;

    let obj_uuid = ObjectUuid::new_v4();

    let mut discoverer = client
        .create_discoverer()
        .bare_object((), obj_uuid)
        .build()
        .await
        .unwrap();

    let obj = client.create_object(obj_uuid).await.unwrap();
    let ev = discoverer.next_event().await.unwrap();
    test_created(&discoverer, ev, (), &obj, None, None);

    client.join().await;

    assert!(discoverer.next_event().await.is_none());
    assert!(discoverer.is_finished());

    broker.join().await;
}

#[tokio::test]
#[should_panic]
async fn query_invalid_object_uuid1() {
    let mut broker = TestBroker::new();
    let client = broker.add_client().await;

    let obj_uuid = ObjectUuid::new_v4();
    let svc_uuid = ServiceUuid::new_v4();

    let mut discoverer = client
        .create_discoverer()
        .object_with_services((), obj_uuid, [svc_uuid])
        .build()
        .await
        .unwrap();

    let obj = client.create_object(obj_uuid).await.unwrap();
    let info = ServiceInfo::new(0);
    let svc = obj.create_service(svc_uuid, info).await.unwrap();

    let ev = discoverer.next_event().await.unwrap();
    test_created(&discoverer, ev, (), &obj, Some(&svc), None);

    // This should panic because it queries an invalid object UUID.
    discoverer.object_id((), ObjectUuid::NIL);
    assert_eq!(discoverer.service_id((), ObjectUuid::NIL, svc_uuid), None);
}

#[tokio::test]
#[should_panic]
async fn query_invalid_object_uuid2() {
    let mut broker = TestBroker::new();
    let client = broker.add_client().await;

    let obj_uuid = ObjectUuid::new_v4();
    let svc_uuid = ServiceUuid::new_v4();

    let mut discoverer = client
        .create_discoverer()
        .object_with_services((), obj_uuid, [svc_uuid])
        .build()
        .await
        .unwrap();

    let obj = client.create_object(obj_uuid).await.unwrap();
    let info = ServiceInfo::new(0);
    let svc = obj.create_service(svc_uuid, info).await.unwrap();

    let ev = discoverer.next_event().await.unwrap();
    test_created(&discoverer, ev, (), &obj, Some(&svc), None);

    // This should panic because it queries an invalid object UUID.
    discoverer.service_id((), ObjectUuid::NIL, svc_uuid);
}

#[tokio::test]
#[should_panic]
async fn query_entry_invalid_object_uuid1() {
    let mut broker = TestBroker::new();
    let client = broker.add_client().await;

    let obj_uuid = ObjectUuid::new_v4();
    let svc_uuid = ServiceUuid::new_v4();

    let mut discoverer = client
        .create_discoverer()
        .object_with_services((), obj_uuid, [svc_uuid])
        .build()
        .await
        .unwrap();

    let obj = client.create_object(obj_uuid).await.unwrap();
    let info = ServiceInfo::new(0);
    let svc = obj.create_service(svc_uuid, info).await.unwrap();

    let ev = discoverer.next_event().await.unwrap();
    test_created(&discoverer, ev, (), &obj, Some(&svc), None);

    let entry = discoverer.entry(());

    // This should panic because it queries an invalid object UUID.
    entry.object_id(ObjectUuid::NIL);
}

#[tokio::test]
#[should_panic]
async fn query_entry_invalid_object_uuid2() {
    let mut broker = TestBroker::new();
    let client = broker.add_client().await;

    let obj_uuid = ObjectUuid::new_v4();
    let svc_uuid = ServiceUuid::new_v4();

    let mut discoverer = client
        .create_discoverer()
        .object_with_services((), obj_uuid, [svc_uuid])
        .build()
        .await
        .unwrap();

    let obj = client.create_object(obj_uuid).await.unwrap();
    let info = ServiceInfo::new(0);
    let svc = obj.create_service(svc_uuid, info).await.unwrap();

    let ev = discoverer.next_event().await.unwrap();
    test_created(&discoverer, ev, (), &obj, Some(&svc), None);

    let entry = discoverer.entry(());

    // This should panic because it queries an invalid object UUID.
    entry.service_id(ObjectUuid::NIL, svc_uuid);
}

#[tokio::test]
#[should_panic]
async fn query_invalid_entry() {
    let mut broker = TestBroker::new();
    let client = broker.add_client().await;

    let discoverer = client
        .create_discoverer()
        .bare_object(1, ObjectUuid::new_v4())
        .build()
        .await
        .unwrap();

    // This should panic because it queries an invald key.
    discoverer.entry(2);
}

#[tokio::test]
#[should_panic]
async fn query_invalid_entry_iter() {
    let mut broker = TestBroker::new();
    let client = broker.add_client().await;

    let discoverer = client
        .create_discoverer()
        .bare_object(1, ObjectUuid::new_v4())
        .build()
        .await
        .unwrap();

    // This should panic because it queries an invald key.
    discoverer.entry_iter(2);
}
