use chrono::{DateTime, Utc};
use nostr_rs_types::types::{Event, Filter, Kind, Tag};
use std::collections::HashSet;

#[derive(Debug)]
pub struct EventFilter {
    ids: Vec<Vec<u8>>,
    authors: Vec<Vec<u8>>,
    kinds: Vec<Kind>,
    references_to_public_key: Vec<Vec<u8>>,
    references_to_event: Vec<Vec<u8>>,
    since: Option<DateTime<Utc>>,
    until: Option<DateTime<Utc>>,
}

impl From<Filter> for EventFilter {
    fn from(query: Filter) -> Self {
        let mut authors = query
            .authors
            .into_iter()
            .map(|id| id.bytes)
            .collect::<Vec<_>>();

        let mut references_to_public_key = query
            .references_to_public_key
            .into_iter()
            .map(|tag| tag.bytes)
            .collect::<HashSet<_>>()
            .into_iter()
            .collect::<Vec<_>>();

        let mut references_to_event = query
            .references_to_event
            .into_iter()
            .map(|tag| tag.bytes)
            .collect::<HashSet<_>>()
            .into_iter()
            .collect::<Vec<_>>();

        let mut kinds = query.kinds.into_iter().collect::<Vec<Kind>>();

        let mut ids = query.ids.into_iter().map(|id| id.bytes).collect::<Vec<_>>();

        // Sort everything for a quick binary_search instead of a linear search
        ids.sort();
        references_to_event.sort();
        references_to_public_key.sort();
        authors.sort();
        kinds.sort();

        EventFilter {
            ids,
            authors,
            kinds,
            references_to_public_key,
            references_to_event,
            since: query.since,
            until: query.until,
        }
    }
}

impl EventFilter {
    /// Receives a list of event-IDs, and returns a list of events that match
    /// the given filter.
    ///
    /// The list of event-IDs should come from an index from any namespace, and
    /// the filter will load the events from the `Events` namespace and will
    /// filter them by the given parameters.
    #[inline]
    pub fn check_event(&self, event: &Event) -> bool {
        if !self.ids.is_empty() && self.ids.binary_search(&event.id.0.to_vec()).is_err() {
            return false;
        }
        if !self.references_to_public_key.is_empty()
            || !self.references_to_event.is_empty() && self.authors.is_empty()
        {
            let mut found = false;

            for tag in event.tags().iter() {
                match tag {
                    Tag::Event(event) => {
                        if self
                            .references_to_event
                            .binary_search(&event.id.as_ref().to_vec())
                            .is_ok()
                        {
                            found = true;
                        }
                    }
                    Tag::PubKey(key) => {
                        if self
                            .references_to_public_key
                            .binary_search(&key.id.as_ref().to_vec())
                            .is_ok()
                        {
                            found = true;
                        }
                    }
                    _ => {}
                }

                if found {
                    break;
                }
            }

            if !found {
                return false;
            }
        }

        if !self.authors.is_empty()
            && self
                .authors
                .binary_search(&event.author().as_ref().to_vec())
                .is_err()
        {
            return false;
        }

        if !self.kinds.is_empty() && self.kinds.binary_search(&event.kind()).is_err() {
            return false;
        }

        if let Some(since) = self.since {
            if event.created_at() < since {
                return false;
            }
        }
        if let Some(until) = self.until {
            if event.created_at() > until {
                return false;
            }
        }

        true
    }
}
