import os
import asyncio
import importlib
import requests
import glob
import sys
import types
import traceback
import re
import malcat
from intelligence.base import *


CHECKER_TIMEOUT = 15

def get_checkers(data_dir, user_data_dir, reload_plugins=True, with_method=None):
    checkers = []
    seen = set()
    if user_data_dir and user_data_dir != data_dir:
        toscan = (user_data_dir, data_dir)
    else:
        toscan = (data_dir,)
    for plugins_path in toscan:
        plugins_path = os.path.normpath(os.path.abspath(plugins_path))
        for fname in glob.glob(os.path.join(plugins_path, "intelligence", "*.py")):
            module_name = os.path.relpath(fname, plugins_path)[:-3].replace(os.sep, ".")
            if module_name in sys.modules:
                mod = sys.modules[module_name]
                if reload_plugins:
                    for element in dir(mod):
                        if isinstance(element, types.ModuleType):
                            importlib.reload(element)
                    mod = importlib.reload(mod)
            else:
                mod = importlib.import_module(module_name)
            for elemname in dir(mod):
                elem = getattr(mod, elemname)
                try:
                    if issubclass(elem, OnlineChecker) and elem != OnlineChecker and not elemname in seen:
                        if with_method is None or hasattr(elem, with_method):
                            checkers.append(elem)
                            seen.add(elemname)
                except TypeError: pass
    return checkers



async def check_poll(analysis, todo):
    loop = asyncio.get_event_loop()
    checkers = []
    checkers_tasks = []
    res = []
    for c, options in todo.items():
        checker = c(**options)
        if options.get("enable", True):
            checkers.append(c)
            task = loop.run_in_executor(None, checker.check, analysis)
            checkers_tasks.append(asyncio.wait_for(task, CHECKER_TIMEOUT))
        else:
            res.append((checker, OnlineResult(CheckStatus.DEACTIVATED)))
    all_returns = await asyncio.gather(*checkers_tasks, return_exceptions=True)
    for checker, check in zip(checkers, all_returns):
        if check is None:
            res.append((checker, OnlineResult(status=CheckStatus.NOTFOUND)))
        elif isinstance(check, TimeoutError) or isinstance(check, asyncio.exceptions.TimeoutError):
            res.append((checker, OnlineResult(status=CheckStatus.TIMEOUT)))
        elif isinstance(check, NotImplementedError):
            pass
        elif isinstance(check, BaseException):
            message = "\n".join(map(str.strip, traceback.format_exception_only(check.__class__, check)))
            print(message)
            res.append((checker, OnlineResult(status=CheckStatus.ERROR, message=str(check))))
        elif isinstance(check, OnlineResult):
            res.append((checker, check))
        else:
            res.append((checker, OnlineResult(status=CheckStatus.NOTFOUND, message=str(check))))
    return res


def check(analysis, options):
    if sys.platform == "win32" and sys.version_info >= (3, 8, 0):
        asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
    checkers_classes = get_checkers(malcat.env.datadir, malcat.env.userdir)
    results = asyncio.run(check_poll(analysis, {c: options.get(c.name, {}) for c in checkers_classes} ))
    res = []
    for checker, result in results:
        detections = [(k, v.level.value, v.name) for k,v in result.detections.items()]
        res.append((checker.name, result.status.value, result.message, detections, result.url))
    return res


