import json
from typing import Optional

from intelmq.lib.bot import ExpertBot
from intelmq.lib.mixins import CacheMixin
from intelmq.lib.utils import create_request_session

CACHE_FORMAT = "vuln:{identifier}"
CACHE_NOT_FOUND = "VULN_NOT_FOUND"


class VulnerabilityLookupExpertBot(ExpertBot, CacheMixin):
    url = "https://vulnerability.circl.lu"
    vulnerability_field = "classification.identifier"
    description_length = 500
    overwrite = False

    filter_classification_type = ["vulnerable-system"]

    redis_cache_ttl = 86400  # 1 day

    def init(self):
        self.set_request_parameters()
        self.http_session = create_request_session(self)

    def _get_vulnerability_data(self, vuln_id: Optional[str]) -> Optional[dict]:
        vuln_id = (vuln_id or "").strip().lower()
        if not vuln_id:
            return None

        cache_key = CACHE_FORMAT.format(identifier=vuln_id)
        cached_data = self.cache_get(cache_key)
        if cached_data:
            if cached_data == CACHE_NOT_FOUND:
                return None
            return json.loads(cached_data)

        response = self.http_session.get(f"{self.url}/api/vulnerability/{vuln_id}")
        if response.status_code != 200:
            response.raise_for_status()

        vuln_raw_data = response.json()
        if response.status_code == 404 or not vuln_raw_data:
            self.cache_set(cache_key, CACHE_NOT_FOUND)
            return None

        epss = None
        response = self.http_session.get(f"{self.url}/api/epss/{vuln_id}")
        if response.status_code == 200:
            epss_data = response.json().get("data", [])
            if epss_data:
                epss = epss_data[0].get("epss", None)
        else:
            self.logger.info(
                "Cannot get EPSS score, status code: %d", response.status_code
            )

        vuln_data = {"url": f"{self.url}/vuln/{vuln_id}"}
        if epss:
            vuln_data["epss"] = epss

        description = None
        cvss3_1 = None
        cvss3_0 = None
        cvss4_0 = None

        # Every source queried by Vulnerability Lookup provides different data format

        # CVE records
        if vuln_raw_data.get("dataType", "") == "CVE_RECORD":
            cna = vuln_raw_data.get("containers", {}).get("cna")
            if cna:
                for item in cna.get("descriptions", []):
                    if item.get("lang", "") in ["en"]:
                        description = item.get("value", "")
                        break

                for item in cna.get("metrics", []):
                    if "cvssV3_1" in item:
                        cvss3_1 = item["cvssV3_1"].get("baseScore")
                    if "cvssV3_0" in item:
                        cvss3_0 = item["cvssV3_0"].get("baseScore")
                    if "cvssV4_0" in item:
                        cvss4_0 = item["cvssV4_0"].get("baseScore")

        # GitHub Security Advisories
        if vuln_id.startswith("ghsa-"):
            description = vuln_raw_data.get("details")
            # TODO: Calculate CVSS

        if description:
            # Some CSV readers do not understand multi-line texts
            description = description.replace("\n", " ")
            vuln_data["description"] = description[: self.description_length]
            if len(description) > self.description_length:
                vuln_data["description"] += "..."

        if cvss3_0:
            vuln_data["cvss3_0"] = cvss3_0

        if cvss3_1:
            vuln_data["cvss3_1"] = cvss3_1

        if cvss4_0:
            vuln_data["cvss4_0"] = cvss4_0

        self.cache_set(cache_key, json.dumps(vuln_data))
        return vuln_data

    def process(self):
        event = self.receive_message()

        if (
            not self.filter_classification_type
            or event.get("classification.type") in self.filter_classification_type
        ):
            vuln_id = event.get(self.vulnerability_field)
            vuln_data = self._get_vulnerability_data(vuln_id) or {}

            if description := vuln_data.get("description"):
                event.add(
                    "event_description.text", description, overwrite=self.overwrite
                )

            if url := vuln_data.get("url"):
                event.add("event_description.url", url, overwrite=self.overwrite)

            for score_type in ["cvss3_0", "cvss3_1", "cvss4_0", "epss"]:
                if score := vuln_data.get(score_type):
                    event.add(f"extra.{score_type}", score, overwrite=self.overwrite)

        self.send_message(event)
        self.acknowledge_message()


BOT = VulnerabilityLookupExpertBot
