import json

from libs.osaf.osaf_core import OSAF_Core

import libs.pykek.kek as kek
from libs.spk_v.api import SPK5_API
from libs.spk_v.tkmg import VTaskManager
from libs.unirepo.unirepo import UniRepo
from libs.spk_v.loader import SPK5Loader


class SPKRPkgTask:
    def __init__(self, runtime, action, target, target_args):
        self.runtime = runtime
        self.action = action
        self.target = target
        self.target_args = target_args

        self.deps = []


class SPK_runtime:

    @staticmethod
    def default_conf():
        return {
            'runtime_dir': 'run/'
        }

    def __init__(self, syspack_api, conf):
        if syspack_api is None:
            raise Exception("Syspack Runtime requre a Syspack API")
        self.syspack_api = SPK5_API(syspack_api.syspack_instance)

        self.log = syspack_api.get_logger()
        self.osa: OSAF_Core = syspack_api.get_osa()
        self.repo : UniRepo = syspack_api.get_repoman()
        self.id = kek.random_str(6)

        if conf is not None:
            self.conf = conf
        else:
            self.log.w('Runtime configuration not setted, using default conf')
            self.conf = self.default_conf()

        self.dir = self.conf['runtime_dir'] + self.id + '/'
        self.tasks = []
        self.log.i(f'created runtime with id {self.id}')
        self.osa.files.mkdir(self.dir)

        self.dir_task_mapping = {}

        self.rtask_manager = VTaskManager(self.osa)
        self.syspack_api.runtime = self

    def import_tasks_from_file(self, filepath):
        raw = self.osa.files.read_str(filepath)
        if raw is None or len(raw) == 0:
            raise (f'file {filepath} emtry or not exist')

        jtask = json.loads(raw)
        list = jtask['tasks']

        for jt in list:
            action = jt['action']
            target = jt['target']
            target_args = jt['target_args']
            t = SPKRPkgTask(self, action, target, target_args)

            self.tasks.append(t)

        self.log.i(f'loaded {len(self.tasks)} tasks, from {filepath}')

    def prepare_tasks(self):
        self.log.d(f'prepare tasks')
        for t in self.tasks:
            t.dir = self.dir + kek.random_str(6) + '/'
            self.osa.files.mkdir(t.dir)
            self.dir_task_mapping[t.dir] = t
            # fetch repo data
            if t.action in ['run', 'install', 'module', 'data']:
                p = self.repo.pkg_selector(self.repo.search(t.target))
                self.repo.get_bundle(p, t.dir)

            # get deps
            # exec scripts
            # load modules for task

    def exec_tasks(self):
        self.log.i(f'start tasks ({len(self.tasks)}) execution')

        tasks_exec_results = []

        tid = 0
        for t in self.tasks:
            t.tid = tid
            tid = tid + 1
            code, result = self.__exec_task(t)

            tasks_exec_results.append({"task": t, "code": code, "result": result})

            if code == 1:
                self.log.w(f'Warn! task [{t.action}, {t.target}, {t.target_args}] exit with code 1, result {result}')

            if code > 1:
                self.log.e(
                    f'Error! task [{t.action}, {t.target}, {t.target_args}] exit with code {code}, result {result}')
                break


        self.log.i('tasks execution finished!')
        self.log.d(tasks_exec_results)

        return tasks_exec_results

    def __exec_task(self, t):
        code = 1

        data = None
        if t.action == 'binexec':
            code, data = self.osa.processes.exec([t.target] + t.target_args, log=True)

        if t.action == 'run' or t.action == 'install':
            b_meta = self.prepare(t)
            self.log.d(f'b_meta {b_meta}')
            if b_meta is None:
                self.log.f(f'candidate package {t.target} not found, abort')
            runtype = None
            user = 'root'
            module_name = None
            value = None
            run_c = []
            try:
                api_v = b_meta.get("spk-api")
                if api_v != 4 or api_v is None:
                    self.log.f(f'Package has no support SysPack API ver {api_v}')
                self.log.dd(f"b_meta get {t.action} :: {b_meta.get('actions').get(t.action)}")
                run_c = b_meta.get('actions').get(t.action)

                self.log.d(f'run_c{run_c}')

                if type(run_c) is not list:
                    self.log.dd(f'run_c{run_c} is NOT list')

                    run_c = [run_c]
                else:
                    self.log.dd(f'run_c{run_c} is list')

                self.log.d(f'run_c{run_c}')


            except:
                self.log.f(f'fail to parse json pkg {t.target}')

            for act in run_c:

                runtype = act.get('type')
                value = act.get('value')
                user = act.get('user')
                module_name = act.get('module_name')
                message = act.get('message')
                action_override = act.get('action_override')

                # add args from command



                # binexec - direct binary files run
                # module - import file from package, and run with access to SysPack API
                # system_pkg - install package use pkg manager from current OS
                if message is not None:
                    self.log.i(f'PKG: {message}')

                if runtype == 'binexec':
                    if t.target_args is not None:
                        if type(t.target_args) == list:
                            value = value + t.target_args
                        else:
                            value = value + [t.target_args]
                    code, data = self.osa.processes.exec(value, workdir=b_meta.get('__current_dir'), log=True)
                elif runtype == 'module':

                    if t.target_args is not None:
                        if type(t.target_args) == list:
                            value = value + t.target_args
                        else:
                            value = value + [t.target_args]

                    loader = SPK5Loader(self.syspack_api, b_meta.get('__current_dir'))
                    if module_name is None:
                        self.log.f("Module name is null!")
                    loader.load(module_name, value[0])
                    # todo add load submodules
                    loader.init_modules()
                    loader.run_module(module_name, value[1:])
                elif runtype == 'system-pkg':
                    if value[:2] == "./":
                        print(value[:2] + " - local pkg")
                        value = b_meta.get('__current_dir') + "/" + value

                    if action_override is not None:
                        if action_override == 'remove':
                            self.osa.packages.remove(value)
                        else:
                            self.log.f(f"Action Override {action_override} is not support!")
                    else:

                        self.osa.packages.install(value)
                else:
                    self.log.f(f'No support run type: {runtype}')

        return code, data

    def prepare(self, task):
        b = self.repo.auto_get(task.target)
        if b is None:
            return None
        tdir = f'{task.runtime.dir}{task.tid}/'
        self.osa.files.mkdir(tdir)
        self.repo.export(b, tdir)

        spk_f = f'{tdir}spk.json'
        if not self.osa.files.exists(spk_f):
            self.log.f(f'spk.json not found in [{task.target}] {tdir}')
            return None

        sd = None
        try:
            sd = json.loads(self.osa.files.read_str(spk_f))
        except:
            self.log.f(f'failed to load file {spk_f}')

        sd['__current_dir'] = tdir
        return sd