from random import randrange

import yaml

from secscanner2junit.logger import warn


class _Base:
    def __init__(self, raw_object: dict):
        self._raw_object = raw_object

    def _parse_simple_property(self, property_name):
        try:
            return self._raw_object[property_name]
        except KeyError:
            return ''

    def _parse_required_property(self, property_name):
        try:
            return self._raw_object[property_name]
        except KeyError:
            warn("Could not find required property: " + property_name + " in: " + str(self._raw_object))
            return ''

    def _parse_simple_property_list(self, property_name, property_type):
        try:
            elements = self._raw_object[property_name]
            result = []
            for element in elements:
                result.append(property_type(element))
            return result
        except KeyError:
            return []

    def _parse_required_property_list(self, property_name, property_type):
        try:
            elements = self._raw_object[property_name]
            result = []
            for element in elements:
                result.append(property_type(element))
            return result
        except KeyError:
            warn("Could not find required property: " + property_name + " in: " + str(self._raw_object))
            return []


# https://gitlab.com/gitlab-org/security-products/security-report-schemas/-/blob/master/src/security-report-format.json
class _Vulnerability(_Base):
    def __init__(self, raw_vulnerability: dict):
        super().__init__(raw_vulnerability)
        self.__id = self.__parse_id()
        self.__name = super()._parse_simple_property('name')
        self.__description = super()._parse_simple_property('description')
        self.__severity = super()._parse_simple_property('severity')
        self.__solution = super()._parse_simple_property('solution')
        self.__identifiers = super()._parse_required_property_list('identifiers', Identifier)
        self.__links = super()._parse_simple_property_list('links', Link)
        # details
        # tracking
        # flags

    def __repr__(self):
        return f"Vulnerability(raw_vulnerability={self._raw_object})"

    def __parse_id(self):
        try:
            return self._raw_object['id']
        except KeyError:
            warn("Could not find id (use fake instead) for vulnerability: " + str(self._raw_object))
            return 'fake_id_' + str(randrange(1, 10000000))

    def get_testcase_name(self):
        id_part = " (ID: " + self.__id + ")"
        severity_part = " (Severity: " + self.get_severity() + ")"
        if len(self.__name) > 0:
            return self.__name + id_part + severity_part
        else:
            return self.__identifiers[0].get_name() + id_part + severity_part

    def get_location(self):
        pass

    def get_description(self):
        if len(self.__description) > 0:
            return self.__description
        else:
            return "no description"

    def get_failure_type(self):
        return self.__identifiers[0].get_name()

    def get_output(self):
        # Explanation: *width* needs to be set, as yaml otherwise inserts linebreaks after 80 characters which messes
        # up the description and the urls *sort_keys* is True by default, which moves the description to the top and
        # the links to
        return yaml.dump(self._raw_object, width=1000, indent=4, sort_keys=False)

    def get_severity(self):
        if len(self.__severity) > 0:
            return self.__severity
        else:
            return "UNKNOWN"


class SastVulnerability(_Vulnerability):
    def __init__(self, raw_vulnerability: dict):
        super().__init__(raw_vulnerability)
        self.__location = SastLocation(super()._parse_required_property('location'))

    def get_location(self):
        return self.__location.get_location()


class ContainerScanningVulnerability(_Vulnerability):
    def __init__(self, raw_vulnerability: dict):
        super().__init__(raw_vulnerability)
        self.__location = ContainerScanningLocation(super()._parse_required_property('location'))

    def get_location(self):
        return self.__location.get_location()


class SecretsVulnerability(SastVulnerability):
    def __init__(self, raw_vulnerability: dict):
        super().__init__(raw_vulnerability)


class Identifier(_Base):
    def __init__(self, raw_identifier: dict):
        super().__init__(raw_identifier)
        self.__type = super()._parse_required_property('type')
        self.__name = super()._parse_required_property('name')
        self.__url = super()._parse_simple_property('url')
        self.__value = super()._parse_required_property('value')

    def get_name(self):
        return self.__name


class Link(_Base):
    def __init__(self, raw_link: dict):
        super().__init__(raw_link)
        self.__name = super()._parse_simple_property('name')
        self.__url = super()._parse_required_property('url')


class Location(_Base):
    def __init__(self, raw_location: dict):
        super().__init__(raw_location)

    def get_location(self):
        pass


# https://gitlab.com/gitlab-org/security-products/security-report-schemas/-/blob/master/src/sast-report-format.json
class SastLocation(Location):
    def __init__(self, raw_location: dict):
        super().__init__(raw_location)
        self.__file = super()._parse_simple_property('file')
        self.__start_line = super()._parse_simple_property('start_line')
        self.__end_line = super()._parse_simple_property('end_line')
        # class is reserved keyword so property is called class_property
        self.__class_property = super()._parse_simple_property('class')
        self.__method = super()._parse_simple_property('method')

    def get_location(self):
        return self.__file


# https://gitlab.com/gitlab-org/security-products/security-report-schemas/-/blob/master/src/container-scanning-report-format.json
class ContainerScanningLocation(Location):
    def __init__(self, raw_location: dict):
        super().__init__(raw_location)
        self.__dependency = Dependency(super()._parse_required_property('dependency'))
        self.__operating_system = super()._parse_required_property('operating_system')
        self.__image = super()._parse_required_property('image')
        self.__default_branch_image = super()._parse_simple_property('default_branch_image')

    def get_location(self):
        return self.__image


# https://gitlab.com/gitlab-org/security-products/security-report-schemas/-/blob/master/src/security-report-format.json
class Dependency(_Base):
    def __init__(self, raw_dependency: dict):
        super().__init__(raw_dependency)
        self.__package = Package(super()._parse_required_property('package'))
        self.__version = super()._parse_required_property('version')
        self.__iid = super()._parse_simple_property('iid')
        self.__direct = super()._parse_simple_property('direct')
        # dependency_path - TODO in future ?


# https://gitlab.com/gitlab-org/security-products/security-report-schemas/-/blob/master/src/security-report-format.json
class Package(_Base):
    def __init__(self, raw_package: dict):
        super().__init__(raw_package)
        self.__name = super()._parse_required_property('name')
