"""
Utility to signal a child process that they should exit.

Signals are completely different on POSIX and Windows. Here is the gist of it:
    - POSIX:
        - PopenInstance.send_signal(signal.SIGINT) sends SIGINT
        - PopenInstance.terminate() sends SIGTERM
        - PopenInstance.kill() sends SIGKILL
    - Windows
        - PopenInstance.send_signal(signal.CTRL_C_EVENT) sends SIGINT (but is not reliable)
        - PopenInstance.send_signal(signal.CTRL_BREAK_EVENT) sends SIGBREAK (java does not terminate by it)
        - PopenInstance.terminate() sends SIGKILL
        - PopenInstance.kill() sends SIGKILL

For Windows there is a solution that involves a subprocess to reliably send a CTRL_C_EVENT, which we tried to use.
However, during logoff and shutdown Windows prevents the transmitter process for consoles to be started with a very
cryptic and confusing error that we have no way of handling. We must not confuse the users with such nonsense...
so we're moving all child processes to GUI apps that we need no transmitter processes for to terminate.
The console solution is still implemented and usable, but it is mostly relevant for testing now.
"""
import signal
import subprocess
import sys
from typing import Union

import psutil

from clarabot_nano.utility.subprocess.executable import EXEC_CONSOLE
from clarabot_nano.utility.subprocess.spawn import platform_fix_spawn_child_kwargs

if sys.platform.startswith("win"):
    import pywintypes
    import win32con
    import win32gui
    import win32process

    def send_close_message(pid: int, notify_max: int = 0) -> int:
        notified = 0

        def _enum_callback(hwnd, _):
            hwnd_tid, hwnd_pid = win32process.GetWindowThreadProcessId(hwnd)
            if hwnd_pid == pid:
                win32gui.PostMessage(hwnd, win32con.WM_CLOSE, 0, 0)
                nonlocal notified
                notified += 1
                if notified >= notify_max > 0:
                    return False
            return True

        try:
            win32gui.EnumWindows(_enum_callback, 0)
        except pywintypes.error:
            pass  # Interrupting the enumeration raises, but it's actually a success.
        return notified

    def interrupt_child(child: Union[subprocess.Popen, psutil.Process], gui_type: bool = False):
        if gui_type:
            send_close_message(child.pid)
        else:
            args = [EXEC_CONSOLE, "-m", "clarabot_nano.utility.subprocess.winterrupt", str(child.pid)]
            subprocess.call(args, **platform_fix_spawn_child_kwargs({}, gui_type=False))

else:

    def interrupt_child(child: Union[subprocess.Popen, psutil.Process], gui_type: bool = False):
        child.send_signal(signal.SIGTERM)
