use super::{FeedListTree, TagListModel};
use crate::{
    content_page::ArticleListMode,
    gobject_models::{GSidebarSelection, SidebarSelectionType},
    sidebar::FeedListItemID,
    undo_action::UndoAction,
};
use news_flash::{
    NewsFlash,
    error::NewsFlashError,
    models::{Category, CategoryID, CategoryMapping, Feed, FeedID, FeedMapping, NEWSFLASH_TOPLEVEL, Tag},
};
use std::collections::HashMap;
use tokio::time::Instant;

#[derive(Debug, Default)]
pub struct SidebarLoader {
    hide_future_articles: bool,
    article_list_mode: ArticleListMode,
    undo_action: Option<UndoAction>,
}

impl SidebarLoader {
    pub fn hide_future_articles(mut self, hide_future_articles: bool) -> Self {
        self.hide_future_articles = hide_future_articles;
        self
    }

    pub fn article_list_mode(mut self, article_list_mode: ArticleListMode) -> Self {
        self.article_list_mode = article_list_mode;
        self
    }

    pub fn undo_action(mut self, undo_action: Option<UndoAction>) -> Self {
        self.undo_action = undo_action;
        self
    }

    pub fn load(self, news_flash: &NewsFlash) -> Result<SidebarLoadResult, NewsFlashError> {
        let (categories, category_mappings) = news_flash.get_categories()?;
        let (feeds, feed_mappings) = news_flash.get_feeds()?;

        let start = Instant::now();
        let today_count = match self.article_list_mode {
            ArticleListMode::All | ArticleListMode::Unread => {
                news_flash.today_unread_count(self.hide_future_articles)?
            }
            ArticleListMode::Marked => news_flash.today_marked_count()?,
        } as u32;

        // collect unread and marked counts
        let feed_count_map = match self.article_list_mode {
            ArticleListMode::All | ArticleListMode::Unread => {
                news_flash.unread_count_feed_map(self.hide_future_articles)?
            }
            ArticleListMode::Marked => news_flash.marked_count_feed_map()?,
        }
        .into_iter()
        .map(|(key, val)| (key, val as u32))
        .collect();

        let end = Instant::now();
        let duration = end.duration_since(start).as_millis();
        tracing::trace!(duration, "reading unread counts took");

        let (tags, _taggings) = news_flash.get_tags()?;

        Ok(SidebarLoadResult {
            categories,
            category_mappings,
            feeds,
            feed_mappings,
            feed_count_map,
            today_count,
            tags,
            undo_action: self.undo_action,
        })
    }
}

#[derive(Debug, Default, Clone)]
pub struct SidebarLoadResult {
    pub(self) categories: Vec<Category>,
    pub(self) category_mappings: Vec<CategoryMapping>,
    pub(self) feeds: Vec<Feed>,
    pub(self) feed_mappings: Vec<FeedMapping>,
    pub(self) feed_count_map: HashMap<FeedID, u32>,
    pub(self) today_count: u32,
    pub(self) tags: Vec<Tag>,
    pub(self) undo_action: Option<UndoAction>,
}

impl SidebarLoadResult {
    pub fn build_list_models(mut self, selection: GSidebarSelection) -> (u32, u32, u32, FeedListTree, TagListModel) {
        let mut tree = FeedListTree::new();
        let mut tag_list_model = TagListModel::new();

        let mut pending_delete_feed = None;
        let mut pending_delete_category = None;
        let mut pending_delete_tag = None;

        if let Some(undo_action) = &self.undo_action {
            match undo_action {
                UndoAction::DeleteFeed(id, _label) => pending_delete_feed = Some(id),
                UndoAction::DeleteCategory(id, _label) => pending_delete_category = Some(id),
                UndoAction::DeleteTag(id, _label) => pending_delete_tag = Some(id),
                UndoAction::MarkRead(_) => {}
            };
        }

        // If there are feeds without a category:
        // - create mappings for all feeds without category to be children of the toplevel
        let mut uncategorized_mappings =
            Self::create_mappings_for_uncategorized_feeds(&self.feeds, &self.feed_mappings);
        self.feed_mappings.append(&mut uncategorized_mappings);

        let category_count_map = self.build_category_item_count_map(pending_delete_feed, pending_delete_category);

        // feedlist: Categories
        let mut categories_to_add = Vec::new();
        for mapping in &self.category_mappings {
            if Some(&mapping.category_id) == pending_delete_category {
                continue;
            }

            let category = match self.categories.iter().find(|c| c.category_id == mapping.category_id) {
                Some(res) => res,
                None => {
                    tracing::warn!(%mapping.category_id, "Mapping for category exists, but can't find the category itself");
                    continue;
                }
            };

            let category_item_count = category_count_map.get(&category.category_id).copied().unwrap_or(0);
            categories_to_add.push((category, mapping, category_item_count));
        }
        tree.add_categories(categories_to_add);

        // feedlist: Feeds
        for mapping in &self.feed_mappings {
            if Some(&mapping.feed_id) == pending_delete_feed || Some(&mapping.category_id) == pending_delete_category {
                continue;
            }

            let feed = match self.feeds.iter().find(|feed| feed.feed_id == mapping.feed_id) {
                Some(res) => res,
                None => {
                    tracing::warn!(%mapping.feed_id, "Mapping for feed exists, but can't find the feed itself");
                    continue;
                }
            };

            let item_count = match self.feed_count_map.get(&mapping.feed_id) {
                Some(count) => *count,
                None => 0,
            };
            if tree.add_feed(feed, mapping, item_count).is_err() {}
        }

        // tag list
        for tag in self.tags {
            if Some(&tag.tag_id) == pending_delete_tag {
                continue;
            }
            _ = tag_list_model.add(tag);
        }

        tree.sort();
        tag_list_model.sort();

        let total_item_count = self.feed_count_map.values().sum();

        let selected_item_count = match selection.selection_type() {
            SidebarSelectionType::None => 0,
            SidebarSelectionType::All => total_item_count,
            SidebarSelectionType::Today => self.today_count,
            SidebarSelectionType::FeedList => match selection.feedlist_id().as_ref() {
                FeedListItemID::Category(category_id) => category_count_map.get(category_id).copied().unwrap_or(0),
                FeedListItemID::Feed(feed_mapping) => {
                    self.feed_count_map.get(&feed_mapping.feed_id).copied().unwrap_or(0)
                }
            },
            SidebarSelectionType::TagList => 0,
        };
        (
            total_item_count,
            self.today_count,
            selected_item_count,
            tree,
            tag_list_model,
        )
    }

    fn build_category_item_count_map(
        &self,
        pending_delete_feed: Option<&FeedID>,
        pending_delete_category: Option<&CategoryID>,
    ) -> HashMap<CategoryID, u32> {
        let mut map = HashMap::new();

        for mapping in &self.feed_mappings {
            let count = Self::calculate_item_count_for_category(
                &mapping.category_id,
                &self.feed_mappings,
                &self.category_mappings,
                &self.feed_count_map,
                pending_delete_feed,
                pending_delete_category,
            );
            map.insert(mapping.category_id.clone(), count);
        }

        let total_count = Self::calculate_item_count_for_category(
            &NEWSFLASH_TOPLEVEL,
            &self.feed_mappings,
            &self.category_mappings,
            &self.feed_count_map,
            pending_delete_feed,
            pending_delete_category,
        );
        map.insert(NEWSFLASH_TOPLEVEL.clone(), total_count);

        map
    }

    fn calculate_item_count_for_category(
        category_id: &CategoryID,
        feed_mappings: &[FeedMapping],
        category_mappings: &[CategoryMapping],
        item_count_map: &HashMap<FeedID, u32>,
        pending_deleted_feed: Option<&FeedID>,
        pending_deleted_category: Option<&CategoryID>,
    ) -> u32 {
        let mut count = 0;

        count += feed_mappings
            .iter()
            .filter_map(|m| {
                if &m.category_id == category_id {
                    if Some(&m.feed_id) == pending_deleted_feed {
                        return None;
                    }

                    item_count_map.get(&m.feed_id)
                } else {
                    None
                }
            })
            .sum::<u32>();

        count += category_mappings
            .iter()
            .filter_map(|m| {
                if m.parent_id == m.category_id {
                    return None;
                }

                if &m.parent_id == category_id {
                    if Some(&m.category_id) == pending_deleted_category {
                        return None;
                    }

                    Some(Self::calculate_item_count_for_category(
                        &m.category_id,
                        feed_mappings,
                        category_mappings,
                        item_count_map,
                        pending_deleted_feed,
                        pending_deleted_category,
                    ))
                } else {
                    None
                }
            })
            .sum::<u32>();

        count
    }

    fn create_mappings_for_uncategorized_feeds(feeds: &[Feed], mappings: &[FeedMapping]) -> Vec<FeedMapping> {
        let mut uncategorized_mappings = Vec::new();
        for (i, feed) in feeds.iter().enumerate() {
            if !mappings.iter().any(|m| m.feed_id == feed.feed_id) {
                uncategorized_mappings.push(FeedMapping {
                    feed_id: feed.feed_id.clone(),
                    category_id: NEWSFLASH_TOPLEVEL.clone(),
                    sort_index: Some(i as i32),
                });
            }
        }

        uncategorized_mappings
    }
}
