import json
import logging
import os
import requests
import traceback
import ast

LOG = logging.getLogger('alerta.plugins.slack')

try:
    from jinja2 import Template
except Exception as e:
    LOG.error('SLACK: ERROR - Jinja template error: %s, template functionality will be unavailable', e)

try:
    from alerta.plugins import app  # alerta >= 5.0
except ImportError:
    from alerta.app import app  # alerta < 5.0

from alerta.plugins import PluginBase

LOG = logging.getLogger('alerta.plugins.slack')

SLACK_ATTACHMENTS = True if os.environ.get(
    'SLACK_ATTACHMENTS', 'False') == 'True' else app.config.get('SLACK_ATTACHMENTS', False)
try:
    SLACK_CHANNEL_ENV_MAP = json.loads(
        os.environ.get('SLACK_CHANNEL_ENV_MAP'))
except Exception as e:
    SLACK_CHANNEL_ENV_MAP = app.config.get('SLACK_CHANNEL_ENV_MAP', dict())

try:
    SLACK_CHANNEL_EVENT_MAP = json.loads(
        os.environ.get('SLACK_CHANNEL_EVENT_MAP'))
except Exception as e:
    SLACK_CHANNEL_EVENT_MAP = app.config.get('SLACK_CHANNEL_EVENT_MAP', dict())

try:
    SLACK_CHANNEL_SEVERITY_MAP = json.loads(
        os.environ.get('SLACK_CHANNEL_SEVERITY_MAP'))
except Exception as e:
    SLACK_CHANNEL_SEVERITY_MAP = app.config.get('SLACK_CHANNEL_SEVERITY_MAP', dict())

try:
    SLACK_CHANNEL_MAP = json.loads(
        os.environ.get('SLACK_CHANNEL_MAP'))
except Exception as e:
    SLACK_CHANNEL_MAP = app.config.get('SLACK_CHANNEL_MAP', dict())

try:
    SLACK_SEVERITY_FILTER = ast.literal_eval(
        os.environ.get('SLACK_SEVERITY_FILTER'))
except Exception as e:
    SLACK_SEVERITY_FILTER = app.config.get('SLACK_SEVERITY_FILTER', list())

SLACK_SEND_ON_ACK = os.environ.get(
    'SLACK_SEND_ON_ACK') or app.config.get('SLACK_SEND_ON_ACK', False)
SLACK_SEVERITY_MAP = app.config.get('SLACK_SEVERITY_MAP', {})
SLACK_DEFAULT_SEVERITY_MAP = {'security': '#000000', # black
                              'critical': '#FF0000', # red
                              'major': '#FFA500', # orange
                              'minor': '#FFFF00', # yellow
                              'warning': '#1E90FF', #blue
                              'informational': '#808080', #gray
                              'debug': '#808080', # gray
                              'trace': '#808080', # gray
                              'ok': '#00CC00'} # green
SLACK_DEFAULT_SUMMARY_FMT='*[{status}] {environment} {service} {severity}* - _{event} on {resource}_ <{dashboard}/#/alert/{alert_id}|{short_id}>'
SLACK_HEADERS = {
    'Content-Type': 'application/json'
}


class ServiceIntegration(PluginBase):

    def __init__(self, name=None):
        # override user-defined severities
        self._severities = SLACK_DEFAULT_SEVERITY_MAP
        self._severities.update(SLACK_SEVERITY_MAP)

        super(ServiceIntegration, self).__init__(name)

    def pre_receive(self, alert):
        return alert

    def _format_template(self, templateFmt, templateVars):
        try:
            LOG.debug('SLACK: generating template: %s' % templateFmt)
            template = Template(templateFmt)
        except Exception as e:
            LOG.error('SLACK: ERROR - Template init failed: %s', e)
            return

        try:
            LOG.debug('SLACK: rendering template: %s' % templateFmt)
            LOG.debug('SLACK: rendering variables: %s' % templateVars)
            return template.render(**templateVars)
        except Exception as e:
            LOG.error('SLACK: ERROR - Template render failed: %s', e)
            return

    def _slack_prepare_payload(self, alert, status=None, text=None, **kwargs):
        SLACK_CHANNEL = self.get_config('SLACK_CHANNEL', default='', type=str, **kwargs)
        SLACK_SUMMARY_FMT = self.get_config('SLACK_SUMMARY_FMT', type=str, **kwargs)  # Message summary format
        SLACK_PAYLOAD = self.get_config('SLACK_PAYLOAD', type=str, **kwargs)  # Full API control
        ICON_EMOJI = self.get_config('ICON_EMOJI', type=str, **kwargs)
        ALERTA_USERNAME = self.get_config('ALERTA_USERNAME', default='alerta', type=str, **kwargs)
        DASHBOARD_URL = self.get_config('DASHBOARD_URL', default='', type=str, **kwargs)
        SLACK_TOKEN = self.get_config('SLACK_TOKEN', type=str, **kwargs)
        if SLACK_TOKEN:
            SLACK_HEADERS['Authorization'] = 'Bearer ' + SLACK_TOKEN

        if alert.severity in self._severities:
            color = self._severities[alert.severity]
        else:
            color = '#00CC00'  # green
        channel = SLACK_CHANNEL_SEVERITY_MAP.get(alert.severity, SLACK_CHANNEL)
        if SLACK_CHANNEL_SEVERITY_MAP.get(alert.severity):
            LOG.debug("Found severity mapping. Channel: %s" % channel)
        else:
            LOG.debug("No severity mapping. Channel: %s" % channel)
        channel = SLACK_CHANNEL_ENV_MAP.get(alert.environment, channel)
        if SLACK_CHANNEL_ENV_MAP.get(alert.environment):
            LOG.debug("Found env mapping. Channel: %s" % channel)
        else:
            LOG.debug("No env mapping. Channel: %s" % channel)
        channel = SLACK_CHANNEL_EVENT_MAP.get(alert.event, channel)
        if SLACK_CHANNEL_EVENT_MAP.get(alert.event):
            LOG.debug("Found event mapping. Channel: %s" % channel)
        else:
            LOG.debug("No event mapping. Channel: %s" % channel)
        channel = SLACK_CHANNEL_MAP.get(alert.environment, dict()).get(alert.severity, channel)
        if SLACK_CHANNEL_MAP.get(alert.environment, dict()).get(alert.severity, channel):
            LOG.debug("Found env-severity mapping. Channel: %s" % channel)
        else:
            LOG.debug("No env-severity mapping. Channel: %s" % channel)

        templateVars = {
            'alert': alert,
            'status': status if status else alert.status,
            'config': app.config,
            'color': color,
            'channel': channel,
            'emoji': ICON_EMOJI,
        }

        if SLACK_PAYLOAD:
            LOG.debug("Formatting with slack payload template")
            formattedPayload = self._format_template(json.dumps(SLACK_PAYLOAD), templateVars).replace('\n', '\\n')
            LOG.debug("Formatted slack payload:\n%s" % formattedPayload)
            payload = json.loads(formattedPayload)
        else:
            if type(SLACK_SUMMARY_FMT) is str:
                summary = self._format_template(SLACK_SUMMARY_FMT, templateVars)
            else:
                summary = SLACK_DEFAULT_SUMMARY_FMT.format(
                    status=alert.status.capitalize(),
                    environment=alert.environment.upper(),
                    service=','.join(alert.service),
                    severity=alert.severity.capitalize(),
                    event=alert.event,
                    resource=alert.resource,
                    alert_id=alert.id,
                    short_id=alert.get_id(short=True),
                    dashboard=DASHBOARD_URL
                )
            payload = {}
            payload['username'] = ALERTA_USERNAME
            payload['channel'] = channel
            payload['text'] = summary
            if ICON_EMOJI:
                payload['icon_emoji'] = ICON_EMOJI
            if SLACK_ATTACHMENTS:
                payload['attachments'] = [{
                    "fallback": summary,
                    "color": color,
                    "fields": [
                        {"title": "Status", "value": (status if status else alert.status).capitalize(),
                            "short": True},
                        {"title": "Environment",
                            "value": alert.environment, "short": True},
                        {"title": "Resource", "value": alert.resource, "short": True},
                        {"title": "Services", "value": ", ".join(
                            alert.service), "short": True}
                    ]
                }]

        return payload

    def post_receive(self, alert, **kwargs):
        SLACK_WEBHOOK_URL = self.get_config('SLACK_WEBHOOK_URL', type=str, **kwargs)

        if alert.repeat:
            return

        if alert.severity in SLACK_SEVERITY_FILTER:
            LOG.debug("Alert severity %s is included in SLACK_SEVERITY_FILTER list, thus it will not be forwarded to Slack." % alert.severity)
            return

        if alert.severity in ['ok', 'normal', 'cleared', app.config.get('DEFAULT_NORMAL_SEVERITY')] and alert.previous_severity in SLACK_SEVERITY_FILTER:
            LOG.debug("Alert severity is %s but previous_severity was %s (included in SLACK_SEVERITY_FILTER list), thus it will not be forwarded to Slack." % (alert.severity, alert.previous_severity))
            return

        try:
            payload = self._slack_prepare_payload(alert, **kwargs)
            LOG.debug('Slack payload: %s', payload)
        except Exception as e:
            LOG.error('Exception formatting payload: %s\n%s' % (e, traceback.format_exc()))
            return

        try:
            r = requests.post(SLACK_WEBHOOK_URL,
                              data=json.dumps(payload), headers=SLACK_HEADERS, timeout=2)
        except Exception as e:
            raise RuntimeError("Slack connection error: %s", e)

        LOG.debug('Slack response: %s\n%s' % (r.status_code, r.text))

    def status_change(self, alert, status, text, **kwargs):
        SLACK_WEBHOOK_URL = self.get_config('SLACK_WEBHOOK_URL', type=str, **kwargs)

        if SLACK_SEND_ON_ACK == False or status not in ['ack', 'assign']:
            return

        try:
            payload = self._slack_prepare_payload(alert, status, text, **kwargs)

            LOG.debug('Slack payload: %s', payload)
        except Exception as e:
            LOG.error('Exception formatting payload: %s\n%s' % (e, traceback.format_exc()))
            return

        try:
            r = requests.post(SLACK_WEBHOOK_URL,
                              data=json.dumps(payload), headers=SLACK_HEADERS, timeout=2)
        except Exception as e:
            raise RuntimeError("Slack connection error: %s", e)

        LOG.debug('Slack response: %s\n%s' % (r.status_code, r.text))