use std::collections::HashSet;

use crate::models::{ArticleID, ArticleOrder, CategoryID, FeedID, Marked, OrderBy, Read, TagID};
use chrono::{DateTime, Utc};

use super::{CategoryMapping, FeedMapping};

#[derive(Clone, Debug, Default)]
pub struct ArticleFilter {
    pub limit: Option<i64>,
    pub offset: Option<i64>,
    pub order: Option<ArticleOrder>,
    pub order_by: Option<OrderBy>,
    pub unread: Option<Read>,
    pub marked: Option<Marked>,
    pub feeds: Option<Vec<FeedID>>,
    pub feed_blacklist: Option<Vec<FeedID>>,
    pub categories: Option<Vec<CategoryID>>,
    pub category_blacklist: Option<Vec<CategoryID>>,
    pub tags: Option<Vec<TagID>>,
    pub ids: Option<Vec<ArticleID>>,
    pub newer_than: Option<DateTime<Utc>>,
    pub older_than: Option<DateTime<Utc>>,
    pub synced_before: Option<DateTime<Utc>>,
    pub synced_after: Option<DateTime<Utc>>,
    pub search_term: Option<String>,
}

impl ArticleFilter {
    pub fn ids(article_ids: Vec<ArticleID>) -> Self {
        Self {
            ids: Some(article_ids),
            ..Self::default()
        }
    }

    pub fn read_ids(article_ids: Vec<ArticleID>, read: Read) -> Self {
        Self {
            unread: Some(read),
            ids: Some(article_ids),
            ..Self::default()
        }
    }

    pub fn marked_ids(article_ids: Vec<ArticleID>, marked: Marked) -> Self {
        Self {
            marked: Some(marked),
            ids: Some(article_ids),
            ..Self::default()
        }
    }

    pub fn feed_unread(feed_id: &FeedID) -> Self {
        Self {
            unread: Some(Read::Unread),
            feeds: Some([feed_id.clone()].into()),
            ..Self::default()
        }
    }

    pub fn category_unread(category_id: &CategoryID) -> Self {
        Self {
            unread: Some(Read::Unread),
            categories: Some([category_id.clone()].into()),
            ..Self::default()
        }
    }

    pub fn tag_unread(tag_id: &TagID) -> Self {
        Self {
            unread: Some(Read::Unread),
            tags: Some([tag_id.clone()].into()),
            ..Self::default()
        }
    }

    pub fn all_unread() -> Self {
        Self {
            unread: Some(Read::Unread),
            ..Self::default()
        }
    }

    pub fn all_marked() -> Self {
        Self {
            marked: Some(Marked::Marked),
            ..Self::default()
        }
    }

    pub fn feeds_to_load(&self, category_mappings: &Vec<CategoryMapping>, feed_mappings: &Vec<FeedMapping>) -> HashSet<FeedID> {
        let mut result = HashSet::new();

        if let Some(feeds) = &self.feeds {
            for feed_id in feeds {
                result.insert(feed_id.clone());
            }
        }

        if let Some(categories) = &self.categories {
            for category_id in categories {
                let feed_ids = Self::find_all_feeds(category_id, category_mappings, feed_mappings);
                for feed_id in feed_ids {
                    result.insert(feed_id);
                }
            }
        }

        result
    }

    pub fn feeds_to_blacklist(&self, category_mappings: &Vec<CategoryMapping>, feed_mappings: &Vec<FeedMapping>) -> HashSet<FeedID> {
        let mut result = HashSet::new();

        if let Some(feed_blacklist) = &self.feed_blacklist {
            for feed_id in feed_blacklist {
                result.insert(feed_id.clone());
            }
        }

        if let Some(category_blacklist) = &self.category_blacklist {
            for category_id in category_blacklist {
                let feed_ids = Self::find_all_feeds(category_id, category_mappings, feed_mappings);
                for feed_id in feed_ids {
                    result.insert(feed_id);
                }
            }
        }

        result
    }

    fn find_all_feeds(category_id: &CategoryID, category_mappings: &Vec<CategoryMapping>, feed_mappings: &Vec<FeedMapping>) -> HashSet<FeedID> {
        fn find_all_feeds_impl(
            category_id: &CategoryID,
            category_mappings: &Vec<CategoryMapping>,
            feed_mappings: &Vec<FeedMapping>,
            result: &mut HashSet<FeedID>,
        ) {
            for mapping in feed_mappings {
                if &mapping.category_id == category_id {
                    result.insert(mapping.feed_id.clone());
                }
            }

            for mapping in category_mappings {
                if &mapping.parent_id == category_id {
                    find_all_feeds_impl(&mapping.category_id, category_mappings, feed_mappings, result);
                }
            }
        }

        let mut result = HashSet::new();
        find_all_feeds_impl(category_id, category_mappings, feed_mappings, &mut result);
        result
    }
}
