import copy
from typing import List, Dict, Optional


import config
from helpers.md_and_html_convertor import MdAndHTMLConvertorLowLevel, MdAndHTMLConvertor
from pwndoc_custom_fields import PwndocCustomFields
from template_pwndoc import TNSTemplatePwndoc, HTML_ATTRIBUTES
from template_manager import PwndocTemplateManager
from template_scan2report import TemplateScan2Report, str_bool_to_bool

from loguru import logger

SCAN2REPORT_TO_PWNDOC_MAPPING = {
    "fid": "fid",
    "locale": "locale",
    "name": "title",
    "severity": "severity_label",
    "attack_complexicity": "attack_complexicity_label",
    "system_impact": "system_impact_label",
    "description": "description",
    "impact": "observation",
    "recommendations": "remediation",
    "links": "references",
    "author": "author",
    "hosts": "hosts",
    "ignore_pluginoutput": "ignore_pluginoutput_checkbox",
}
PWNDOC_TO_SCAN2REPORT_MAPPING = {v: k for k, v in SCAN2REPORT_TO_PWNDOC_MAPPING.items()}


class Scan2ReportConvert:
    @staticmethod
    def convert_single_finding_to_tns_pwndoc(s2r_template: TemplateScan2Report, locale: str) -> TNSTemplatePwndoc:
        tmp = TNSTemplatePwndoc()

        for scan2report_tag, pwndoc_tag in SCAN2REPORT_TO_PWNDOC_MAPPING.items():
            scan2report_val = getattr(s2r_template, scan2report_tag)

            if pwndoc_tag.endswith("_label") and isinstance(scan2report_val, int):
                scan2report_val = PwndocCustomFields.select_field_scan2report_to_pwndoc(pwndoc_tag, locale, scan2report_val)

            if pwndoc_tag.endswith("_checkbox"):
                # todo: maybe don't use suffixes, but types of custom fields?
                # todo: checkbox is list of strings. Maybe write it more clearly?
                true_val: List[str] = PwndocCustomFields.get_possible_values_for_select_field(pwndoc_tag, locale)
                scan2report_val = true_val if str_bool_to_bool(scan2report_val) else []

            setattr(tmp, pwndoc_tag, scan2report_val)

        # todo: check if any other attributes are missing
        tmp.fid = s2r_template.fid
        tmp.locale = locale

        if tmp.title == "":
            tmp.title = f'AUTOGENERATED_TITLE: {tmp.fid}'

        description_parts = tmp.description.split(config.TNS_DESCRIPTION_PROOF_DELIMITER, 1)
        if len(description_parts) == 2:
            general_description, scan_specific_description = description_parts
            tmp.description = general_description
            tmp.poc = scan_specific_description.lstrip("=")

        return tmp


class PwndocConverter:
    @classmethod
    def convert_to_list_of_scan2report_combined_dict(cls, output_without_html_tags: bool = False, locale: str = config.LOCALES[0]) -> List[dict]:
        # Note that this uses DB. Make sure to update it before running this.
        template_storage = PwndocTemplateManager.get_all_templates_as_dict_of_locales()
        for fid in template_storage:
            for single_locale in template_storage[fid]:
                template_storage[fid][single_locale] = cls.html_conversion(
                    template_storage[fid][single_locale],
                    html_to_md=True,
                    output_without_html_tags=output_without_html_tags
                )

        answer: List[dict] = []

        for fid in template_storage:
            localized_version = template_storage[fid].get(locale)
            # og_version = template_storage[fid].get("og")

            finding_dict_combined = {}

            if localized_version:
                finding_dict_with_lang = PwndocConverter._convert_to_single_scan2report_dict(localized_version, output_as_original=False)
                finding_dict_combined.update(finding_dict_with_lang)
            #     if og_version:
            #         finding_dict_with_og = PwndocConverter._convert_to_single_scan2report_dict(localized_version, output_as_original=True)
            #         finding_dict_combined.update(finding_dict_with_og)
            #
            # elif og_version:  # If localized version doesn't exist, try to use the OG version.
            #     # todo: maybe note this situation in the title?
            #     finding_dict_with_og = PwndocConverter._convert_to_single_scan2report_dict(og_version, output_as_original=False)
            #     finding_dict_combined.update(finding_dict_with_og)

            else:
                # todo: maybe rather use any existing template?
                logger.debug(f"For finding with fid {fid} there isn't appropriate localized ({locale}) template.")

            if len(finding_dict_combined):
                answer.append(finding_dict_combined)

        return answer

    @staticmethod
    def html_conversion(
            template: TNSTemplatePwndoc,
            html_to_md: bool = True,
            output_without_html_tags: bool = False,
            html_force_unescape_basic_tags: bool = False
    ) -> TNSTemplatePwndoc:

        if output_without_html_tags:
            assert html_to_md

        converted_template = copy.deepcopy(template)
        for attr_name in HTML_ATTRIBUTES:
            val = getattr(converted_template, attr_name, "")
            if html_force_unescape_basic_tags:
                # todo: validate that this low-level call is not duplicated
                val = MdAndHTMLConvertorLowLevel.force_unescape_basic_tags(val)
            if html_to_md:
                if output_without_html_tags:
                    val = MdAndHTMLConvertor.pwndoc_html_to_md_native(val)
                else:
                    val = MdAndHTMLConvertor.html_to_custom_pwndoc_html(val)
            setattr(converted_template, attr_name, val)
        return converted_template

    @staticmethod
    def _convert_to_single_scan2report_dict(finding: TNSTemplatePwndoc, output_as_original: bool = False) -> dict:
        answer = {}
        finding_dict = finding.to_dict()

        # todo: answer is missing some fields. Check if finding_dict could be used directly.

        for key in list(finding_dict.keys()):
            if not (key.endswith("_label") and isinstance(finding_dict[key], str)):
                continue

            value_text = finding_dict[key].strip()
            if len(value_text) == 0:
                continue

            finding_dict[key] = PwndocCustomFields.select_field_pwndoc_to_scan2report(key, finding.locale, value_text)

        for key in list(finding_dict.keys()):
            if not key.endswith("_checkbox"):
                continue
            list_of_active_checkboxes: List[str] = finding_dict[key]
            checkbox_active = bool(len(list_of_active_checkboxes))  # todo: I don't handle list of checkboxes, only a single checkbox
            finding_dict[key] = str(checkbox_active).lower()  # note: scan2report expects some attributes (e.g. ignore_pluginoutput) as string instead of bool

        for key in PWNDOC_TO_SCAN2REPORT_MAPPING:
            if key in finding_dict:
                answer[PWNDOC_TO_SCAN2REPORT_MAPPING[key]] = finding_dict[key]

        if output_as_original:
            for key in list(answer.keys()):
                answer[f"{key}_original"] = answer[key]
                del answer[key]

        # Hosts key is only in Finding, not in Template
        answer.pop("hosts", None)
        answer.pop("hosts_original", None)

        return answer
