# -*- coding: utf-8 -*-

# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements.  See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License.  You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import logging
from multiprocessing import Process, Queue
from threading import Timer

from .base import Command
from shellbot.updaters import Updater
from shellbot.events import Message


class Audit(Command):
    """
    Checks and changes audit status

    In essence, audit starts with the capture of information in real-time,
    and continues with the replication of information.

    A typical use case is the monitoring of interactions happening in a space,
    for security reasons or for compliancy.

    The command Audit() has to be armed beforehand, meaning that it is
    provided with a callable function that can receive updates, and that
    it hooks the listener to filter all inbound traffic.

    The command itself allows for suspending or restarting the audit process.


    """

    keyword = u'audit'
    information_message = u'Check and change audit status'
    usage_message = u'audit [on|off]'

    disabled_message = u'Audit has not been enabled.'

    on_message = u'Chat interactions are currently audited.'
    off_message = u'Chat interactions are not audited.'
    already_on_message = u'Chat interactions are already audited.'
    already_off_message = u'Chat interactions are already private.'

    off_duration = 60  # after this time off, back to auditing on
    temporary_off_message = u"Please note that auditing will restart after {}"

    updater = None
    updater_ruler = u"<br >"

    _armed = False  # for tests only

    def execute(self, bot, arguments=None):
        """
        Checks and changes audit status

        :param bot: The bot for this execution
        :type bot: Shellbot

        :param arguments: either 'on' or 'off'
        :type arguments: str

        """
        if self.armed == False:
            bot.say(self.disabled_message)

        elif arguments == 'on':
            self.audit_on(bot)

        elif arguments =='off':
            self.audit_off(bot)

        elif arguments in (None, ''):
            self.audit_status(bot)

        else:
            bot.say(u"usage: {}".format(self.usage_message))

    def audit_on(self, bot):
        """
        Activates audit mode

        :param bot: The bot for this execution
        :type bot: Shellbot

        """
        if self.engine.get('audit.switch', 'off') == 'on':
            bot.say(self.already_on_message)
        else:
            self.say(u"{0} AUDIT ON {0}".format("====================="))
            self.engine.set('audit.switch', 'on')
            bot.say(self.on_message)

    def audit_off(self, bot):
        """
        Activates private mode

        :param bot: The bot for this execution
        :type bot: Shellbot

        """
        if self.engine.get('audit.switch', 'off') == 'on':
            self.say(u"{0} AUDIT OFF {0}".format("====================="))
            self.engine.set('audit.switch', 'off')
            bot.say(self.off_message)
            self.on_off(bot)
        else:
            bot.say(self.already_off_message)

    def audit_status(self, bot):
        """
        Reports on audit status

        :param bot: The bot for this execution
        :type bot: Shellbot

        """
        if self.engine.get('audit.switch', 'off') == 'on':
            bot.say(self.on_message)
        else:
            bot.say(self.off_message)

    def arm(self, updater):
        """
        Arms the auditing function

        :param updater: the function to be used on each update
        :type updater: callable

        """
        assert updater is not None
        self.updater = updater
        self.engine.listener.filter = self.filter

    @property
    def armed(self):
        """
        Are we ready for auditing or not?

        :rtype: bool
        """
        if self._armed:
            return True

        if self.updater is None:
            return False

        if self.engine.listener.filter != self.filter:
            return False

        return True

    def on_init(self):
        """
        Registers callback from bot
        """
        self.engine.subscribe('start', self)

    def on_start(self):
        """
        Reacts on bot start
        """
        logging.info(u"Activating real-time audit")
        self.engine.set('audit.switch', 'on')

    def on_off(self, bot):
        """
        Triggers watchdog when audit is disabled
        """
        if self.off_duration and self.off_duration > 0:

            bot.say(
                self.temporary_off_message.format(
                    str(self.off_duration)+' seconds'))

            logging.debug(u"- triggering watchdog timer")
            t = Timer(self.off_duration, self.watchdog)
            t.start()

    def watchdog(self):
        """
        Ensures that audit is restarted
        """
        logging.debug(u"Watchdog is checking audit status")
        if self.engine.get('audit.switch', 'off') == 'off':
            logging.debug(u"- restarting audit")
            self.engine.set('audit.switch', 'on')

    def filter(self, event):
        """
        Filters events handled by listener

        :param event: an event received by listener
        :type event: Event or Message or Attachment or Join or Leave, etc.

        :return: a filtered event

        This function implements the actual auditing of incoming events.
        """
        logging.debug(u"- filtering a {} event".format(event.type))
        try:
            if self.engine.get('audit.switch', 'off') == 'on':
                logging.debug(u"- {}".format(str(event)))
                self.updater.put(event)
            else:
                logging.debug(u"- audit has not been switched on")
        finally:
            return event

    def say(self, text):
        """
        Inserts a string in the auditing flow
        """
        logging.info(u"Audit says: {}".format(text))
        if not self.updater:
            logging.debug(u"- no updater")
            return

        message = Message({'text': text,
                           'from_id': self.engine.name,
                           'from_label': self.engine.name})
        self.updater.put(message)

