import os
import os.path
import faulthandler
import time
import logging
from ruxit.plugin_status_reporter import PluginFullStatus, PluginState

NOT_FINISHED_TASK_TIMEOUT_SECONDS = 120
REPORT_TIMEOUT_SECONDS = 120
log = logging.getLogger(__name__)
from ruxit.messages import EXECUTION_TIMEOUT


class FaultHandlerTrap:
    def __init__(self, fault_info_dir):
        self.fault_info_dir = fault_info_dir
        self.fault_file_path = os.path.join(fault_info_dir, "ruxitagent_pluginagent_fault_info_%s.log" % os.getpid())
        self.fault_info_file = open(self.fault_file_path, "w")
        faulthandler.enable(file=self.fault_info_file, all_threads=True)

    def __del__(self):
        # if we get to fire up the dtor, no fatal fault happened, so we can delete the file
        faulthandler.disable()
        self.fault_info_file.close()
        os.remove(self.fault_file_path)


class HungPluginsReporter:
    def __init__(self, fault_info_dir):
        self.fault_info_dir = fault_info_dir
        self.reported_futures_timestamps = {}

    def report_hung_tasks(self, not_finished_tasks):
        timestamp = time.monotonic()
        hung_task_found = False
        for submitted_future, submitted_task in not_finished_tasks.items():
            not_finished_timeout = (timestamp - submitted_task.created_timestamp) > NOT_FINISHED_TASK_TIMEOUT_SECONDS
            report_timeout = submitted_future not in self.reported_futures_timestamps or \
                             (timestamp - self.reported_futures_timestamps[submitted_future]) > REPORT_TIMEOUT_SECONDS

            if not_finished_timeout and report_timeout:
                log.warn(
                    "%s(running:%s) has not finished in expected time",
                    submitted_task,
                    submitted_future.running()
                )
                hung_task_found = True
                self.reported_futures_timestamps[submitted_future] = timestamp
                for state, engine in not_finished_tasks.items():
                    plugin_status = PluginFullStatus(
                                pluginName=engine.engine.plugin_info.reported_name,
                                pluginVersion=engine.engine.metadata['version'],
                                state=PluginState.ERROR_UNKNOWN.value,
                                description=EXECUTION_TIMEOUT,
                                stacktrace='No stack trace',
                                monitoredEntityId=engine.engine.get_entity_id()
                            )
                    engine.engine.full_status = plugin_status
                    engine.engine.execution_timeout = True

        self._clean_up_old_entries(not_finished_tasks)

        if hung_task_found:
            try:
                self._write_traces(timestamp)
            except Exception as e:
                log.warn("Unexpected Exception occured during writing trace %s",str(e))




    def _write_traces(self, timestamp):
        plugin_hang_filename = self._get_hung_filename(timestamp)
        log.warn("hang tasks found, dumping stacktrace into: %s", plugin_hang_filename)
        with open(plugin_hang_filename, "w") as hang_file:
            faulthandler.dump_traceback(file=hang_file, all_threads=True)

    def _get_hung_filename(self, timestamp):
        return os.path.join(
            self.fault_info_dir,
            "ruxitagent_pluginagent_hang_info_%s_%s.log" % (os.getpid(), str(timestamp).replace('.', "_"))
        )

    def _clean_up_old_entries(self, current_entries):
        entries_to_remove = [ftr for ftr in self.reported_futures_timestamps if ftr not in current_entries]
        for ftr in entries_to_remove:
            self.reported_futures_timestamps.pop(ftr, None)
