import sys
import os
import tempfile
from contextlib import contextmanager
from typing import ContextManager

import psutil

from clarabot_nano.utility.subprocess.interrupt import interrupt_child
from clarabot_nano.utility.subprocess.spawn import platform_fix_spawn_child_kwargs


@contextmanager
def popen(
    args, out=None, err=None, cwd=None, env=None, copy_current_env=True, gui_type=False
) -> ContextManager[psutil.Popen]:
    if env and copy_current_env:
        tmp = dict(os.environ)
        tmp.update(env)
        env = tmp
    subp = psutil.Popen(
        args,
        **platform_fix_spawn_child_kwargs(
            {
                "stderr": err,
                "stdout": out,
                "cwd": cwd,
                "env": env,
                "close_fds": True,
            },
            gui_type=gui_type,  # NOTE: all are started with console in testing generally
        ),
    )
    try:
        yield subp
    finally:
        if subp.poll() is None:
            interrupt_child(subp, gui_type=gui_type)
            print("_DEV-TERM", subp.pid, args)
            for _ in range(10):
                try:
                    subp.wait(3.0)
                    break
                except psutil.TimeoutExpired:
                    pass
            else:
                print("_DEV-POPEN-TIMEOUT", subp.pid, args)
                raise Exception(f"Dev-popen-timeout {subp.pid}")
            print("_DEV-TERM-DONE", subp.pid, subp.returncode, args)


def pypopen(args, out=None, err=None, env=None, copy_current_env=True, gui_type=False) -> ContextManager[psutil.Popen]:
    return popen(
        [sys.executable, "-m"] + args,
        out=out,
        err=err,
        env=env,
        copy_current_env=copy_current_env,
        gui_type=gui_type,
    )


class PopenCommunicate(object):
    def __init__(self):
        self.subp: psutil.Popen = None
        self.out = None
        self.err = None


@contextmanager
def popen_communicate(args, cwd=None, env=None, encoding="utf-8", gui_type=False) -> ContextManager[PopenCommunicate]:
    """
    Popen that does not block by communicate.
    Instead of the inter process piping, two temporary files are used.
    """
    mode = "w+" if encoding else "w+b"
    obj = PopenCommunicate()
    with tempfile.NamedTemporaryFile(mode=mode, encoding=encoding) as fo:
        try:
            with tempfile.NamedTemporaryFile(mode=mode, encoding=encoding) as fe:
                try:
                    with popen(args, out=fo, err=fe, cwd=cwd, env=env, gui_type=gui_type) as subp:
                        obj.subp = subp
                        yield obj
                finally:
                    fe.seek(0)
                    obj.err = fe.read()
        finally:
            fo.seek(0)
            obj.out = fo.read()


def pypopen_communicate(args, cwd=None, env=None, encoding="utf-8", gui_type=False) -> ContextManager[PopenCommunicate]:
    return popen_communicate([sys.executable, "-m"] + args, cwd=cwd, env=env, encoding=encoding, gui_type=gui_type)
