import enum
import requests
import functools

class DetectionLevel(enum.IntEnum):
    CLEAN = 0
    UNKNOWN = 1
    LIBRARY = 2
    ENCRYPTED = 3
    PACKED = 5
    APPL = 8
    PUA = 10
    SPR = 20
    SUSPECT = 30
    HACKTOOL = 50
    ADWARE = 80
    MALWARE = 100

    @staticmethod
    def from_number(number, maximum_number=100):
        if number is None:
            return DetectionLevel.UNKNOWN
        if number > maximum_number / 2:
            return DetectionLevel.MALWARE
        elif number > maximum_number / 5:
            return DetectionLevel.SUSPECT
        else:
            return DetectionLevel.UNKNOWN

    @staticmethod
    def from_text(text):
        words = {
                "malicious": DetectionLevel.MALWARE,
                "likely_malicious": DetectionLevel.MALWARE,
                "trj": DetectionLevel.MALWARE,
                "trojan": DetectionLevel.MALWARE,
                "spreader": DetectionLevel.MALWARE,
                "virus": DetectionLevel.MALWARE,
                "apt": DetectionLevel.MALWARE,
                "malware": DetectionLevel.MALWARE,
                "malware2": DetectionLevel.MALWARE,
                "adware": DetectionLevel.ADWARE,
                "adload": DetectionLevel.ADWARE,
                "pua": DetectionLevel.PUA,
                "pup": DetectionLevel.PUA,
                "unwanted": DetectionLevel.PUA,
                "suspicious": DetectionLevel.SUSPECT,
                "clean": DetectionLevel.CLEAN,
                "cleanware": DetectionLevel.CLEAN,
                "clean1": DetectionLevel.CLEAN,
                "harmless": DetectionLevel.CLEAN,
                "legit file": DetectionLevel.CLEAN,
                "undetected": DetectionLevel.CLEAN,
                "no-threat": DetectionLevel.CLEAN,
                "marked-non-malware": DetectionLevel.CLEAN,
        }
        for word in text.split(" "):
            cat = words.get(word.lower(), None)
            if cat is not None:
                return cat
        return DetectionLevel.UNKNOWN


@functools.total_ordering
class OnlineDetection:

    def __init__(self, level=DetectionLevel.UNKNOWN, name=""):
        self.level = level
        self.name = name

    def __eq__(self, o):
        return self.level == o.level and self.name == o.name

    def __lt__(self, o):
        return self.level < o.level or self.level == o.level and self.name < o.name

    def __repr__(self):
        return "[{}] {}".format(self.level.name, self.name)


class CheckStatus(enum.IntEnum):
    OK = 0
    DEACTIVATED = 1
    NOTFOUND = 2
    TIMEOUT = 3
    ERROR = 4


class OnlineResult:

    def __init__(self, status=CheckStatus.OK, message="", url="", detections={}):
        self.status = status
        self.message = message
        self.detections = detections
        self.url = url


class OnlineFile:

    def __init__(self, status=CheckStatus.OK, message="", url="", content=b""):
        self.status = status
        self.message = message
        self.url = url
        self.content = content

    def __repr__(self):
        return f"[{self.status}] ({len(self.content)} bytes): {self.message}"


class OnlineChecker:
    name = None         # a nice name for your provider, may include spaces
    options = {}        # a dict of {option_name : (option_default_value, option_comment)}, for all options suported by this provider
                        # Malcat will create the corresponding options fields in the preferences dialog 

    def __init__(self, **options):
        self.options = options

    def preconfigured_session(self, json=False, key_header_name=None):
        session = requests.Session()
        session.headers.update({"User-Agent": "malcat"})
        session.request = functools.partial(session.request, timeout=self.options.get("timeout", 20), verify=self.options.get("ssl_verify", True))
        return session

    def check(self, analysis) -> OnlineResult:
        raise NotImplementedError

    def download(self, query:str) -> OnlineFile:
        raise NotImplementedError

    def download_and_verify(self, query:str) -> OnlineFile:
        res = self.download(query)
        if res is None or res.status != CheckStatus.OK or not res.content:
            return res
        import hashlib
        if len(query) == 32:
            fn = hashlib.md5
        elif len(query) == 40:
            fn = hashlib.sha1
        else:
            fn = hashlib.sha256
        if fn(res.content).hexdigest().lower() == query.lower():
            return res
        if res.content.startswith(b"PK"):
            # most likely a pwd-protected zip
            import malcat
            from filetypes.ZIP import ZipAnalyzer
            try:
                za = ZipAnalyzer()
                f = malcat.FileBuffer(bytearray(res.content), "downloaded")
                za.run(f, lazy=True)
                if len(za.files) == 1:
                    res.content = za.open(za.files[0])
                    if fn(res.content).hexdigest().lower() == query.lower():
                        return res
            except BaseException as e:
                raise
                return res
        raise ValueError(f"Hash of result mismatch for {self.name}: {query.lower()} vs {fn(res.content).hexdigest().lower()}")

    def upload(self, file: bytes):
        raise NotImplementedError

    @property
    def name(self):
        if hasattr(self.__class__, "name") and self.__class__.name:
            return self.__class__.name
        else:
            return self.__class__.__name__

    def __repr__(self):
        return self.name

