"""This module contains entry-point functionality for processing CVE data from vendors."""

from datetime import date, timedelta
from urllib.error import HTTPError
import traceback
import configparser
import pkg_resources
import structlog
from selenium.common.exceptions import WebDriverException

from cve_connector.vendor_cve.implementation.parsers.vendor_parsers.adobe_parsers\
    .adobe_main_page_parser import AdobeMainPageParser
from cve_connector.vendor_cve.implementation.parsers.vendor_parsers.adobe_parsers\
    .adobe_vulnerability_parser import AdobeVulnerabilityParser
from cve_connector.vendor_cve.implementation.parsers.vendor_parsers.android_parsers\
    .android_main_page_parser import AndroidMainPageParser
from cve_connector.vendor_cve.implementation.parsers.vendor_parsers.android_parsers\
    .android_vulnerability_parser import AndroidVulnerabilityParser
from cve_connector.vendor_cve.implementation.parsers.vendor_parsers.apple_parsers\
    .apple_main_page_parser import AppleMainPageParser
from cve_connector.vendor_cve.implementation.parsers.vendor_parsers.apple_parsers\
    .apple_vulnerability_parser import AppleVulnerabilityParser
from cve_connector.vendor_cve.implementation.parsers.vendor_parsers.cisco_parsers\
    .cisco_main_page_parser import CiscoMainPageParser
from cve_connector.vendor_cve.implementation.parsers.vendor_parsers.cisco_parsers\
    .cisco_vulnerability_parser import CiscoVulnerabilityParser
from cve_connector.vendor_cve.implementation.parsers.vendor_parsers.lenovo_parsers\
    .lenovo_vulnerability_parser import LenovoVulnerabilityParser
from cve_connector.vendor_cve.implementation.parsers.vendor_parsers.microsoft_parsers\
    .microsoft_main_page_parser import MicrosoftExcelMainPageParser
from cve_connector.vendor_cve.implementation.parsers.vendor_parsers.microsoft_parsers\
    .microsoft_vulnerability_parser import MicrosoftVulnerabilityParser
from cve_connector.vendor_cve.implementation.parsers.vendor_parsers.oracle_parsers\
    .oracle_main_page_parser import OracleMainPageParser
from cve_connector.vendor_cve.implementation.parsers.vendor_parsers.oracle_parsers\
    .oracle_vulnerability_parser import OracleVulnerabilityParser
from cve_connector.vendor_cve.implementation.parsers.vendor_parsers.redhat_parsers\
    .red_hat_main_page_parser import RedHatMainPageParser
from cve_connector.vendor_cve.implementation.parsers.vendor_parsers.redhat_parsers\
    .red_hat_vulnerability_parser import RedHatVulnerabilityPageParser
from cve_connector.vendor_cve.implementation.storing_to_db.neo4j_storing import Neo4j
from cve_connector.vendor_cve.implementation.parsers.vendor_parsers.lenovo_parsers\
    .lenovo_main_page_parser import LenovoMainPageParser

CONFIG_PATH = pkg_resources.resource_filename(__name__, "data/conf.ini")
config = configparser.ConfigParser()
config.read(CONFIG_PATH)


# --- Apple functions ---
def parse_apple_vuln(url):
    """
    Parses vulnerabilities in Apple products.

    :param url: URL which contains information about products
    :return: parsed entities
    """
    parser = AppleVulnerabilityParser(url)
    parser.parse()
    return parser.entities


def parse_apple_main(from_date, to_date):
    """
    Parses Apple main page according to the specified date interval.

    :param from_date: start of date interval
    :param to_date:  end of date interval
    :return: parsed entities
    """
    apple_url = config['vendor-cve']['apple_url']
    parser = AppleMainPageParser(apple_url, from_date, to_date)
    parser.parse()
    return parser.entities


def get_apple_entries(from_date, to_date):
    """
    Extract Apple vulnerabilities.

    :param from_date: start of date interval
    :param to_date: end of date interval
    :return: parsed vulnerabilities
    """
    apple_urls = parse_apple_main(from_date, to_date)
    dict_entries = {}
    for url in apple_urls:
        parsed_entries = parse_apple_vuln(url)
        apple_add_to_dict_entities(dict_entries, parsed_entries)
    return list(dict_entries.values())


def apple_add_to_dict_entities(dict_entities, entities):
    """
    Helper function which adds entity (vulnerability) to the dictionary.

    :param dict_entities: dictionary containing vulnerabilities
    :param entities: vulnerabilities
    :return: None
    """
    for entry in entities:
        searched_entry = dict_entities.get(entry.cve_id, None)
        if searched_entry is None:
            dict_entities[entry.cve_id] = entry
        else:
            searched_entry.description = searched_entry.description + entry.description
            searched_entry.published = min(entry.published, searched_entry.published)
            if entry.impact not in searched_entry.impact:
                searched_entry.impact = searched_entry.impact + ' ' + entry.impact
            if entry.description not in searched_entry.description:
                searched_entry.description = searched_entry.description + ' ' + entry.description
            if entry.available_for not in searched_entry.available_for:
                searched_entry.available_for = \
                    searched_entry.available_for + ', ' + entry.available_for


# --- Microsoft functions ---
def parse_microsoft_vuln(cve_id):
    """
    Parses properties of vulnerability.

    :param cve_id: CVE ID
    :return: parsed entities
    """
    url = config['vendor-cve']['microsoft_url'].format(cve_id)
    m_parser = MicrosoftVulnerabilityParser(url)
    m_parser.parse()
    return m_parser.entities


def get_microsoft_ids(ms_directory, from_date, to_date, logger):
    """
    Gets CVE IDs for Microsoft products in the specified date interval.

    :param ms_directory: directory to which files are downloaded
    :param from_date: start of date interval
    :param to_date: end of date interval
    :param logger: logger for logging exceptions inside Microsoft parser class
    :return: parsed CVE IDs
    """
    url_guide = config['vendor-cve']['microsoft_url_guide']
    m_parser = MicrosoftExcelMainPageParser(url_guide, ms_directory, logger, from_date, to_date)
    m_parser.parse()
    return m_parser.entities


def get_microsoft_entries(ms_directory, from_date, to_date, logger):
    """
    Returns list of Microsoft vulnerabilities.

    :param ms_directory: directory to which the files are downloaded
    :param from_date: start of date interval
    :param to_date: end of date interval
    :param logger: logger for logging exceptions inside Microsoft parser class
    :return: parsed vulnerabilities
    """
    ms_entries = []
    ms_cve_ids = get_microsoft_ids(ms_directory, from_date, to_date, logger)
    for cve_id in ms_cve_ids:
        parsed_entries = parse_microsoft_vuln(cve_id)
        ms_entries.extend(parsed_entries)
    return ms_entries


# --- Adobe functions ---

def get_adobe_urls(from_date, to_date):
    """
    Extracts URLs on which the data about CVEs are.

    :param from_date: start of date interval
    :param to_date: end of date interval
    :return: urls
    """
    adobe_url = config['vendor-cve']['adobe_url']
    adobe_parser = AdobeMainPageParser(adobe_url, from_date, to_date)
    adobe_parser.parse()
    return adobe_parser.entities


def parse_adobe_vuln(url):
    """
    Parses Adobe vulnerabilities from URL.

    :param url: URL containing data about vulnerabilities
    :return: parsed content
    """
    adobe_vuln_parser = AdobeVulnerabilityParser(url)
    adobe_vuln_parser.parse()
    return adobe_vuln_parser.entities


def get_adobe_entries(from_date, to_date):
    """
    Returns list of Adobe vulnerabilities within the specified time interval.

    :param from_date: start of date interval
    :param to_date: end of date interval
    :return: parsed vulnerabilities
    """
    entities = []
    urls_to_parse = get_adobe_urls(from_date, to_date)
    for url in urls_to_parse:
        parsed_entries = parse_adobe_vuln(url)
        entities.extend(parsed_entries)
    return entities


# --- Red Hat functions ---

def parse_redhat_vuln(url):
    """
    Parses RedHat vulnerabilities from URL.

    :param url: URL containing data about vulnerabilities
    :return: parsed content
    """
    rh_p = RedHatVulnerabilityPageParser(url)
    rh_p.parse()
    return rh_p.entities


def get_redhat_urls(from_date, to_date, logger):
    """
    Extracts URLs containing data about vulnerabilities within date interval.

    :param from_date: start of date interval
    :param to_date: end of date interval
    :param logger: logger for logging exceptions inside RedHat parser class
    :return: urls
    """
    rh_url = config['vendor-cve']['redhat_url']
    rh_parser = RedHatMainPageParser(rh_url, logger, from_date, to_date)
    rh_parser.parse()
    return rh_parser.entities


def get_redhat_entries(from_date, to_date, logger):
    """
    Parses vulnerabilities within the date interval.

    :param from_date: start of date interval
    :param to_date: end of date interval
    :param logger: logger for logging exceptions inside RedHat parser class
    :return: list of vulnerabilities
    """
    entries = []
    rh_urls = get_redhat_urls(from_date, to_date, logger)
    for url in rh_urls:
        parsed_entries = parse_redhat_vuln(url)
        entries.extend(parsed_entries)
    return entries


# --- Oracle functions ---

def parse_oracle_vuln(url):
    """
    Parses Oracle vulnerabilities from URL.

    :param url: URL containing data about vulnerabilities
    :return: parsed content
    """
    or_p = OracleVulnerabilityParser(url)
    or_p.parse()
    return or_p.entities


def get_oracle_entries(from_date, to_date):
    """
    Parses vulnerabilities within the date interval.

    :param from_date: start of date interval
    :param to_date: end of date interval
    :return: list of vulnerabilities
    """
    dict_entries = {}
    urls_to_get_data = get_oracle_urls(from_date, to_date)
    for url in urls_to_get_data:
        parsed_entries = parse_oracle_vuln(url)
        for entity in parsed_entries:
            add_entity_to_dict_oracle(dict_entries, entity)
    return list(dict_entries.values())


def get_oracle_urls(from_date, to_date):
    """
    Extracts URL on which the data about vulnerabilities are available.

    :param from_date: start of date interval
    :param to_date: end of date interval
    :return: urls
    """
    oracle_url = config['vendor-cve']['oracle_url']
    or_p = OracleMainPageParser(oracle_url, from_date, to_date)
    or_p.parse()
    return or_p.entities


def add_entity_to_dict_oracle(entities_dict, entity_to_add):
    """
    Helper function which adds vulnerability to the dictionary of vulnerabilities.

    :param entities_dict: dictionary of vulnerabilities
    :param entity_to_add: vulnerability to be added
    :return: None
    """
    searched_entry = entities_dict.get(entity_to_add.cve_id, None)
    if searched_entry is None:
        entities_dict[entity_to_add.cve_id] = entity_to_add
    else:
        if entity_to_add.product != '' and entity_to_add.product not in searched_entry.product:
            searched_entry.product = searched_entry.product + ', ' + entity_to_add.product
        if entity_to_add.published is not None:
            searched_entry.published = min(searched_entry.published, entity_to_add.published)
        if entity_to_add.component != '' \
                and entity_to_add.component not in searched_entry.component:
            searched_entry.component = searched_entry.component + ', ' + entity_to_add.component
        if entity_to_add.protocol != '' and entity_to_add.protocol not in searched_entry.protocol:
            searched_entry.protocol = searched_entry.protocol + ', ' + entity_to_add.protocol
        if entity_to_add.remote_exploit_without_auth != '' \
                and entity_to_add.remote_exploit_without_auth not in \
                searched_entry.remote_exploit_without_auth:
            searched_entry.remote_exploit_without_auth = \
                searched_entry.remote_exploit_without_auth \
                + ', ' + entity_to_add.remote_exploit_without_auth
        if entity_to_add.cvss_v3 is not None:
            searched_entry.cvss_v3 = entity_to_add.cvss_v3
        if entity_to_add.cvss_v2 is not None:
            searched_entry.cvss_v2 = entity_to_add.cvss_v2


# --- Android functions ---

def get_android_entries(from_date, to_date):
    """
    Parses vulnerabilities within the date interval.

    :param from_date: start of date interval
    :param to_date: end of date interval
    :return: list of vulnerabilities
    """
    entities = []
    urls_to_parse = get_android_urls(from_date, to_date)
    for url in urls_to_parse:
        parsed_entries = parse_android_vuln(url)
        entities.extend(parsed_entries)
    return entities


def get_android_urls(from_date, to_date):
    """
    Extracts URL on which the data about vulnerabilities are available.

    :param from_date: start of date interval
    :param to_date: end of date interval
    :return: urls
    """
    urls = []
    android_url = config['vendor-cve']['android_url']
    pixel_nexus_url = config['vendor-cve']['pixel_nexus_url']
    andr_parser = AndroidMainPageParser(android_url, from_date, to_date)
    pixel_parser = AndroidMainPageParser(pixel_nexus_url, from_date, to_date)
    andr_parser.parse()
    pixel_parser.parse()
    for url in andr_parser.entities + pixel_parser.entities:
        urls.append("https://source.android.com" + url)
    return urls


def parse_android_vuln(url):
    """
    Parses Android vulnerabilities from URL.

    :param url: URL containing data about vulnerabilities
    :return: parsed content
    """
    parser = AndroidVulnerabilityParser(url)
    parser.parse()
    return parser.entities


# --- Cisco functions ---

def get_cisco_entries(from_date, to_date, logger):
    """
    Parses vulnerabilities within the date interval.

    :param from_date: start of date interval
    :param to_date: end of date interval
    :param logger: logger for logging exceptions inside Cisco parser class
    :return: list of vulnerabilities
    """
    entries = []
    cisco_urls = get_cisco_urls(from_date, to_date, logger)
    for url in cisco_urls:
        cisco_v_p = CiscoVulnerabilityParser(url, logger)
        cisco_v_p.parse()
        entries.extend(cisco_v_p.entities)
    return entries


def get_cisco_urls(from_date, to_date, logger):
    """
    Extracts URL on which the data about vulnerabilities are available.

    :param from_date: start of date interval
    :param to_date: end of date interval
    :param logger: logger for logging exceptions inside Cisco parser class
    :return: urls
    """
    cisco_url = config['vendor-cve']['cisco_url']
    cisco_p = CiscoMainPageParser(cisco_url, logger, from_date, to_date)
    cisco_p.parse()
    return cisco_p.entities


def parse_cisco_vuln(url, logger):
    """
    Parses Cisco vulnerabilities from URL.

    :param url: URL containing data about vulnerabilities
    :param logger: logger for logging exceptions inside Cisco parser class
    :return: parsed content
    """
    cisco_v_p = CiscoVulnerabilityParser(url, logger)
    cisco_v_p.parse()
    return cisco_v_p.entities


# --- Lenovo functions ---
def get_lenovo_entries(from_date, to_date):
    """
    Parses vulnerabilities within the date interval.

    :param from_date: start of date interval
    :param to_date: end of date interval
    :return: list of vulnerabilities
    """
    dict_entries = {}
    urls_to_parse = get_lenovo_urls(from_date, to_date)
    for url in urls_to_parse:
        parsed_entries = parse_lenovo_vuln(url)
        add_entities_to_dict_lenovo(dict_entries, parsed_entries)
    return list(dict_entries.values())


def parse_lenovo_vuln(url):
    """
    Parses Android vulnerabilities from URL.

    :param url: URL containing data about vulnerabilities
    :return: parsed content
    """
    len_parser = LenovoVulnerabilityParser(url)
    len_parser.parse()
    return len_parser.entities


def get_lenovo_urls(from_date, to_date):
    """
    Extracts URL on which the data about vulnerabilities are available.

    :param from_date: start of date interval
    :param to_date: end of date interval
    :return: urls
    """
    lenovo_url = config['vendor-cve']['lenovo_url']
    len_p = LenovoMainPageParser(lenovo_url, from_date, to_date)
    len_p.parse()
    return len_p.entities


def add_entities_to_dict_lenovo(dict_entities, entries):
    """
    Helper function which adds vulnerability to the dictionary of vulnerabilities.

    :param dict_entities: dictionary of vulnerabilities
    :param entries: vulnerability to be added
    :return: None
    """
    for entry in entries:
        searched_entry = dict_entities.get(entry.cve_id, None)
        if searched_entry is None:
            dict_entities[entry.cve_id] = entry
        else:
            searched_entry.description = searched_entry.description + ' ' + entry.description
            searched_entry.published = min(entry.published, searched_entry.published)


# ==================================================


def save_to_db(neo4j_db, entries, logger):
    """
    Stores CVE data to neo4j database.

    :param neo4j_db: instance of class Neo4j (neo4j_storing.py) which implements method
    store_data_from_vendor
    :param entries: list of vulnerabilities
    :param logger: logger for the method
    :return: number of CVE nodes created in the database
    """
    return neo4j_db.store_data_from_vendor(entries, logger)


def add_vendor_cves(ms_directory, neo4jpasswd, logger=structlog.get_logger()):
    """
    Adds CVEs to database.

    :param ms_directory: directory where the microsoft files are downloaded
    :param neo4jpasswd: password to neo4j database
    :param logger: logger for the method
    :return: output message
    """
    logger.info("Method add_vendor_CVEs was called with parameter: " +
                "ms_directory={}"
                .format(ms_directory))
    neo4j = Neo4j(neo4jpasswd)

    from_date = date.today() - timedelta(days=14)
    to_date = date.today()
    count = {'Adobe': 0, 'Android': 0, 'Apple': 0, 'Cisco': 0, 'Lenovo': 0,
             'Microsoft': 0, 'Oracle': 0, 'RedHat': 0}

    try:
        logger.debug("Microsoft")
        microsoft_entries = get_microsoft_entries(ms_directory, from_date, to_date, logger)
        count['Microsoft'] = save_to_db(neo4j, microsoft_entries, logger)
    except (ValueError, AttributeError, TimeoutError, IndexError,
            WebDriverException, TypeError, HTTPError) as err:
        logger.error("Error while parsing Microsoft data: " + str(err))
        logger.error(traceback.format_exc())

    try:
        logger.debug("Apple")
        apple_entries = get_apple_entries(from_date, to_date)
        count['Apple'] = save_to_db(neo4j, apple_entries, logger)
    except (ValueError, AttributeError, TimeoutError, IndexError,
            WebDriverException, TypeError, HTTPError) as err:
        logger.error("Error while parsing Apple data: " + str(err))
        logger.error(traceback.format_exc())

    try:
        logger.debug("Adobe")
        adobe_entries = get_adobe_entries(from_date, to_date)
        count['Adobe'] = save_to_db(neo4j, adobe_entries, logger)
    except (ValueError, AttributeError, TimeoutError, IndexError,
            WebDriverException, TypeError, HTTPError) as err:
        logger.error("Error while parsing Adobe data: " + str(err))
        logger.error(traceback.format_exc())

    try:
        logger.debug("RedHat")
        redhat_entries = get_redhat_entries(from_date, to_date, logger)
        count['RedHat'] = save_to_db(neo4j, redhat_entries, logger)
    except (ValueError, AttributeError, TimeoutError, IndexError,
            WebDriverException, TypeError, HTTPError) as err:
        logger.error("Error while parsing RedHat data: " + str(err))
        logger.error(traceback.format_exc())

    try:
        logger.debug("Oracle")
        oracle_entries = get_oracle_entries(from_date, to_date)
        count['Oracle'] = save_to_db(neo4j, oracle_entries, logger)
    except (ValueError, AttributeError, TimeoutError, IndexError,
            WebDriverException, TypeError, HTTPError) as err:
        logger.error("Error while parsing Oracle data: " + str(err))
        logger.error(traceback.format_exc())

    try:
        logger.debug("Android")
        android_entries = get_android_entries(from_date, to_date)
        count['Android'] = save_to_db(neo4j, android_entries, logger)
    except (ValueError, AttributeError, TimeoutError, IndexError,
            WebDriverException, TypeError, HTTPError) as err:
        logger.error("Error while parsing Android data: " + str(err))
        logger.error(traceback.format_exc())

    try:
        logger.debug("Cisco")
        cisco_entries = get_cisco_entries(from_date, to_date, logger)
        count['Cisco'] = save_to_db(neo4j, cisco_entries, logger)
    except (ValueError, AttributeError, TimeoutError, IndexError,
            WebDriverException, TypeError, HTTPError) as err:
        logger.error("Error while parsing Cisco data: " + str(err))
        logger.error(traceback.format_exc())

    try:
        logger.debug("Lenovo")
        lenovo_entries = get_lenovo_entries(from_date, to_date)
        count['Lenovo'] = save_to_db(neo4j, lenovo_entries, logger)
    except (ValueError, AttributeError, TimeoutError, IndexError,
            WebDriverException, TypeError, HTTPError) as err:
        logger.error("Error while parsing Lenovo data: " + str(err))
        logger.error(traceback.format_exc())

    result = ""
    for key in count:
        result += "{}: {} updated CVEs. ".format(key, count[key])

    logger.info(result)
    return result
