"""Python code executor for UnitTestBot"""
import inspect
import importlib
import logging
import resource
import sys
import traceback
from typing import Any, Callable, Dict, Iterable, List, Set, Tuple
import coverage

from utbot_executor.deep_serialization.deep_serialization import serialize_objects
from utbot_executor.deep_serialization.json_converter import DumpLoader, deserialize_memory_objects
from utbot_executor.deep_serialization.utils import PythonId, getattr_by_path

from utbot_executor.parser import ExecutionRequest, ExecutionResponse, ExecutionFailResponse, ExecutionSuccessResponse
from utbot_executor.utils import suppress_stdout


class PythonExecutor:
    def add_syspaths(self, syspaths: Iterable[str]):
        for path in syspaths:
            if path not in sys.path:
                sys.path.append(path)

    def add_imports(self, imports: Iterable[str]):
        for module in imports:
            for i in range(1, module.count('.') + 2):
                submodule_name = '.'.join(module.split('.', maxsplit=i)[:i])
                globals()[submodule_name] = importlib.import_module(submodule_name)

    def run_function(self, request: ExecutionRequest) -> ExecutionResponse:
        logging.debug("Prepare to run function `%s`", request.function_name)
        logging.debug("Module `%s`", request.function_module)
        logging.debug("Imports `%s`", request.imports)
        try:
            memory_dump = deserialize_memory_objects(request.serialized_memory)
            loader = DumpLoader(memory_dump)
            logging.debug("loader `%s`", loader)
        except Exception as ex:
            logging.debug("Error \n%s", traceback.format_exc())
            return ExecutionFailResponse("fail", traceback.format_exc())

        try:
            self.add_syspaths(request.syspaths)
            self.add_imports(request.imports)
            logging.debug("self imports OK")
            loader.add_syspaths(request.syspaths)
            loader.add_imports(request.imports)
            logging.debug("loader imports OK")
        except Exception as ex:
            logging.debug("Error \n%s", traceback.format_exc())
            return ExecutionFailResponse("fail", traceback.format_exc())

        try:
            function: Callable = getattr_by_path(
                    importlib.import_module(request.function_module),
                    request.function_name
                    )
            logging.debug("Function OK")
            args = [loader.load_object(PythonId(arg_id)) for arg_id in request.arguments_ids]
            logging.debug("Arguments OK")
            kwargs: dict[str, Any] = {}
        except Exception as ex:
            logging.debug("Error \n%s", traceback.format_exc())
            return ExecutionFailResponse("fail", traceback.format_exc())

        try:
            resource.setrlimit(resource.RLIMIT_AS, (10**8, 10**8))
            value = run_calculate_function_value(
                    function,
                    args,
                    kwargs,
                    request.filepath
                    )
            logging.debug("Value OK")
        except Exception as ex:
            logging.debug("Error \n%s", traceback.format_exc())
            return ExecutionFailResponse("fail", traceback.format_exc())
        logging.debug("Function execution: done")
        return value


def __get_lines(start, end, lines):
    return [x for x in lines if start < x < end]


def _serialize_state(
        args: List[Any],
        kwargs: Dict[str, Any],
        result: Any = None,
        ) -> Tuple[List[PythonId], List[PythonId], PythonId, str]:
    """Serialize objects from args, kwargs and result.

    Returns: tuple of args ids, kwargs ids, result id and serialized memory."""

    all_arguments = args + list(kwargs.values()) + [result]
    ids, serialized_memory = serialize_objects(all_arguments)
    return (
            ids[:len(args)],
            ids[len(args):len(args)+len(kwargs)],
            ids[-1],
            serialized_memory,
            )


def run_calculate_function_value(
        function: Callable,
        args: List[Any],
        kwargs: Dict[str, Any],
        fullpath: str,
    ) -> ExecutionResponse:
    """ Calculate function evaluation result.

    Return serialized data: status, coverage info, object ids and memory."""

    logging.debug("State before...")
    _, _, _, state_before = _serialize_state(args, kwargs)
    logging.debug("State before OK")

    __is_exception = False
    __cov = coverage.Coverage(
        data_file=None,
    )
    __cov.start()
    logging.debug("Run function")
    try:
        with suppress_stdout():
            logging.debug("Inside suppress_stdout")
            __result = function(*args, **kwargs)
            logging.debug("Result %s", __result)
    except Exception as __exception:
        logging.debug("Exception %s", __exception)
        __result = __exception
        __is_exception = True
    __cov.stop()
    logging.debug("Exception success OK")

    (__sources, __start, ) = inspect.getsourcelines(function)
    __end = __start + len(__sources)
    (_, __stmts, _, __missed, _, ) = __cov.analysis2(fullpath)
    __cov.erase()
    __stmts_filtered = __get_lines(__start, __end, __stmts)
    __stmts_filtered_with_def = [__start] + __stmts_filtered
    __missed_filtered = __get_lines(__start, __end, __missed)

    args_ids, kwargs_ids, result_id, state_after = _serialize_state(args, kwargs, __result)

    return ExecutionSuccessResponse(
            status="success",
            is_exception=__is_exception,
            statements=__stmts_filtered_with_def,
            missed_statements=__missed_filtered,
            state_before=state_before,
            state_after=state_after,
            args_ids=args_ids,
            kwargs_ids=kwargs_ids,
            result_id=result_id,
            )
