import ast
import multiprocessing
import re


def run_with_timeout_process(func, timeout, *args, **kwargs):
    """
        Executes a function in a separate process and terminates it if
        the timeout is reached.

        Parameters
        ----------
        func : callable
            The function to execute in the subprocess.
        timeout : int
            Maximum allowed execution time in seconds.

        Raises
        ------
        Exception
            Any exception raised by the function.
        TimeoutError
            If the function execution exceeds the timeout.
    """

    queue = multiprocessing.Queue()

    def target():
        try:
            func(*args, **kwargs)
            queue.put(None)
        except Exception as e:
            queue.put(e)

    proc = multiprocessing.Process(target = target)
    proc.start()
    proc.join(timeout)
    if proc.is_alive():
        proc.terminate()
        proc.join()
        raise TimeoutError(f'Timeout de {timeout}s atteint')

    exception = queue.get()
    if exception:
        raise exception

def extract_traceback_from_caplog(caplog):
    """
        Extracts the traceback section from a pytest caplog output.

        Parameters
        ----------
        caplog : str
            Raw text content captured from pytest's caplog.

        Returns
        -------
        str
            The traceback text, formatted exactly as it would appear
            in the terminal output.
    """

    pattern = re.compile(
        r"Subprocess output - start\n"
        r"(.*?)"
        r"Subprocess output - end",
        re.DOTALL
    )
    matches = pattern.findall(caplog)
    if not matches:
        return None

    last_block = matches[-1].strip()

    # Supprime les préfixes de log de type 'INFO wox:subprocessor.py:198 '
    last_block = re.sub(
        r"^\w+\s+\S+:\d+\s", "",
        last_block,
        flags=re.MULTILINE
    )

    # Supprime les lignes de séparation (----)
    last_block = re.sub(
        r"^-{3,}\s*$", "",
        last_block,
        flags=re.MULTILINE
    )

    # Supprime toute ligne qui commence par un préfixe de log résiduel
    last_block = re.sub(
        r"^(DEBUG|INFO|WARNING|ERROR|CRITICAL)\s+.*$", "",
        last_block,
        flags=re.MULTILINE
    )

    return last_block.strip()

def extract_dict_from_caplog(caplog, marker):
    """
        Extracts a dict from a pytest caplog output.

        Parameters
        ----------
        caplog : str
            Raw text content captured from pytest's caplog.
        marker: str
            String helping identifying the log storing the dict.

        Returns
        -------
        dict
            The dict recreated from its string form from log.

        Notes
        -----
        Extracting a dict from logs implies to turn the string representing the dict
        into an actual dict.

        ast is a library that manipulates Python code as data structure.
        Among its features, literal_eval() turns any string into an actual object, which
        is perfect for this use case.

        https://docs.python.org/3/library/ast.html#ast.literal_eval
    """

    log_record = [
        record for record in caplog.records \
        if marker in record.message
    ][0].msg

    match = re.search(r'(\{.*\})', log_record, re.DOTALL)
    if match:
        return ast.literal_eval(match.group(1))
