#
# Copyright (c) nexB Inc. and others. All rights reserved.
# VulnerableCode is a trademark of nexB Inc.
# SPDX-License-Identifier: Apache-2.0
# See http://www.apache.org/licenses/LICENSE-2.0 for the license text.
# See https://github.com/nexB/vulnerablecode for support or download.
# See https://aboutcode.org for more information about nexB OSS projects.
#

import asyncio
import urllib

import requests
from bs4 import BeautifulSoup
from packageurl import PackageURL
from univers.version_range import VersionRange
from univers.versions import SemverVersion

from vulnerabilities.importer import AdvisoryData
from vulnerabilities.importer import Importer
from vulnerabilities.importer import Reference
from vulnerabilities.importer import VulnerabilitySeverity
from vulnerabilities.package_managers import GitHubTagsAPI
from vulnerabilities.severity_systems import APACHE_HTTPD
from vulnerabilities.utils import nearest_patched_package


class ApacheHTTPDImporter(Importer):

    base_url = "https://httpd.apache.org/security/json/"

    def set_api(self):
        self.version_api = GitHubTagsAPI()
        asyncio.run(self.version_api.load_api(["apache/httpd"]))
        self.version_api.cache["apache/httpd"] = set(
            filter(
                lambda version: version.value not in ignore_tags,
                self.version_api.cache["apache/httpd"],
            )
        )

    def updated_advisories(self):
        links = fetch_links(self.base_url)
        self.set_api()
        advisories = []
        for link in links:
            data = requests.get(link).json()
            advisories.append(self.to_advisory(data))
        return self.batch_advisories(advisories)

    def to_advisory(self, data):
        cve = data["CVE_data_meta"]["ID"]
        descriptions = data["description"]["description_data"]
        description = None
        for desc in descriptions:
            if desc["lang"] == "eng":
                description = desc.get("value")
                break

        severities = []
        impacts = data.get("impact", [])
        for impact in impacts:
            value = impact.get("other")
            if value:
                severities.append(
                    VulnerabilitySeverity(
                        system=APACHE_HTTPD,
                        value=value,
                    )
                )
                break
        reference = Reference(
            reference_id=cve,
            url=urllib.parse.urljoin(self.base_url, f"{cve}.json"),
            severities=severities,
        )

        versions_data = []
        for vendor in data["affects"]["vendor"]["vendor_data"]:
            for products in vendor["product"]["product_data"]:
                for version_data in products["version"]["version_data"]:
                    versions_data.append(version_data)

        fixed_version_ranges, affected_version_ranges = self.to_version_ranges(versions_data)

        affected_packages = []
        fixed_packages = []

        for version_range in fixed_version_ranges:
            fixed_packages.extend(
                [
                    PackageURL(type="apache", name="httpd", version=version)
                    for version in self.version_api.get("apache/httpd").valid_versions
                    if SemverVersion(version) in version_range
                ]
            )

        for version_range in affected_version_ranges:
            affected_packages.extend(
                [
                    PackageURL(type="apache", name="httpd", version=version)
                    for version in self.version_api.get("apache/httpd").valid_versions
                    if SemverVersion(version) in version_range
                ]
            )

        return AdvisoryData(
            vulnerability_id=cve,
            summary=description,
            affected_packages=nearest_patched_package(affected_packages, fixed_packages),
            references=[reference],
        )

    def to_version_ranges(self, versions_data):
        fixed_version_ranges = []
        affected_version_ranges = []
        for version_data in versions_data:
            version_value = version_data["version_value"]
            range_expression = version_data["version_affected"]
            if range_expression == "<":
                fixed_version_ranges.append(
                    VersionRange.from_scheme_version_spec_string(
                        "semver", ">={}".format(version_value)
                    )
                )
            elif range_expression == "=" or range_expression == "?=":
                affected_version_ranges.append(
                    VersionRange.from_scheme_version_spec_string(
                        "semver", "{}".format(version_value)
                    )
                )

        return (fixed_version_ranges, affected_version_ranges)


def fetch_links(url):
    links = []
    data = requests.get(url).content
    soup = BeautifulSoup(data, features="lxml")
    for tag in soup.find_all("a"):
        link = tag.get("href")
        if not link.endswith("json"):
            continue
        links.append(urllib.parse.urljoin(url, link))
    return links


ignore_tags = {
    "AGB_BEFORE_AAA_CHANGES",
    "APACHE_1_2b1",
    "APACHE_1_2b10",
    "APACHE_1_2b11",
    "APACHE_1_2b2",
    "APACHE_1_2b3",
    "APACHE_1_2b4",
    "APACHE_1_2b5",
    "APACHE_1_2b6",
    "APACHE_1_2b7",
    "APACHE_1_2b8",
    "APACHE_1_2b9",
    "APACHE_1_3_PRE_NT",
    "APACHE_1_3a1",
    "APACHE_1_3b1",
    "APACHE_1_3b2",
    "APACHE_1_3b3",
    "APACHE_1_3b5",
    "APACHE_1_3b6",
    "APACHE_1_3b7",
    "APACHE_2_0_2001_02_09",
    "APACHE_2_0_52_WROWE_RC1",
    "APACHE_2_0_ALPHA",
    "APACHE_2_0_ALPHA_2",
    "APACHE_2_0_ALPHA_3",
    "APACHE_2_0_ALPHA_4",
    "APACHE_2_0_ALPHA_5",
    "APACHE_2_0_ALPHA_6",
    "APACHE_2_0_ALPHA_7",
    "APACHE_2_0_ALPHA_8",
    "APACHE_2_0_ALPHA_9",
    "APACHE_2_0_BETA_CANDIDATE_1",
    "APACHE_BIG_SYMBOL_RENAME_POST",
    "APACHE_BIG_SYMBOL_RENAME_PRE",
    "CHANGES",
    "HTTPD_LDAP_1_0_0",
    "INITIAL",
    "MOD_SSL_2_8_3",
    "PCRE_3_9",
    "POST_APR_SPLIT",
    "PRE_APR_CHANGES",
    "STRIKER_2_0_51_RC1",
    "STRIKER_2_0_51_RC2",
    "STRIKER_2_1_0_RC1",
    "WROWE_2_0_43_PRE1",
    "apache-1_3-merge-1-post",
    "apache-1_3-merge-1-pre",
    "apache-1_3-merge-2-post",
    "apache-1_3-merge-2-pre",
    "apache-apr-merge-3",
    "apache-doc-split-01",
    "dg_last_1_2_doc_merge",
    "djg-apache-nspr-07",
    "djg_nspr_split",
    "moving_to_httpd_module",
    "mpm-3",
    "mpm-merge-1",
    "mpm-merge-2",
    "post_ajp_proxy",
    "pre_ajp_proxy",
}
