import logging

from lxml.html import HtmlElement

from github.post import Post
from util.requests import delay_next_request
from lxml import html

logger = logging.getLogger("github-retriever_logger")


class Discussion(object):
    """ A GitHub Discussion. """

    def __init__(self, repo, github_path):
        self.repo = repo
        self.uri = "https://github.com" + github_path

        # discussion metadata
        self.title = None
        self.number = None
        self.state = None
        self.author = None
        self.timestamp = None
        self.emoji = None
        self.category = None
        self.converted_from_issue = None

        # discussion posts
        self.posts = []

    def get_column_values(self):
        return [self.repo.full_name, self.uri, self.title, self.number, self.state, self.author, self.timestamp,
                self.emoji, self.category, self.converted_from_issue]

    @classmethod
    def get_column_names(cls):
        return ["repo_name", "discussion", "title", "number", "state", "author", "timestamp",
                "emoji", "category", "converted_from_issue"]

    def __str__(self):
        return str(self.uri)

    def retrieve_discussion_posts(self, session):
        delay_next_request()
        response = None
        try:
            # retrieve discussion posts
            response = session.get(self.uri)
        except ConnectionError:
            logger.error("An error occurred while accessing discussion posts: " + str(self))

        if response and response.ok:
            logger.info("Successfully accessed discussion posts: " + str(self))
            tree = html.fromstring(response.content)
            self._retrieve_discussion_metadata(tree)
            self._retrieve_discussion_posts(tree)
        else:
            logger.info("No data found in discussion posts: " + str(self))

    def _retrieve_discussion_metadata(self, root):
        logger.info("Retrieving discussion metadata...")

        self.title = _retrieve_element_content(root, "span", "js-issue-title")
        if self.title is None:
            logger.error("Error retrieving title of discussion in: " + str(self))

        self.number = _retrieve_element_content(root, "span", "gh-header-number")
        if self.number:
            self.number = int(self.number.replace("#", ""))
        else:
            logger.error("Error retrieving number of discussion in: " + str(self))

        header_prefix = '//' + _select_elements_with_class("div", "gh-header-meta")

        self.state = _retrieve_element_content(root, "span", "State", "text", header_prefix)
        if self.state is None:
            logger.error("Error retrieving state of discussion in: " + str(self))

        self.author = _retrieve_element_content(root, "a", "author", "@href", header_prefix)
        if self.author:
            self.author = self.author.replace("/", "")
        else:
            logger.error("Error retrieving author of discussion in: " + str(self))

        self.emoji = _retrieve_element_content(root, "g-emoji", "f5", "text", header_prefix)
        if self.emoji is None:
            logger.error("Error retrieving emoji of discussion in: " + str(self))

        self.category = _retrieve_element_content(root, "g-emoji", "f5", "parent-text", header_prefix)
        if self.category is None:
            logger.error("Error retrieving category of discussion in: " + str(self))

        self.timestamp = _retrieve_element_content(root, "time-ago", None, "@datetime", header_prefix)
        if self.timestamp is None:
            logger.error("Error retrieving timestamp of discussion in: " + str(self))

        sidebar_prefix = "//" + _select_elements_with_class("div", "discussion-sidebar-item")
        conversion_remark = _retrieve_element_content(root, "svg", "octicon-issue-opened", "parent-text",
                                                      sidebar_prefix)
        self.converted_from_issue = conversion_remark is not None \
                                    and conversion_remark.strip() == "Converted from issue"

    def _retrieve_discussion_posts(self, root):
        logger.info("Retrieving posts...")

        post_divs = root.xpath('.//' + _select_elements_with_class("div", "discussion")
                               + '//' + _select_elements_with_class("div", "timeline-comment"))

        for post_div in post_divs:
            post = Post(self)

            post.author = _retrieve_element_content(post_div, "a", "author", "@href")
            if post.author:
                post.author = post.author.replace("/", "")
            else:
                logger.error("Error retrieving author of discussion post in: " + str(self))

            post.timestamp = _retrieve_element_content(post_div, "time-ago", None, "@datetime")
            if post.timestamp is None:
                logger.error("Error retrieving timestamp of discussion post in: " + str(self))

            post.is_part_of_selected_answer =\
                len(post_div.xpath('.//' + _select_elements_with_class("svg", "octicon-check"))) > 0 \
                or len(post_div.xpath('.//ancestor::' + _select_elements_with_class("div", "discussion-comment") + '//'
                                      + _select_elements_with_class("svg", "octicon-check"))) > 0

            content_elements = post_div.xpath('./*/*/*/*/*/*/' + _select_elements_with_class("td", "comment-body") + '/node()')
            post.content = "\n".join(list(map(lambda elem: str(
                html.tostring(elem, pretty_print=True, encoding="unicode", with_tail=False)).strip(),
                                              filter(lambda elem: type(elem) is HtmlElement, content_elements))))
            if post.content is None:
                logger.error("Error retrieving content of discussion post in: " + str(self))

            emojis = post_div.xpath('./*/*/*/*/' + _select_elements_with_class("form", "js-pick-reaction")
                                    + '//g-emoji/text()')
            counts = post_div.xpath('./*/*/*/*/' + _select_elements_with_class("form", "js-pick-reaction")
                                    + '//span/text()')
            if len(emojis) > 0 and len(counts) > 0:
                post.reactions = [emojis, list(map(lambda count: int(count), counts))]
            else:
                post.reactions = None

            self.posts.append(post)


def _retrieve_element_content(root, element, class_name=None, target="text", prefix=None):
    if prefix:
        xpath_expression = prefix
    else:
        xpath_expression = ""

    if class_name:
        element_selector = _select_elements_with_class(element, class_name)
    else:
        element_selector = element

    if target == "text":
        xpath_expression = xpath_expression + '//' + element_selector + '/text()'
    elif target == "parent-text":
        xpath_expression = xpath_expression + '//' + element_selector + '/../text()'
    elif target.startswith("@"):
        xpath_expression = xpath_expression + '//' + element_selector + '/' + target
    else:
        logger.error("Invalid target: " + target)

    content = list(filter(lambda elem: len(elem) > 0,
                          map(lambda elem: elem.strip(),
                              root.xpath("." + xpath_expression))))

    if len(content) > 0:
        return str(content[0]).strip()
    else:
        return None


def _select_elements_with_class(element, class_name):
    # see https://stackoverflow.com/a/9133579
    return element + '[contains(concat(" ", normalize-space(@class), " "), " ' + class_name + ' ")]'
