use super::{GArticleID, GDateTime, GEnclosures, GFeedID, GMarked, GOptionalDateTime, GRead, GTags};
use crate::content_page::{ArticleListColumn, ArticleListMode};
use crate::util::constants;
use chrono::{TimeDelta, Utc};
use diffus::{Diffable, Same, edit::Edit};
use glib::{Object, Properties, prelude::*};
use gtk4::subclass::prelude::*;
use news_flash::models::{Article, Enclosure, FatArticle, Feed, Tag};
use std::cell::{Cell, RefCell};

mod imp {
    use super::*;

    #[derive(Default, Properties)]
    #[properties(wrapper_type = super::GArticle)]
    pub struct GArticle {
        #[property(get, set, name = "article-id")]
        pub id: RefCell<GArticleID>,
        #[property(get, set, nullable)]
        pub title: RefCell<Option<String>>,
        #[property(get, set, name = "title-is-markup")]
        pub title_is_markup: Cell<bool>,
        #[property(get, set, name = "feed-id")]
        pub feed_id: RefCell<GFeedID>,
        #[property(get, set)]
        pub summary: RefCell<String>,
        #[property(get, set, nullable)]
        pub url: RefCell<Option<String>>,

        #[property(get, set)]
        pub date: RefCell<GDateTime>,
        #[property(get, set)]
        pub updated: RefCell<GOptionalDateTime>,
        #[property(get, set = Self::set_read, builder(GRead::Unread))]
        pub read: Cell<GRead>,
        #[property(get, set, builder(GMarked::Unmarked))]
        pub marked: Cell<GMarked>,
        #[property(get, set)]
        pub tags: RefCell<GTags>,
        #[property(get, set, nullable)]
        pub thumbnail: RefCell<Option<String>>,
        #[property(get, set, name = "feed-title")]
        pub feed_title: RefCell<String>,

        // fat article
        #[property(get, set, nullable)]
        pub content: RefCell<Option<String>>,
        #[property(get, set, nullable, name = "scraped-content")]
        pub scraped_content: RefCell<Option<String>>,
        #[property(get, set, nullable)]
        pub author: RefCell<Option<String>>,
        #[property(get, set, name = "title-classes")]
        pub title_classes: RefCell<Vec<String>>,
        #[property(get, set, name = "summary-classes")]
        pub summary_classes: RefCell<Vec<String>>,
        #[property(get, set)]
        pub enclosures: RefCell<GEnclosures>,

        #[property(get, set = Self::set_selected)]
        pub selected: Cell<bool>,

        #[property(get = Self::get_has_scraped_content, name = "has-scraped-content")]
        pub has_scraped_content: Cell<bool>,
    }

    #[glib::object_subclass]
    impl ObjectSubclass for GArticle {
        const NAME: &'static str = "GNewsFlashArticle";
        type Type = super::GArticle;
    }

    #[glib::derived_properties]
    impl ObjectImpl for GArticle {}

    impl PartialEq for GArticle {
        fn eq(&self, other: &GArticle) -> bool {
            self.id.borrow().as_ref() == other.id.borrow().as_ref()
        }
    }

    impl GArticle {
        fn set_read(&self, read: GRead) {
            self.read.set(read);
            self.update_label_styles();
        }

        fn set_selected(&self, selected: bool) {
            self.selected.set(selected);
            self.update_label_styles();
        }

        fn get_has_scraped_content(&self) -> bool {
            self.scraped_content.borrow().is_some()
        }

        pub fn update_label_styles(&self) {
            let have_title = self.title.borrow().is_some();
            let read = self.read.get();
            let selected = self.selected.get();
            let article_list_mode = ArticleListColumn::instance().mode();

            let title_css_classes = Self::get_title_styles(read, selected, article_list_mode);
            self.obj().set_title_classes(title_css_classes);

            let summary_css_classes = Self::get_summary_styles(have_title, read, selected, article_list_mode);
            self.obj().set_summary_classes(summary_css_classes);
        }

        pub(super) fn get_title_styles(read: GRead, selected: bool, article_list_mode: ArticleListMode) -> Vec<String> {
            match (read, selected, article_list_mode) {
                (GRead::Read, false, ArticleListMode::Unread) => vec!["large".into(), "dim-label".into()],
                (GRead::Unread, _, _) => vec!["large".into(), "heading".into()],
                _ => vec!["large".into()],
            }
        }

        pub(super) fn get_summary_styles(
            have_title: bool,
            read: GRead,
            selected: bool,
            article_list_mode: ArticleListMode,
        ) -> Vec<String> {
            if have_title {
                vec!["subtitle".into()]
            } else {
                match (read, selected, article_list_mode) {
                    (GRead::Read, false, ArticleListMode::Unread) => vec!["body".into(), "dim-label".into()],
                    (GRead::Unread, _, _) => vec!["heading".into()],
                    _ => vec!["body".into()],
                }
            }
        }
    }
}

glib::wrapper! {
    pub struct GArticle(ObjectSubclass<imp::GArticle>);
}

impl Default for GArticle {
    fn default() -> Self {
        Object::new()
    }
}

impl GArticle {
    pub const NONE: Option<GArticle> = None;

    pub fn from_fat_article(
        fat_article: FatArticle,
        feed: Option<&Feed>,
        tags: Vec<Tag>,
        enclosures: Vec<Enclosure>,
    ) -> Self {
        let article = Article {
            article_id: fat_article.article_id,
            title: fat_article.title,
            author: fat_article.author,
            feed_id: fat_article.feed_id,
            date: fat_article.date,
            summary: fat_article.summary,
            direction: fat_article.direction,
            marked: fat_article.marked,
            thumbnail_url: fat_article.thumbnail_url,
            url: fat_article.url,
            synced: fat_article.synced,
            unread: fat_article.unread,
            updated: fat_article.updated,
        };

        Self::from_article(
            article,
            feed,
            tags,
            enclosures,
            fat_article.html,
            fat_article.scraped_content,
        )
    }

    pub fn from_article(
        article: Article,
        feed: Option<&Feed>,
        tags: Vec<Tag>,
        enclosures: Vec<Enclosure>,
        content: Option<String>,
        scraped_content: Option<String>,
    ) -> Self {
        let Article {
            article_id,
            title,
            author,
            feed_id,
            date,
            summary,
            direction: _,
            marked,
            thumbnail_url,
            url,
            synced: _,
            updated,
            unread,
        } = article;

        let enclosures =
            GEnclosures::with_additional_data(enclosures, title.as_ref(), author.as_ref(), feed.map(|f| &f.label));

        let have_title = title.is_some();
        let is_html = title.as_deref().map(ammonia::is_html).unwrap_or(false);
        let title = title.map(|title| {
            if is_html {
                html2gtk::Parser::parse_label(&title)
                    .map(|label| label.text)
                    .unwrap_or(title)
            } else {
                title
            }
        });
        let feed_title = feed.map(|f| f.label.clone()).unwrap_or(constants::UNKNOWN_FEED.into());
        let summary = match summary {
            Some(summary) => summary,
            None => "No Summary".to_owned(),
        };
        let tags: GTags = tags.into();
        let url = url.map(|url| url.as_str().to_string());

        let obj = GArticle::default();
        let imp = obj.imp();
        imp.id.replace(article_id.into());
        imp.title.replace(title);
        imp.title_is_markup.set(is_html);
        imp.feed_id.replace(feed_id.into());
        imp.feed_title.replace(feed_title);
        imp.summary.replace(summary);
        imp.date.replace(date.into());
        imp.updated.replace(updated.into());
        imp.read.set(unread.into());
        imp.marked.set(marked.into());
        imp.tags.replace(tags);
        imp.thumbnail.replace(thumbnail_url);
        imp.url.replace(url);
        imp.author.replace(author);
        imp.content.replace(content);
        imp.scraped_content.replace(scraped_content);
        imp.enclosures.replace(enclosures);
        imp.title_classes.replace(imp::GArticle::get_title_styles(
            unread.into(),
            false,
            ArticleListMode::All,
        ));
        imp.summary_classes.replace(imp::GArticle::get_summary_styles(
            have_title,
            unread.into(),
            false,
            ArticleListMode::All,
        ));
        obj
    }
}

impl Same for GArticle {
    fn same(&self, other: &Self) -> bool {
        self.article_id() == other.article_id()
    }
}

impl<'a> Diffable<'a> for GArticle {
    type Diff = ArticleDiff;

    fn diff(&'a self, other: &'a Self) -> Edit<'a, Self> {
        let self_imp = self.imp();
        let other_imp = other.imp();

        let date = if *self_imp.date.borrow() == *other_imp.date.borrow() {
            let diff_days = Utc::now().date_naive() - self.date().as_ref().date_naive();
            if diff_days > TimeDelta::days(0) && diff_days <= TimeDelta::days(1) {
                Some(other.date())
            } else {
                None
            }
        } else {
            Some(other.date())
        };

        let updated = if *self_imp.updated.borrow() == *other_imp.updated.borrow() {
            let diff_days = if let Some(self_updated) = self.updated().as_ref() {
                Utc::now().date_naive() - self_updated.date_naive()
            } else {
                TimeDelta::zero()
            };
            if diff_days > TimeDelta::days(0) && diff_days <= TimeDelta::days(1) {
                Some(other.updated())
            } else {
                None
            }
        } else {
            Some(other.updated())
        };

        let read = if self.read() == other.read() {
            None
        } else {
            Some(other.read())
        };

        let marked = if self.marked() == other.marked() {
            None
        } else {
            Some(other.marked())
        };

        let tags = if self.tags() == other.tags() {
            None
        } else {
            Some(other.tags())
        };

        let thumbnail = if self_imp.thumbnail.borrow().as_deref() == other_imp.thumbnail.borrow().as_deref() {
            None
        } else {
            Some(other.thumbnail())
        };

        let feed_title = if self_imp.feed_title.borrow().as_str() == other_imp.feed_title.borrow().as_str() {
            None
        } else {
            Some(other.feed_title())
        };

        if self_imp == other_imp
            && date.is_none()
            && read.is_none()
            && marked.is_none()
            && tags.is_none()
            && thumbnail.is_none()
            && feed_title.is_none()
        {
            Edit::Copy(self)
        } else {
            Edit::Change(ArticleDiff {
                id: self.article_id(),
                read,
                marked,
                date,
                updated,
                tags,
                thumbnail,
                feed_title,
            })
        }
    }
}

#[derive(Debug, Clone)]
pub struct ArticleDiff {
    pub id: GArticleID,
    pub read: Option<GRead>,
    pub marked: Option<GMarked>,
    pub date: Option<GDateTime>,
    pub updated: Option<GOptionalDateTime>,
    pub tags: Option<GTags>,
    pub thumbnail: Option<Option<String>>,
    pub feed_title: Option<String>,
}
