# SPDX-License-Identifier: LGPL-2.0-or-later
# SPDX-FileCopyrightText: 2024 Ics inc.
# SPDX-FileContributor: Michael Dingwall <mdingwall@ics.com>
# SPDX-FileContributor: Gerardo Stola <gstola@ics.com>

from datetime import datetime, tzinfo, timedelta
import textwrap

import rich.padding
from rich.text import Text
from rich.color import Color
from rich.style import Style


class SimpleUtc(tzinfo):
    """
    derived from
    https://stackoverflow.com/questions/19654578/
       python-utc-datetime-objects-iso-format-doesnt-include-z-zulu-or-zero-offset
    """

    def tzname(self, **kwargs):
        return "UTC"

    def utcoffset(self, dt):
        return timedelta(0)

    def dst(self, __dt):
        return 0


class SeverityProperties:
    priority: int
    style: str | Style
    indicator: str

    def __init__(self, priority: int, style: str | Style, indicator: str):
        self.priority = priority
        self.style = style
        self.indicator = indicator

    def __lt__(self, other):
        if isinstance(other, SeverityProperties):
            return self.priority < other.priority

        return False

    def __gt__(self, other):
        if isinstance(other, SeverityProperties):
            return self.priority > other.priority

        return False

    def __le__(self, other):
        if isinstance(other, SeverityProperties):
            return self.priority <= other.priority

        return False

    def __ge__(self, other):
        if isinstance(other, SeverityProperties):
            return self.priority >= other.priority

        return False

    def __eq__(self, other):
        if isinstance(other, SeverityProperties):
            return self.priority == other.priority

        return False


vulnerability_styles = {
    "NONE": SeverityProperties(4, "green", "🟢"),
    "LOW": SeverityProperties(3, "yellow", "🟡"),
    "MEDIUM": SeverityProperties(2, Style(color=Color.from_rgb(255.0, 165.0, 0.0)), "🟠"),
    "HIGH": SeverityProperties(1, "red", "🔴"),
    "CRITICAL": SeverityProperties(0, "magenta", "🟣"),
}


class Vulnerability:
    cve_number: str
    last_modified: datetime
    severity: str
    score: float
    cvss_vector: str
    cvss_version: float
    source: str
    description: str
    cwes: list

    def __init__(self, sql_list=None, cwe_list=None):
        if sql_list is None:
            self.cve_number = ""
            self.last_modified = datetime.utcnow()
            self.severity = "NONE"
            self.score = 0
            self.cvss_vector = ""
            self.cvss_version = 0
            self.source = ""
            self.description = ""
        else:
            self.cve_number = sql_list[0]
            self.last_modified = datetime.fromisoformat(sql_list[2])
            self.severity = sql_list[3]
            self.score = float(sql_list[4])
            self.cvss_vector = sql_list[5]
            self.cvss_version = float(sql_list[6])
            self.source = sql_list[7]
            self.description = sql_list[1]

        self.last_modified = self.last_modified.replace(tzinfo=SimpleUtc())

        if cwe_list is None:
            self.cwes = []
        else:
            self.cwes = cwe_list

    def __str__(self):
        return self.rich().plain

    def rich(self):
        column_fmt_str = "{:<14}"

        def format_string(prop: str, value: str = ""):
            return Text.assemble(
                (f"{column_fmt_str.format(prop)}", "bold cyan"), ("  :  ", "yellow")
            ) + Text.from_markup(value)

        name = format_string("CVE")
        column_spacer = ("{:<" + f"{name.cell_len}" + "}").format(" ")
        rep = format_string("CVE", f"{self.generate_cve_link_text()}\n")
        rep += format_string("Last Modified", f"{self.last_modified}\n")
        rep += format_string("Severity", f"[{vulnerability_styles[self.severity].style}]{self.severity}[/]\n")
        rep += format_string("Score", f"[{vulnerability_styles[self.severity].style}]{self.score}[/]\n")
        rep += format_string("CVSS Vector", f"{self.cvss_vector}\n")
        rep += format_string("CVSS Version", f"{self.cvss_version}\n")
        rep += format_string("Source", f"{self.source}\n")
        rep += (
            format_string("Description")
            + textwrap.fill(
                self.description, width=120, initial_indent=column_spacer, subsequent_indent=column_spacer
            ).strip()
            + "\n"
        )
        if self.cwes and len(self.cwes) > 0:
            rep += format_string("CWEs")
            formatted_cwes = list()
            for cwe in self.cwes:
                formatted_cwes.append(
                    f"[link=https://cwe.mitre.org/data/definitions/{cwe.split('-')[1]}.html]{cwe}[/link]"
                )
            rep += Text.from_markup(f"\n{column_spacer}".join(formatted_cwes))

        return rep

    def __rich_repr__(self):
        return self.rich()

    def generate_cve_link_text(self):
        vulnerability_color: Color
        if isinstance(vulnerability_styles[self.severity].style, str):
            vulnerability_color = vulnerability_styles[self.severity].style
        else:
            vulnerability_color = vulnerability_styles[self.severity].style.color

        cve_link_style = Style(color=vulnerability_color)
        cve_link_style.update_link(f"https://nvd.nist.gov/vuln/detail/{self.cve_number}")
        cve_link = rich.text.Text(
            f"[link=https://nvd.nist.gov/vuln/detail/{self.cve_number}]{self.cve_number}[/link]",
            style=vulnerability_styles[self.severity].style,
        )
        cve_link.pad_right(17 - len(self.cve_number))
        return cve_link

    def __lt__(self, other):
        if isinstance(other, Vulnerability):
            if vulnerability_styles[self.severity] != vulnerability_styles[other.severity]:
                return vulnerability_styles[self.severity] < vulnerability_styles[other.severity]

            return self.cve_number < other.cve_number

        if isinstance(other, str):
            return self.cve_number < other

        return False

    def __gt__(self, other):
        if isinstance(other, Vulnerability):
            if vulnerability_styles[self.severity] != vulnerability_styles[other.severity]:
                return vulnerability_styles[self.severity] > vulnerability_styles[other.severity]

            return self.cve_number > other.cve_number

        if isinstance(other, str):
            return self.cve_number > other

        return False

    def __le__(self, other):
        if isinstance(other, Vulnerability):
            if vulnerability_styles[self.severity] != vulnerability_styles[other.severity]:
                return vulnerability_styles[self.severity] <= vulnerability_styles[other.severity]

            return self.cve_number <= other.cve_number

        if isinstance(other, str):
            return self.cve_number <= other

        return False

    def __ge__(self, other):
        if isinstance(other, Vulnerability):
            if vulnerability_styles[self.severity] != vulnerability_styles[other.severity]:
                return vulnerability_styles[self.severity] >= vulnerability_styles[other.severity]

            return self.cve_number >= other.cve_number

        if isinstance(other, str):
            return self.cve_number >= other

        return False

    def __eq__(self, other):
        if isinstance(other, Vulnerability):
            if vulnerability_styles[self.severity] != vulnerability_styles[other.severity]:
                return False

            return self.cve_number == other.cve_number

        if isinstance(other, str):
            return self.cve_number == other

        return False

    @staticmethod
    def sql_query_name_list():
        return [
            "cve_number",
            "description",
            "last_modified",
            "severity",
            "score",
            "cvss_vector",
            "cvss_version",
            "data_source",
        ]
