# Copyright 2015 Carnegie Mellon University
#
# Author: Han Chen <hanc@andrew.cmu.edu>
#
# Licensed 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 ast
import json
from oslo.config import cfg
from stevedore import driver

from monasca.common import es_conn
from monasca.common import email_sender
from monasca.common import kafka_conn
from monasca.openstack.common import log
from monasca.openstack.common import service as os_service

es_opts = [
    cfg.StrOpt('topic',
               default='alarm',
               help=('The topic that messages will be retrieved from.'
                     'This also will be used as a doc type when saved '
                     'to ElasticSearch.')),

    cfg.StrOpt('topic2',
               default='notification_methods',
               help=('The topic that messages will be retrieved from.'
                     'This also will be used as a doc type when saved '
                     'to ElasticSearch.')),

    cfg.StrOpt('doc_type',
               default='',
               help=('The document type which defines what document '
                     'type the messages will be save into. If not '
                     'specified, then the topic will be used.')),
    cfg.StrOpt('processor',
               default='',
               help=('The message processer to load to process the message.'
                     'If the message does not need to be process anyway,'
                     'leave the default')),
]

es_group = cfg.OptGroup(name='notification', title='notification')
cfg.CONF.register_group(es_group)
cfg.CONF.register_opts(es_opts, es_group)

LOG = log.getLogger(__name__)


class NotificationEngine(os_service.Service):
    def __init__(self, threads=1000):
        super(NotificationEngine, self).__init__(threads)
        self._kafka_conn = kafka_conn.KafkaConnection(
            cfg.CONF.notification.topic)

        # Use doc_type if it is defined.
        if cfg.CONF.notification.doc_type:
            self._es_conn = es_conn.ESConnection(
                cfg.CONF.notification.doc_type)
        else:
            self._es_conn = es_conn.ESConnection(
                cfg.CONF.notification.topic2)

    def handle_alarm_msg(self, msg):
        if msg and msg.message:
            LOG.debug("Message received for alarm: " + msg.message.value)
            value = msg.message.value
            if value:
                # value's format is:
                # {
                #   "metrics": {
                #     "timestamp": 1432672915.409,
                #     "name": "biz",
                #     "value": 1500,
                #     "dimensions": {
                #       "key2": "value2",
                #       "key1": "value1"
                #     }
                #   },
                #   "state_updated_timestamp": 1432672915,
                #   "state": "ALARM",
                #   "alarm-definition": {
                #     "alarm_actions": [
                #       "c60ec47e-5038-4bf1-9f95-4046c6e9a759"
                #     ],
                #     "undetermined_actions": [
                #       "c60ec47e-5038-4bf1-9f95-4046c6e9a759"
                #     ],
                #     "name": "Average CPU percent greater than 10",
                #     "match_by": [
                #       "hostname"
                #     ],
                #     "description": "The average CPU percent is greater than 10",
                #     "ok_actions": [
                #       "c60ec47e-5038-4bf1-9f95-4046c6e9a759"
                #     ],
                #     "expression": "max(foo{hostname=mini-mon,mu=na}, 120) > 1100
                #                   and max(bar { asd = asd} )>1200 or avg(biz)>1300",
                #     "id": "c60ec47e-5038-4bf1-9f95-4046c6e91111",
                #     "severity": "LOW"
                #   }
                # }

                # convert to dict, and get state to determine the actions(notification method id) needed.
                # the method id can be used to match the notification method in elasticSearch
                # Then an email will be sent (TODO: phone txt msg are not dealt with for now)
                dict_msg = ast.literal_eval(value)
                state = dict_msg["state"]
                if state not in ["ALARM","OK","UNDETERMINED"]:
                    LOG.error("state of alarm is not defined as expected")
                    return

                actions = []
                if state == 'ALARM':
                    actions = dict_msg["alarm-definition"]["alarm_actions"]
                if state == 'OK':
                    actions = dict_msg["alarm-definition"]["ok_actions"]
                if state == 'UNDETERMINED':
                    actions = dict_msg["alarm-definition"]["undetermined_actions"]

                addresses = []
                types = []
                # the action_id is an id of notification method
                # there can be multiple ids in one alarm message with different types
                for action_id in actions:

                    es_res = self._es_conn.get_message_by_id(action_id)

                    def _get_notification_method_response(res):
                        if res and res.status_code == 200:
                            obj = res.json()
                            if obj:
                                return obj.get('hits')
                            return None
                        else:
                            return None

                    es_res = _get_notification_method_response(es_res)

                    LOG.debug('Query to ElasticSearch returned: %s' % es_res)

                    if es_res is None:
                        LOG.error("The provided is not defined as expected")
                        return

                    name = es_res["hits"][0]["_source"]["name"]
                    type = es_res["hits"][0]["_source"]["type"]
                    address = es_res["hits"][0]["_source"]["address"]

                    types.append(type)
                    addresses.append(address)

                email_addresses = []
                for i in range(len(types)):
                    if types[i] == "EMAIL":
                        email_addresses.append(addresses[i])

                email_sender.send_emails(email_addresses, "Alarm to User", dict_msg["alarm-definition"]["description"])

    def start(self):
        while True:
            try:
                for msg in self._kafka_conn.get_messages():
                    self.handle_alarm_msg(msg)

                # if autocommit is set, this will be a no-op call.
                self._kafka_conn.commit()
            except Exception:
                LOG.exception('Error occurred while handling kafka messages.')

    def stop(self):
        self._kafka_conn.close()
        super(NotificationEngine, self).stop()

[(53, 'oslo.config.cfg.OptGroup', 'cfg.OptGroup', ([], {'name': '"""notification"""', 'title': '"""notification"""'}), False, 'from oslo.config import cfg\n'), (54, 'oslo.config.cfg.CONF.register_group', 'cfg.CONF.register_group', (['es_group'], {}), False, 'from oslo.config import cfg\n'), (55, 'oslo.config.cfg.CONF.register_opts', 'cfg.CONF.register_opts', (['es_opts', 'es_group'], {}), False, 'from oslo.config import cfg\n'), (57, 'monasca.openstack.common.log.getLogger', 'log.getLogger', (['__name__'], {}), False, 'from monasca.openstack.common import log\n'), (29, 'oslo.config.cfg.StrOpt', 'cfg.StrOpt', (['"""topic"""'], {'default': '"""alarm"""', 'help': '"""The topic that messages will be retrieved from.This also will be used as a doc type when saved to ElasticSearch."""'}), False, 'from oslo.config import cfg\n'), (35, 'oslo.config.cfg.StrOpt', 'cfg.StrOpt', (['"""topic2"""'], {'default': '"""notification_methods"""', 'help': '"""The topic that messages will be retrieved from.This also will be used as a doc type when saved to ElasticSearch."""'}), False, 'from oslo.config import cfg\n'), (41, 'oslo.config.cfg.StrOpt', 'cfg.StrOpt', (['"""doc_type"""'], {'default': '""""""', 'help': '"""The document type which defines what document type the messages will be save into. If not specified, then the topic will be used."""'}), False, 'from oslo.config import cfg\n'), (46, 'oslo.config.cfg.StrOpt', 'cfg.StrOpt', (['"""processor"""'], {'default': '""""""', 'help': '"""The message processer to load to process the message.If the message does not need to be process anyway,leave the default"""'}), False, 'from oslo.config import cfg\n'), (63, 'monasca.common.kafka_conn.KafkaConnection', 'kafka_conn.KafkaConnection', (['cfg.CONF.notification.topic'], {}), False, 'from monasca.common import kafka_conn\n'), (68, 'monasca.common.es_conn.ESConnection', 'es_conn.ESConnection', (['cfg.CONF.notification.doc_type'], {}), False, 'from monasca.common import es_conn\n'), (71, 'monasca.common.es_conn.ESConnection', 'es_conn.ESConnection', (['cfg.CONF.notification.topic2'], {}), False, 'from monasca.common import es_conn\n'), (117, 'ast.literal_eval', 'ast.literal_eval', (['value'], {}), False, 'import ast\n'), (168, 'monasca.common.email_sender.send_emails', 'email_sender.send_emails', (['email_addresses', '"""Alarm to User"""', "dict_msg['alarm-definition']['description']"], {}), False, 'from monasca.common import email_sender\n')]