"""Module contains functionality for parsing HTML page of a particular vulnerability."""

from cve_connector.vendor_cve.implementation.parsers.general_and_format_parsers\
    .html_parser import HtmlParser
from cve_connector.vendor_cve.implementation.vendors_storage_structures.lenovo import Lenovo

from cve_connector.vendor_cve.implementation.utilities.utility_functions \
    import concat_strings, string_to_date, normalize_string
from cve_connector.vendor_cve.implementation.utilities.check_correctness \
    import is_correct_cve_id


def normalize_sec_advisory(string):
    """
    Modifies input string to a form which will be further processed.

    :param string: input string
    :return: modified string
    """
    normalized_string = string.lstrip(':').strip()
    return normalized_string


class LenovoVulnerabilityParser(HtmlParser):
    """
    Contains functionality for parsing HTML of specific CVE.
    """
    def __init__(self, url):
        super().__init__(url)
        self.date_format = '%m/%d/%Y'
        self.load_content()

    def parse(self):
        """
        Provides parsing functionality.

        :return: None
        """
        vuln_name = self.get_vulnerability_name()
        subtree_with_info = self.data.xpath('.//div[@id="detailBody"]')
        if not subtree_with_info:
            raise ValueError('Format of Advisory has changed.')
        content = subtree_with_info[0]
        imp = self.get_pot_impact(content)
        sev = self.get_severity(content)
        scope = self.get_scope_od_imp(content)
        ids = self.get_cve_ids(content)
        desc = self.get_summary_descr(content)
        mit_str = self.get_mit_strategy(content)
        released = self.get_published_date()

        is_patched = self.check_patched()

        for cve_id in ids:
            new_entity = Lenovo(cve_id=cve_id, vuln_name=vuln_name, pot_impact=imp, severity=sev,
                                scope=scope, desc=desc, mit_strat=mit_str, fst_pub=released,
                                patch=is_patched)
            if new_entity.is_valid_entity():
                self.entities.append(new_entity)

    def get_vulnerability_name(self):
        """
        Extracts and returns name of vulnerability.

        :return: name of vulnerability
        """
        vuln_name_list = self.data.xpath('.//h1[@class="documentTitle"]/text()')
        return concat_strings(vuln_name_list)

    def get_pot_impact(self, tree):
        """
        Extracts and returns potential impact.

        :param tree: XML tree
        :return: potential impact
        """
        pot_imp = tree.xpath('.//*[contains(strong, "Potential Impact")]/text()')
        return normalize_string(concat_strings(pot_imp))

    def get_severity(self, tree):
        """
        Extracts and returns severity.

        :param tree: XML tree
        :return: severity
        """
        sev = tree.xpath('.//*[contains(strong, "Severity")]/text()')
        return normalize_string(concat_strings(sev))

    def get_scope_od_imp(self, tree):
        """
        Extracts and returns scope of impact.

        :param tree: XML tree
        :return: scope
        """
        scope = tree.xpath('.//*[contains(strong, "Scope of Impact")]/text()')
        return normalize_string(concat_strings(scope))

    def get_cve_ids(self, tree):
        """
        Extracts and returns CVE IDs.

        :param tree: XML tree
        :return: CVE IDs
        """
        texts_with_cve = tree.xpath('.//*[contains(strong, "CVE")]/text()')
        cves = []
        for text in texts_with_cve:
            cves_list = (t.strip() for t in text.split(','))
            for cve in cves_list:
                if is_correct_cve_id(cve):
                    cves.append(cve)
        return cves

    def get_summary_descr(self, tree):
        """
        Extracts and returns description.

        :param tree: XML tree
        :return: description
        """
        txt = self.get_text_between_strings(tree, 'Summary Description', 'Mitigation Strategy')
        return normalize_string(txt)

    def get_mit_strategy(self, tree):
        """
        Extracts and returns mitigation strategy.

        :param tree: XML tree
        :return: mitigation strategy
        """
        txt = self.get_text_between_strings(tree, 'Mitigation Strategy', 'Product Impact')
        return normalize_string(txt)

    def get_text_between_strings(self, tree, begin_str, end_str):
        """
        Helper function for processing string.

        :param tree: XML tree
        :param begin_str: start of string
        :param end_str: end of string
        :return: modified string
        """
        result = ''
        all_p_list = tree.xpath('.//p[preceding-sibling::p[contains(strong, "{0}")]]//text()'
                                .format(begin_str))

        for paragraph in all_p_list:
            if not paragraph.startswith(end_str):
                result += paragraph
            else:
                break
        return result

    def get_published_date(self):
        """
        Extracts and returns published date.

        :return: published date or None
        """
        date_string_list = self.data.xpath('.//div[@class="right rightTwo id-area"]//div/span/text()')
        if date_string_list:
            found_index = date_string_list.index("Original Publish Date:")
            date_string = date_string_list[found_index + 1]
            return string_to_date(date_string, self.date_format)
        return None

    def check_patched(self):
        """
        Determines whether patch is available.

        :return: True if patch is available
        """
        tables_list = self.data.xpath('.//table')
        if len(tables_list) == 0:
            return
        tables_list.pop(0)  # remove table with Revision History
        for table in tables_list:
            strings = table.xpath('.//text()')
            if any('researching' in txt.lower() for txt in strings):
                return False
        return True
