import collections
import enum
import logging
import time
import pprint
from typing import Sequence
from ruxit.utils.execute_every_minute import ExecuteEveryMinute
from ruxit.package_utils.plugin_updater import PluginInfo
from ruxit.messages import DOES_NOT_EXIST

PLUGIN_REPORT_STATUS_INTERVAL_SECONDS = 30


class PluginFullStatus(collections.namedtuple("PluginState",
    ["pluginName", "pluginVersion", "state", "description", "monitoredEntityId", "stacktrace"])):
    __slot__ = ()

    def __repr__(self):
        return "{0}(pluginName={1}, pluginVersion={2}, state={3}, description={4}, monitoredEntityId=0x{5:x}, stacktrace={6})"\
            .format(type(self).__name__, self.pluginName, self.pluginVersion, self.state, self.description,
                    self.monitoredEntityId, self.stacktrace)    


PluginNameVersion = collections.namedtuple(
    "PluginNameVersion",
    ["pluginName", "pluginVersion"]
)


class PluginState(enum.Enum):
    UNINITIALIZED = 'UNINITIALIZED'
    OK = 'OK'
    ERROR_AUTH = 'ERROR_AUTH'
    ERROR_UNKNOWN = 'ERROR_UNKNOWN'
    ERROR_CONFIG = 'ERROR_CONFIG'
    INCOMPATIBLE = 'INCOMPATIBLE'
    NOTHING_TO_REPORT = 'NOTHING_TO_REPORT'
    LIMIT_REACHED = 'LIMIT_REACHED'
    DISABLED = 'DISABLED'


class PluginStatusReporter:

    def __init__(self, external_api):
        self.external_api = external_api
        self.last_reported_status = {}
        self.report_every_minute = ExecuteEveryMinute()

    def remove_engine(self, engine):
        self.last_reported_status.pop(engine.get_id(), None)

    def report_status(self,
                      engines: Sequence,  # sequence of PluginInfo 
                      incompatible_plugins: Sequence[PluginInfo],
                      not_existing_plugins: Sequence  # sequence of NonExistingPlugin
                      ): 
        report = []
        fast_report = []
        all_plugins = set()
        # we reverse the order so that newer plugins are reported over old ones
        # the corner case for this is a plugin that takes a long time to run has
        # config changed frequently
        for engine in engines:
            logger = engine.get_logger()
            fast_status, fast_status_id = engine.get_fast_status()
            if fast_status:
                fast_report.append((fast_status, fast_status_id))

            plugin_engine_id, plugin_full_status = engine.get_id(), engine.get_full_status()
            all_plugins.add(PluginNameVersion(plugin_full_status.pluginName, plugin_full_status.pluginVersion))
            now_seconds = time.monotonic()
            if plugin_engine_id in self.last_reported_status:
                last_status = self.last_reported_status[plugin_engine_id]
                report_interval_passed = (now_seconds - last_status[1]) > PLUGIN_REPORT_STATUS_INTERVAL_SECONDS
                status_changed = last_status[0] != plugin_full_status
                if status_changed or report_interval_passed:
                    self.last_reported_status[plugin_engine_id] = (plugin_full_status, now_seconds)
                    report.append(plugin_full_status)
                if status_changed:
                    logger.info('status changed for engine %s %s', plugin_engine_id, plugin_full_status)
            else:
                self.last_reported_status[plugin_engine_id] = (plugin_full_status, now_seconds)
                report.append(plugin_full_status)
                logger.info('new plugin status for engine %s %s', plugin_engine_id, plugin_full_status)

        for incompat_plugin in incompatible_plugins:
            plugin_id = incompat_plugin.name, incompat_plugin.version
            now_seconds = time.monotonic()
            plugin_full_status = PluginFullStatus(
                pluginName=incompat_plugin.reported_name,
                pluginVersion=incompat_plugin.version,
                state=PluginState.INCOMPATIBLE.value,
                description=repr(incompat_plugin.compatibility_errors),
                stacktrace="",
                monitoredEntityId=0
            )
            last_status, last_status_timestamp = self.last_reported_status.get(plugin_id, (plugin_full_status, 0))
            status_expired = (now_seconds - last_status_timestamp) > PLUGIN_REPORT_STATUS_INTERVAL_SECONDS
            status_changed = last_status != plugin_full_status
            if status_expired or status_changed:
                report.append(plugin_full_status)
                self.last_reported_status[plugin_id] = (plugin_full_status, now_seconds)
        
        for no_plugin in not_existing_plugins:
            plugin_full_status = PluginFullStatus(
                pluginName=no_plugin.name,
                pluginVersion="",
                state=PluginState.ERROR_CONFIG.value,
                description=DOES_NOT_EXIST,
                stacktrace="",
                monitoredEntityId=no_plugin.entity_id
            )
            if no_plugin.fast_id:
                fast_report.append((plugin_full_status, no_plugin.fast_id))
            else:
                report.append(plugin_full_status)

        all_logger = logging.getLogger("plugin_development.global")
        if all_logger.isEnabledFor(logging.DEBUG):
            all_logger.debug(pprint.pformat(report))
            all_logger.debug(pprint.pformat(self.last_reported_status))
            all_logger.debug(all_plugins)

        self.report_every_minute.do(all_logger.info, self.last_reported_status)
        if fast_report:
            all_logger.info(fast_report)
        self.external_api.report_plugins_status(report, fast_report, all_plugins)
