"""
sentry.tasks.post_process
~~~~~~~~~~~~~~~~~~~~~~~~~

:copyright: (c) 2010-2014 by the Sentry Team, see AUTHORS for more details.
:license: BSD, see LICENSE for more details.
"""

from __future__ import absolute_import, print_function

import logging

from django.conf import settings
from hashlib import md5

from sentry.plugins import plugins
from sentry.rules import EventState, rules
from sentry.tasks.base import instrumented_task
from sentry.utils.cache import cache
from sentry.utils.safe import safe_execute


rules_logger = logging.getLogger('sentry.errors')


def condition_matches(project, condition, event, state, rule):
    condition_cls = rules.get(condition['id'])
    if condition_cls is None:
        rules_logger.error('Unregistered condition %r', condition['id'])
        return

    condition_inst = condition_cls(project, data=condition, rule=rule)
    return safe_execute(condition_inst.passes, event, state)


def get_rules(project):
    from sentry.models import Rule

    cache_key = 'project:%d:rules' % (project.id,)
    rules_list = cache.get(cache_key)
    if rules_list is None:
        rules_list = list(Rule.objects.filter(project=project))
        cache.set(cache_key, rules_list, 60)
    return rules_list


@instrumented_task(
    name='sentry.tasks.post_process.post_process_group')
def post_process_group(event, is_new, is_regression, is_sample, **kwargs):
    """
    Fires post processing hooks for a group.
    """
    from sentry.models import GroupRuleStatus, Project

    project = Project.objects.get_from_cache(id=event.group.project_id)

    if settings.SENTRY_ENABLE_EXPLORE_CODE:
        record_affected_code.delay(event=event)

    if settings.SENTRY_ENABLE_EXPLORE_USERS:
        record_affected_user.delay(event=event)

    for plugin in plugins.for_project(project):
        plugin_post_process_group.apply_async(
            kwargs={
                'plugin_slug': plugin.slug,
                'event': event,
                'is_new': is_new,
                'is_regresion': is_regression,
                'is_sample': is_sample,
            },
            expires=300,
        )

    for rule in get_rules(project):
        match = rule.data.get('action_match', 'all')
        condition_list = rule.data.get('conditions', ())

        if not condition_list:
            continue

        # TODO(dcramer): this might not make sense for other rule actions
        # so we should find a way to abstract this into actions
        # TODO(dcramer): this isnt the most efficient query pattern for this
        rule_status, _ = GroupRuleStatus.objects.get_or_create(
            rule=rule,
            group=event.group,
            defaults={
                'project': project,
                'status': GroupRuleStatus.INACTIVE,
            },
        )

        state = EventState(
            is_new=is_new,
            is_regression=is_regression,
            is_sample=is_sample,
            rule_is_active=rule_status.status == GroupRuleStatus.ACTIVE,
        )

        condition_iter = (
            condition_matches(project, c, event, state, rule)
            for c in condition_list
        )

        if match == 'all':
            passed = all(condition_iter)
        elif match == 'any':
            passed = any(condition_iter)
        elif match == 'none':
            passed = not any(condition_iter)
        else:
            rules_logger.error('Unsupported action_match %r for rule %d',
                               match, rule.id)
            continue

        if passed and rule_status.status == GroupRuleStatus.INACTIVE:
            # we only fire if we're able to say that the state has changed
            GroupRuleStatus.objects.filter(
                id=rule_status.id,
                status=GroupRuleStatus.INACTIVE,
            ).update(status=GroupRuleStatus.ACTIVE)
        elif not passed and rule_status.status == GroupRuleStatus.ACTIVE:
            # update the state to suggest this rule can fire again
            GroupRuleStatus.objects.filter(
                id=rule_status.id,
                status=GroupRuleStatus.ACTIVE,
            ).update(status=GroupRuleStatus.INACTIVE)

        if passed:
            execute_rule.apply_async(
                kwargs={
                    'rule_id': rule.id,
                    'event': event,
                    'state': state,
                },
                expires=120,
            )


@instrumented_task(
    name='sentry.tasks.post_process.execute_rule')
def execute_rule(rule_id, event, state):
    """
    Fires post processing hooks for a rule.
    """
    from sentry.models import Project, Rule

    rule = Rule.objects.get(id=rule_id)
    project = Project.objects.get_from_cache(id=event.project_id)
    event.project = project
    event.group.project = project

    for action in rule.data.get('actions', ()):
        action_cls = rules.get(action['id'])
        if action_cls is None:
            rules_logger.error('Unregistered action %r', action['id'])
            continue

        action_inst = action_cls(project, data=action, rule=rule)
        safe_execute(action_inst.after, event=event, state=state)


@instrumented_task(
    name='sentry.tasks.post_process.plugin_post_process_group',
    stat_suffix=lambda plugin_slug, *a, **k: plugin_slug)
def plugin_post_process_group(plugin_slug, event, **kwargs):
    """
    Fires post processing hooks for a group.
    """
    plugin = plugins.get(plugin_slug)
    safe_execute(plugin.post_process, event=event, group=event.group, **kwargs)


@instrumented_task(
    name='sentry.tasks.post_process.record_affected_user')
def record_affected_user(event, **kwargs):
    from sentry.models import Group

    logger = record_affected_user.get_logger()

    if not settings.SENTRY_ENABLE_EXPLORE_USERS:
        logger.info('Skipping sentry:user tag due to SENTRY_ENABLE_EXPLORE_USERS')
        return

    user_ident = event.user_ident
    if not user_ident:
        logger.info('No user data found for event_id=%s', event.event_id)
        return

    user_data = event.data.get('sentry.interfaces.User', event.data.get('user', {}))

    tag_data = {}
    for key in ('id', 'email', 'username', 'data'):
        value = user_data.get(key)
        if value:
            tag_data[key] = value
    tag_data['ip'] = event.ip_address

    Group.objects.add_tags(event.group, [
        ('sentry:user', user_ident, tag_data)
    ])


@instrumented_task(
    name='sentry.tasks.post_process.record_affected_code')
def record_affected_code(event, **kwargs):
    from sentry.models import Group

    if not settings.SENTRY_ENABLE_EXPLORE_CODE:
        return

    data = event.interfaces.get('sentry.interfaces.Exception')
    if not data:
        return

    checksum = lambda x: md5(x).hexdigest()

    tags = []
    for exception in data:
        if not exception.stacktrace:
            continue

        for frame in exception.stacktrace:
            # we only tag explicit app frames to avoid excess fat
            if not frame.in_app:
                continue

            filename = frame.filename or frame.module
            if not filename:
                continue

            tags.append((
                'sentry:filename',
                checksum(filename),
                {'filename': filename},
            ))

            function = frame.function
            if function:
                tags.append((
                    'sentry:function',
                    checksum('%s:%s' % (filename, function)),
                    {'filename': filename, 'function': function}
                ))

    if tags:
        Group.objects.add_tags(event.group, tags)
