from dataclasses import dataclass, field
from typing import List, Dict, Optional, Any

from dataclasses_json import dataclass_json, Undefined

from helpers.md_and_html_convertor import MdAndHTMLConvertor
from pwndoc_custom_fields import PwndocCustomFields
from loguru import logger

HTML_ATTRIBUTES = ['description', 'observation', 'remediation', 'poc']


@dataclass_json(undefined=Undefined.EXCLUDE)  # todo: maybe reconsider? we're throwing away duplicated stuff
@dataclass
class TemplatePwndoc:
    locale: str = "og"

    title: str = ""
    description: str = ''
    observation: str = ''
    remediation: str = ''
    references: List[str] = field(default_factory=list)

    hosts: List[str] = field(default_factory=list)  # only in finding, not in template
    poc: str = ''

    _customFields = PwndocCustomFields()
    _is_template: bool = False

    def set_is_template(self, val: bool):
        self._is_template = val

    def get_is_template(self) -> bool:
        return self._is_template

    def to_pwndoc_html(self):
        for html_key in HTML_ATTRIBUTES:
            og_val = getattr(self, html_key)
            if og_val is None:
                continue

            new_val = MdAndHTMLConvertor.custom_md_to_pwndoc_html(og_val)  # todo: maybe just use html -> pwndoc html
            setattr(self, html_key, new_val)

    def to_pwndoc_dict(self) -> Dict[str, Any]:
        if self._is_template:
            self.remove_finding_specific_data()

        answer = {
            "title": self.title,
            "description": self.description,
            "remediation": self.remediation,
            "references": self.references,
            "observation": self.observation,
            "locale": self.locale,
            "category": "VulnCategory1",

            # These are not in 'Templates' but only in 'Findings'
            "poc": self.poc,
            "scope": "<br>".join(self.hosts),

            "customFields": self._customFields.get_pwndoc_repr(),
        }

        return answer

    def remove_finding_specific_data(self):
        if not self._is_template:
            logger.warning("Removing finding specific data from a Finding? _is_template is not set.")
        self.poc = ''
        self.hosts = []


@dataclass
class MultilocaleTemplateWrapperPwndoc:
    status: int = 0
    _id: Optional[str] = None
    cvssv3: Optional[str] = None
    category: str = "VulnCategory1"
    details: List[TemplatePwndoc] = field(default_factory=list)

    def get_fid(self) -> Optional[str]:
        return self.details[0].fid if len(self.details) else None

    def get_pwndoc_repr(self):
        answer = {
            'status': self.status,
            '_id': self._id,
            'cvssv3': self.cvssv3,
            'category': self.category,
            'details': [x.to_pwndoc_dict() for x in self.details]
        }
        return answer


@dataclass
class TNSTemplatePwndoc(TemplatePwndoc):
    fid: str = ""

    # severity_color: str = "#4a86e8"  # blue
    severity_label: str = ""
    attack_complexicity_label: str = ""
    system_impact_label: str = ""
    author: str = ""
    ignore_pluginoutput_checkbox: List[str] = field(default_factory=list)

    def add_pwndoc_custom_fields(self):
        custom_fields = self._customFields

        custom_fields.set_field("fid", self.fid)

        custom_fields.set_field("severity_label", self.severity_label)
        custom_fields.set_field("attack_complexicity_label", self.attack_complexicity_label)
        custom_fields.set_field("system_impact_label", self.system_impact_label)
        custom_fields.set_field("ignore_pluginoutput_checkbox", self.ignore_pluginoutput_checkbox)

        if self._is_template:
            custom_fields.set_field("author", self.author)

    def set_custom_field(self, field_name: str, field_value: str):
        self._customFields.set_field(field_name, field_value)

    def to_pwndoc_dict(self) -> Dict[str, Any]:
        self.add_pwndoc_custom_fields()
        answer = super().to_pwndoc_dict()

        fake_csvs = FakeCVSS.severity_label_to_fake_cvss(self.severity_label, self.locale)
        if fake_csvs:
            answer['cvssv3'] = fake_csvs

        return answer


class FakeCVSS:
    # Note that this same code is inside PwnDoc UI
    @staticmethod
    def unifiedSeverityLevels() -> List[str]:
        return ['none', 'low', 'medium', 'high', 'critical']

    @classmethod
    def unifiedSeverityToEquivalentCVSSString(cls, unified_severity_string: str) -> str:
        levels = {
            'critical': "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H",
            'high':     "CVSS:3.1/AV:P/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H",
            'medium':   "CVSS:3.1/AV:P/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H",
            'low':      "CVSS:3.1/AV:P/AC:H/PR:H/UI:R/S:U/C:N/I:L/A:N",
            'none':     "CVSS:3.1/AV:P/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:N",
        }
        return levels[unified_severity_string]

    @classmethod
    def severity_label_to_fake_cvss(cls, severity: str, locale: str) -> Optional[str]:
        try:
            severity_index = PwndocCustomFields.get_possible_values_for_select_field('severity_label', locale).index(severity)
            severity_level = cls.unifiedSeverityLevels()[severity_index]
            fake_cvss = cls.unifiedSeverityToEquivalentCVSSString(severity_level)
            return fake_cvss
        except:
            logger.warning("Not able to get fake CVSS - unknown severity_label value.")



def get_fid_from_raw_finding(finding: dict) -> Optional[str]:
    fid_pwndoc_custom_field_id = PwndocCustomFields.get_custom_field_by_label("fid").get("_id", "")

    custom_fields = finding.get("customFields", [])
    for single_field in custom_fields:
        try:
            field_id = single_field.get("customField", {}).get("_id", "")
        except AttributeError:
            field_id = single_field.get("customField", "")
        if field_id == fid_pwndoc_custom_field_id:
            return single_field.get("text")
    return None
