import functools
import logging
import os
import signal
import traceback
from contextlib import suppress
from multiprocessing import pool, Process, Pipe

from typing import Callable, List, Optional

from configparser import ConfigParser

import psutil


from hawkSoar.common.logger import color_string, TerminalColors

from hawkSoar.common.common import set_proc_name


def no_operation(*_):
    '''
    No Operation
    '''
    pass


def timeout(max_timeout_seconds):
    def timeout_decorator(item):
        @functools.wraps(item)
        def func_wrapper(*args, **kwargs):
            this_pool = pool.ThreadPool(processes=1)
            async_result = this_pool.apply_async(item, args, kwargs)
            # raises a TimeoutError if execution exceeds max_timeout
            return async_result.get(max_timeout_seconds)
        return func_wrapper
    return timeout_decorator


class ExceptionSafeProcess(Process):
    def __init__(self, *args, **kwargs):
        proc_name = kwargs.pop('process_name', None)
        Process.__init__(self, *args, **kwargs)
        self._receive_pipe, self._send_pipe = Pipe()
        self._exception = None
        self.called_function = kwargs.get('target')
        """
        if proc_name:
            # set_proc_name(bytes('hawk-soard ' + proc_name, 'ascii'))
            set_proc_name(bytes(proc_name, 'ascii'))
        """

    def run(self):
        try:
            Process.run(self)
            self._send_pipe.send(None)
        except Exception as exception:  # pylint: disable=broad-except
            trace = traceback.format_exc()
            self._send_pipe.send((exception, trace))
            raise exception

    @property
    def exception(self):
        if self._receive_pipe.poll():
            self._exception = self._receive_pipe.recv()
        return self._exception


def terminate_process_and_childs(process):
    process.terminate()
    _terminate_orphans(process)
    process.join()


def _terminate_orphans(process):
    with suppress(psutil.NoSuchProcess):
        parent = psutil.Process(process.pid)
        for child in parent.children(recursive=True):
            child.send_signal(signal.SIGTERM)


def check_worker_exceptions(process_list: List[ExceptionSafeProcess], worker_label: str,
                            config: Optional[ConfigParser] = None, worker_function: Optional[Callable] = None) -> bool:
    return_value = False
    for worker_process in process_list:
        if worker_process.exception:
            logging.error(color_string('Exception in {} process'.format(
                worker_label), TerminalColors.FAIL))
            logging.error(worker_process.exception[1])
            terminate_process_and_childs(worker_process)
            process_list.remove(worker_process)
            if config is None or config.getboolean('ExpertSettings', 'throw_exceptions'):
                return_value = True
            elif worker_function is not None:
                process_index = int(worker_process.name.split('-')[-1])
                logging.warning(
                    color_string('restarting {} {} process'.format(
                        worker_label, process_index), TerminalColors.WARNING)
                )
                process_list.append(start_single_worker(
                    process_index, worker_label, worker_function))
    return return_value
