import os
import pathlib
from typing import List, Optional, Dict, Any, Union
from dataclasses import dataclass, field
from dataclasses_json import dataclass_json

from loguru import logger

import config
from helpers.custom_logging import FlashLog
from helpers.file_utils import relative_path


def str_bool_to_bool(x: Union[str, bool]) -> bool:
    return str(x).lower() == 'true'


@dataclass_json
@dataclass
class TemplateScan2Report:
    fid: str = ""
    locale: str = "og"

    severity: int = 0
    attack_complexicity: int = 0  # note: "complexicity", not complexity. This is coming from scan2report.
    system_impact: int = 0

    name: str = ""
    description: str = ''
    recommendations: str = ''
    impact: str = ''
    links: List[str] = field(default_factory=list)

    author: str = ""  # only in Template

    hosts: List[str] = field(default_factory=list)  # only in Finding
    # plugin_output is only in finding and it is a dict

    # This is bool, but scan2report requires it as a string.
    ignore_pluginoutput: str = 'false'

    @classmethod
    def parse_dict_custom(cls, fid: str, locale: str, template_dict: dict, original: bool = False) -> Optional['TemplateScan2Report']:
        template_dict = cls.__filter_locale_or_og_keys(template_dict, original)
        cls.__warn_if_incorrectly_named_original_fields(template_dict)

        if len(template_dict) == 0:
            return None

        template_dict["fid"] = fid
        template_dict["locale"] = locale if not original else "og"

        new_finding_template = TemplateScan2Report.from_dict(template_dict)
        return new_finding_template

    @classmethod
    def parse_dict_for_locale_and_og(cls, fid: str, locale: str, template_dict: dict) -> Dict[str, 'TemplateScan2Report']:
        locale_version = cls.parse_dict_custom(fid, locale, template_dict, original=False)
        og_version = cls.parse_dict_custom(fid, locale, template_dict, original=True)
        answer = {}
        if locale_version:
            answer[locale] = locale_version
        if og_version:
            answer["og"] = og_version
        return answer

    @staticmethod
    def __filter_locale_or_og_keys(template_dict: dict, original: bool = False) -> dict:
        """
            This filters the dictionary to contain only Template or Template created from OG data with suffix removed
        """
        new_template_dict = {}

        for key, value in template_dict.items():
            if key.endswith('_original') != original:
                continue
            new_key = key[:-len("_original")] if original else key  # remove original suffix if it's present
            new_template_dict[new_key] = value

        return new_template_dict

    @staticmethod
    def __warn_if_incorrectly_named_original_fields(data: Dict[str, Any]):
        incorrectly_named = list(filter(lambda x: x.startswith("original_"), list(data.keys())))
        if len(incorrectly_named):
            logger.warning("Incorrectly named original fields found. Skipping them. - ", incorrectly_named)

    @classmethod
    def native_folder_name_for_locale(cls, locale: str, original: bool = False) -> str:
        if locale == "og":
            return cls.native_folder_name_for_locale(config.LOCALES[0], original=True)
        if not original:
            return relative_path(f"{config.SCAN2REPORT_PLUGINS_FOLDER}/{locale}")
        return cls.native_folder_name_for_locale(locale) + "/templates"

    @classmethod
    def recreate_template_folders_if_necessary(cls):
        for locale in config.LOCALES:
            og_templates_folder_path = pathlib.Path(cls.native_folder_name_for_locale(locale, original=True))
            if not og_templates_folder_path.exists():
                FlashLog.warning("Scan2report plugins template folder didn't exist, though it should based on git. It had to be re-created.")
                og_templates_folder_path.mkdir(parents=True, exist_ok=True)

    @classmethod
    def list_files_in_locale_folder(cls, locale: str, original: bool = False) -> List[str]:
        locale_dir = TemplateScan2Report.native_folder_name_for_locale(locale, original=original)
        files_structs = list(filter(lambda x: x.is_file() and x.name.endswith(".json"), os.scandir(locale_dir)))
        files_paths = list(map(lambda x: x.path, files_structs))
        return files_paths

    @classmethod
    def delete_template_files(cls, locales: Optional[List[str]] = None):
        all_files = set()
        if locales is None:
            locales = config.LOCALES

        for locale in locales:
            for original in [False, True]:
                locale_files = cls.list_files_in_locale_folder(locale, original)
                all_files.update(locale_files)

        for file_path in all_files:
            if os.path.isfile(file_path):
                os.remove(file_path)
