use std::ops::RangeBounds;

use chrono::{DateTime, Utc};
use futures::Stream;

use crate::{
    Contact, ContactId, Group, GroupId, MessageId, ReceivedMessage, RegistrationData, Result,
    RichMessage, Thread, ThreadId, convert::from_presage::MessageConversionAdditionalDataProvider,
};

pub trait Store: Clone + MessageConversionAdditionalDataProvider {
    type PresageStore: presage::store::Store;

    fn presage_store(&self) -> Self::PresageStore;

    fn registration_data(&self) -> impl Future<Output = Result<RegistrationData>>;

    fn group(&self, id: GroupId) -> impl Future<Output = Result<Option<Group>>>;
    fn groups(&self) -> impl Future<Output = Result<Vec<Group>>>;
    fn contact(&self, id: ContactId) -> impl Future<Output = Result<Option<Contact>>>;
    fn contacts(&self) -> impl Future<Output = Result<Vec<Contact>>>;
    fn thread(&self, id: ThreadId) -> impl Future<Output = Result<Option<Thread>>> {
        async move {
            match id {
                ThreadId::Contact(account_id) => Ok(self
                    .contact(ContactId {
                        account_id,
                        in_group: None,
                    })
                    .await?
                    .map(Thread::Contact)),
                ThreadId::Group(group_id) => Ok(self.group(group_id).await?.map(Thread::Group)),
            }
        }
    }
    fn threads(&self) -> impl Future<Output = Result<Vec<Thread>>> {
        async {
            let contacts = self.contacts().await?;
            let groups = self.groups().await?;

            Ok(contacts
                .into_iter()
                .map(Into::into)
                .chain(groups.into_iter().map(Into::into))
                .collect())
        }
    }

    fn upsert_contact(&self, contact: Contact) -> impl Future<Output = Result<()>>;
    fn upsert_group(&self, group: Group) -> impl Future<Output = Result<()>>;
    fn store_message(&self, message: ReceivedMessage) -> impl Future<Output = Result<()>>;

    fn messages(
        &self,
        thread: ThreadId,
        range: impl RangeBounds<DateTime<Utc>>,
    ) -> impl Future<Output = Result<impl Stream<Item = Result<RichMessage>>>>;

    fn message(&self, id: MessageId) -> impl Future<Output = Result<Option<RichMessage>>>;

    fn clear_registration(&self) -> impl Future<Output = Result<()>>;
    fn clear_contents(&self) -> impl Future<Output = Result<()>>;
    fn clear_messages(&self) -> impl Future<Output = Result<()>>;
    fn clear_thread_messages(&self, thread: ThreadId) -> impl Future<Output = Result<()>>;
}
