import sys
import time
import logging
import importlib
import importlib.util

from multiprocessing import Process

from wurker.models import WurkerBeeState

from .base import BaseBee


class PyBee(BaseBee):
    """Base class for modular Python Wurker Bees."""

    full_module_name = ""
    module_name = ""
    module = None

    def __init__(self, session, dbee, dcron=None, args:dict=None, logger=None):
        """Load a modular Wurker Bee."""

        # PyBee args must be a dict
        if args is None:
            args = {}
        elif not isinstance(args, dict):
            raise ValueError(f"Wurker PyBee 'args' must be a dict not {type(args)}: {args}")

        BaseBee.__init__(self, session, dbee, dcron=dcron, args=args, logger=logger)
        self._init_module()

    def _init_module(self):
        """Load the module or raise an exception."""

        if not self.module_name:
            raise Exception("Wurker 'PyBee.module_name' is required")

        self.full_module_name = f"wurker.bees.{self.module_name}"

        if self.full_module_name in sys.modules:
            self.module = sys.modules[self.full_module_name]
        else:
            try:
                module = importlib.import_module(self.full_module_name)
                self.module = module
            except ModuleNotFoundError as ex:
                raise Exception(f"Wurker PyBee not found: {self.module_name!r}") from ex

        if not hasattr(self.module, "run"):
            raise Exception(f"Wurker PyBee missing 'run' function: {self.full_module_name!r}")

    def run(self):
        """Run the 'run()' module function."""

        self.start_dt = self.Now

        # Create new PENDING report record
        self.set_report()

        self.proc = Process(target=self.module.run, name=self.slug, kwargs=self.args)
        self.proc.start()
        self.pid = self.proc.pid
        self.runcount = 1

        # Update to PROCESSING
        self.set_report(status=WurkerBeeState.PROCESSING)

        self.log(f"[{self.pid}] Running Wurker PyBee '{self.slug}'...")
        return True

    def poll(self):
        """Like subprocess poll()."""

        if not self.proc:
            self.reset_proc()
            return None

        return self.proc.exitcode

    def wait(self, timeout=None):
        """Like subprocess wait()."""

        if not self.proc:
            self.reset_proc()
            return None

        return self.proc.join(timeout)

    def kill(self, error_status=WurkerBeeState.ERROR):
        """Like subprocess terminate()."""

        is_dead = True

        # Already dead? With no process?
        if not self.proc:
            self.set_report(status=WurkerBeeState.COMPLETED)
            self.reset_proc()
            return True

        # Already done? Then return True
        if not self.proc.is_alive():
            if self.proc.exitcode != 0:
                self.set_report(status=error_status, status_info={"exit_code": self.proc.exitcode})
            else:
                self.set_report(status=WurkerBeeState.COMPLETED)

            self.reset_proc()
            return True

        # Not dead yet! So try to kill it...
        self.log(f"[{self.pid}] Sending SIGTERM to Wurker PyBee '{self.slug}'...",
                 level=logging.ERROR)

        self.proc.terminate()
        time.sleep(1.0)
        is_dead = True

        # SIGTERM didn't work? Try SIGKILL
        if self.proc.is_alive():
            self.log(f"[{self.pid}] SIGTERM failed. Sending SIGKILL to Wurker PyBee '{self.slug}'...",
                     level=logging.ERROR)

            self.proc.kill()
            time.sleep(1.0)

            # Did it finally stay dead? If not... it failed to die
            if self.proc.is_alive():
                self.log(f"[{self.pid}] SIGTERM and SIGKILL failed! Wurker PyBee '{self.slug}' is a ZomBee!",
                         level=logging.ERROR)

                is_dead = False

        if is_dead:
            self.set_report(status=error_status, status_info={"message": f"PyBee killed: '{self.slug}'"})
        else:
            self.set_report(status=error_status, status_info={"message": f"PyBee is zombie: '{self.slug}'"})

        # Fingers crossed!
        self.reset_proc()
        return is_dead
