"""Connection between the remote server and feather"""

from __future__ import annotations

import re
from abc import ABC, abstractmethod
from ttrss.client import TTRClient, Headline
import google_reader

from feather.config import Config
from feather.data import Article, ArticleId, Category


class ClientSession(ABC):
    """Represent a client session with a RSS reader server. All operations will block indefinitely when waiting for the network."""

    @abstractmethod
    def __init__(self, config: Config):
        self.config: Config = config

    @abstractmethod
    def set_read_flag(self, article_ids: list[ArticleId], read: bool = True):
        """Mark all the given articles as read or unread."""
        pass

    @abstractmethod
    def list_categories(self) -> list[Category]:
        """Returns a list of all the categories on the server."""
        pass

    @abstractmethod
    def get_articles_in_category(
        self,
        category: Category,
        limit: int,
        continuation: int = 0,
        unread_only: bool = False,
    ) -> list[Article]:
        """Returns a list of Articles in the given category. limit and continuation are required for pagination."""
        pass


## Google Reader API ##

# Monkey patch the item.enclosure class; FreshRSS returns the link in the href field but the library expects it in url
ContentItemEnclosure_old_init = google_reader.ContentItemEnclosure.__init__


def ContentItemEnclosure_new_init(self, type: str, url: str = None, href: str = None):
    ContentItemEnclosure_old_init(self, url=url or href, type=type)


google_reader.ContentItemEnclosure.__init__ = ContentItemEnclosure_new_init

label_name_re = re.compile("user/.*/label/(.*)")


class GReaderSession(ClientSession):
    """Google Reader API client"""

    def __init__(self, config: Config):
        self.config: Config = config
        self.greader: google_reader.Client = google_reader.Client(config.server_url)
        self.auth_token: str = self.greader.login(
            config.server_user, config.server_password
        )
        self.csrf_token: str = self.greader.get_token(self.auth_token)

    def set_read_flag(self, article_ids: list[ArticleId], read: bool = True):
        if read:
            self.greader.edit_tags(
                self.auth_token,
                self.csrf_token,
                item_ids=article_ids,
                add_tags=[google_reader.STREAM_READ],
            )
        else:
            self.greader.edit_tags(
                self.auth_token,
                self.csrf_token,
                item_ids=article_ids,
                remove_tags=[google_reader.STREAM_READ],
            )

    def list_categories(self) -> list[Category]:
        tags = [
            tag
            for tag in self.greader.list_tags(self.auth_token)
            if tag.type == "folder"
        ]
        categories = []
        for category in tags:
            category_name = category.label or label_name_re.search(category.id).group(1)
            category_id = category.id
            categories.append(Category(id=category_id, title=category_name))
        return categories

    def get_articles_in_category(
        self,
        category: Category,
        limit: int = 1000,
        continuation: int = 0,
        unread_only: bool = False,
    ) -> list[GReaderArticle]:
        item_ids = [
            item.id
            for item in self.greader.get_stream_items_ids(
                self.auth_token,
                stream_id=category.id,
                exclude_target="user/-/state/com.google/read" if unread_only else None,
                limit=limit,
                continuation=continuation,
            ).item_refs
        ]
        if len(item_ids) == 0:
            return []
        else:
            item_contents = self.greader.get_stream_items_contents(
                self.auth_token, self.csrf_token, item_ids=item_ids
            )
            return [
                GReaderArticle(self, category, item_content)
                for item_content in item_contents.items
            ]


class GReaderArticle(Article):
    def __init__(self, session: GReaderSession, category: Category, item_content):
        self.config = session.config

        self.id = item_content.id
        self.category = category

        self.unread = "user/-/state/com.google/read" not in item_content.categories
        self.title = item_content.title
        self.published = item_content.published
        self.updated = item_content.updated
        self.author = item_content.author
        self.summary = item_content.summary.content
        self.content = item_content.content.content
        self.feed_title = item_content.origin.title
        self.feed_url = item_content.origin.html_url
        if len(item_content.canonical) > 0:
            self.article_url = item_content.canonical[0].href
        else:
            # several API references I've seen didn't mention canonical, but alternate seems to also be the article link (?) and should be an ok fallback
            self.article_url = item_content.alternate[0].href

        self.json_path = self._get_json_path()


## Tiny Tiny RSS API ##


# Monkey patch Headline.__init__ to skip timestamp to datetime conversion
# Articles may have a negative timestamp and Python's datetime.fromtimestamp doesn't like that, so instead we keep the timestamp and deal with the issue in data.py/format_datetime
def Headline_init(self, attr, client):
    super(Headline, self).__init__(attr, client)


Headline.__init__ = Headline_init


class TTRSession(ClientSession):
    """Tiny Tiny RSS API client"""

    def __init__(self, config: Config):
        self.config: Config = config
        self.ttrss: TTRClient = TTRClient(
            config.server_url,
            config.server_user,
            config.server_password,
            auto_login=True,
        )
        self.ttrss.login()
        self.feeds: dict = {}

    def set_read_flag(self, article_ids: list[ArticleId], read: bool = True):
        if read:
            self.ttrss.mark_read(article_ids)
        else:
            self.ttrss.mark_unread(article_ids)

    def list_categories(self) -> list[Category]:
        self.feeds = {}

        def get_categories_recursive(parent_category, parent_categories=[]):
            categories = []
            index = 1
            for item in parent_category["items"]:
                # skip special categories and feeds
                if item["bare_id"] <= 0:
                    continue
                # category
                elif item.get("type") == "category":
                    category = Category(
                        id=item["bare_id"],
                        parents=parent_categories,
                        title=item["name"],
                        order=index,
                    )
                    categories.append(category)
                    categories += get_categories_recursive(
                        item, parent_categories + [category]
                    )
                # feeds
                elif "type" not in item:
                    self.feeds[item["bare_id"]] = item
                    self.feeds[item["bare_id"]]["order"] = index
                index += 1
            return categories

        tree = self.ttrss.get_feed_tree()
        return get_categories_recursive(tree["categories"])

    def get_articles_in_category(
        self,
        category: Category,
        limit: int = 200,
        continuation: int = 0,
        unread_only: bool = False,
    ) -> list[TTRArticle]:
        headlines = self.ttrss.get_headlines(
            feed_id=category.id,
            limit=limit,
            skip=continuation,
            is_cat=True,
            show_excerpt=True,
            show_content=True,
            view_mode="unread" if unread_only else "all_articles",
            include_attachments=False,
            include_nested=False,
        )
        return [TTRArticle(self, category, headline) for headline in headlines]


class TTRArticle(Article):
    def __init__(self, session: TTRSession, category: Category, article):
        self.config = session.config

        self.id = article.id
        self.category = category

        self.unread = article.unread
        self.title = article.title
        self.published = article.updated
        self.updated = article.updated
        self.author = article.author
        self.summary = article.excerpt
        self.content = article.content
        self.feed_title = article.feed_title
        self.feed_url = article.site_url
        self.feed_icon_url = session.feeds[article.feed_id]["icon"]
        self.feed_order = session.feeds[article.feed_id]["order"]
        self.article_url = article.link
        self.comments_url = article.comments_link
        self.language = article.lang
        self.image_url = article.flavor_image

        self.json_path = self._get_json_path()
