from django.apps import apps
from django.conf import settings
from django.db.models.signals import post_save
from django.dispatch import Signal, receiver
from notifications.signals import notify
from iconolab.mails import EmailManager
import logging

logger = logging.getLogger(__name__)

# Signal sent during method Annotation.validate_existing_revision to update stats
revision_accepted = Signal(providing_args=['instance'])
revision_rejected = Signal(providing_args=['instance'])
revision_created = Signal(providing_args=['instance'])

emailManager = EmailManager()

def increment_stats_on_new_revision(sender, instance, **kwargs):
    """
        Signal to increment stats on annotation when a revision is created
    """
    from iconolab.models import AnnotationRevision
    if sender == AnnotationRevision:
        if instance.parent_revision:
            # Annotation stats
            annotation = instance.annotation
            annotation.stats.submitted_revisions_count += 1
            if instance.state in [AnnotationRevision.ACCEPTED, AnnotationRevision.STUDIED]:
                annotation.stats.accepted_revisions_count += 1
            if instance.state == AnnotationRevision.ACCEPTED and instance.merge_parent_revision is not None and instance.merge_parent_revision.state == AnnotationRevision.STUDIED:
                annotation.stats.awaiting_revisions_count -= 1
            if instance.state in [AnnotationRevision.AWAITING]:
                annotation.stats.awaiting_revisions_count += 1
            annotation.stats.set_tags_stats()
            annotation.stats.save()
            # Image stats
            image = instance.annotation.image
            image.stats.submitted_revisions_count += 1
            image.stats.set_tags_stats()
            image.stats.save()

def increment_stats_on_new_comment(sender, instance, created, **kwargs):
    """
        Signal to increment stats on annotation when a comment is posted
    """
    from iconolab.models import IconolabComment
    if created and sender == IconolabComment:
        model = apps.get_model(instance.content_type.app_label,instance.content_type.model)
        object_pk = instance.object_pk
        annotation = model.objects.get(pk=object_pk)
        annotation.stats.comments_count +=1
        annotation.stats.save()
        annotation.image.stats.comments_count +=1
        annotation.image.stats.save()

def increment_stats_on_new_metacategory(sender, instance, created, **kwargs):
    """
        Signal to increment stats on annotation when a metacategory is linked to a comment (on comment creation)
    """
    from iconolab.models import MetaCategoryInfo, MetaCategoriesCountInfo
    if created and sender == MetaCategoryInfo:
        metacategory = instance.metacategory
        comment = instance.comment
        annotation = comment.annotation
        if metacategory not in annotation.stats.metacategories.all():
            MetaCategoriesCountInfo.objects.create(annotation_stats_obj=annotation.stats, metacategory=metacategory, count=1)
        else:
            m2m_object = MetaCategoriesCountInfo.objects.get(annotation_stats_obj=annotation.stats, metacategory=metacategory)
            m2m_object.count += 1
            m2m_object.save()

def increment_stats_on_accepted_revision(sender, instance, **kwargs):
    """
        Signal to increment stats on annotation when a revision is accepted
    """
    from iconolab.models import AnnotationRevision
    if sender == AnnotationRevision:
        annotation = instance.annotation
        annotation.stats.accepted_revisions_count += 1
        annotation.stats.awaiting_revisions_count -= 1
        annotation.stats.save()

def increment_stats_on_rejected_revision(sender, instance, **kwargs):
    """
        Signal to increment stats on annotation when a comment is rejected
    """
    from iconolab.models import AnnotationRevision
    if sender == AnnotationRevision:
        annotation = instance.annotation
        annotation.stats.awaiting_revisions_count -= 1
        annotation.stats.save()

def increment_annotations_count(sender, instance, created, **kwargs):
    """
        Signal to increment stats on image when an annotation is created
    """
    from iconolab.models import Annotation
    if created and sender == Annotation:
        image = instance.image
        image.stats.annotations_count += 1
        image.stats.submitted_revisions_count += 1
        image.stats.set_tags_stats()
        image.stats.save()


def update_annotation_validation_state(sender, instance, created, **kwargs):
    """
        Example signal to check if a metacategory added on an annotation is enough to make the annotation "validated"

        We listen to saves on the m2m for the metacategory count in annotation stats,
        check if the corresponding metacategory can affect the validation state,
        check if the state wasn't overriden, and compute the state again if checks were passed
    """
    from iconolab.models import MetaCategory, MetaCategoryInfo, MetaCategoriesCountInfo
    if sender == MetaCategoriesCountInfo:
        metacategory = instance.metacategory
        annotation = instance.annotation_stats_obj.annotation
        if metacategory.validation_value != MetaCategory.NEUTRAL and not annotation.validation_state_overriden:
            non_neutral_mtcgs_count = 0
            validating_mtcgs_count = 0
            unvalidating_mtcgs_count = 0
            # For each metacategory linked to annotation stats we check if it affects the validation state and keep track of counts if this is the case
            for metacategory_infos in annotation.stats.metacategoriescountinfo_set.all():
                if metacategory_infos.metacategory.validation_value == MetaCategory.AGREEMENT:
                    validating_mtcgs_count += metacategory_infos.count
                    non_neutral_mtcgs_count += metacategory_infos.count
                if metacategory_infos.metacategory.validation_value == MetaCategory.DISAGREEMENT:
                    unvalidating_mtcgs_count += metacategory_infos.count
                    non_neutral_mtcgs_count += metacategory_infos.count
            if non_neutral_mtcgs_count > 3 and (validating_mtcgs_count / non_neutral_mtcgs_count > 0.6):
                annotation.validation_state = True
                annotation.save()
            if non_neutral_mtcgs_count > 3 and (validating_mtcgs_count / non_neutral_mtcgs_count < 0.4):
                annotation.validation_state = False
                annotation.save()

def notify_users_on_new_comment(sender, instance, **kwargs):
    """
        Signal to notify users when a comment is created. Notified users are: annotation author, parent comment author
    """
    from iconolab.models import IconolabComment, Annotation, MetaCategory, MetaCategoryInfo
    if sender == IconolabComment and instance.content_type.app_label == 'iconolab' and instance.content_type.model == 'annotation':
        comment_annotation = Annotation.objects.get(id=instance.object_pk)
        # Notifying new user comment
        if instance.thread_id:
            notified_author = False
            if instance.level > 0: # We check parent_id as django comment xtd saves comments in two steps and only set the information we need in the second step
                parent_comment = IconolabComment.objects.get(id=instance.parent_id)
                if parent_comment.user != instance.user:
                    emailManager.new_reply(parent_comment.user)
                    notify.send(instance.user, recipient=parent_comment.user, verb=EmailManager.VERB_NEW_REPLY, action_object=instance, target=comment_annotation, emailed=True)
                    if parent_comment.user == comment_annotation.author:
                        notified_author = True
            if instance.user != comment_annotation.author and not notified_author:
                emailManager.new_comment(comment_annotation.author)
                notify.send(instance.user, recipient=comment_annotation.author, verb=EmailManager.VERB_NEW_COMMENT, action_object=instance, target=comment_annotation, emailed=True)

def notify_users_on_metacategory(sender, instance, created, **kwargs):
    """
        Signal to notify users when a comment is created. Notified users are: annotation author, parent comment author
    """
    from iconolab.models import MetaCategory, MetaCategoryInfo, Annotation, UserProfile
    if sender == MetaCategoryInfo and created:
        related_metacategory = instance.metacategory
        related_comment = instance.comment
        if related_comment.content_type.app_label == "iconolab" and related_comment.content_type.model == "annotation":
            comment_annotation = Annotation.objects.prefetch_related("image__item__collection").get(id=related_comment.object_pk)
            if related_metacategory.triggers_notifications == MetaCategory.COMMENTERS:
                for commenter in comment_annotation.stats.commenters.exclude(id=related_comment.user.id).all():
                    emailManager.request_for_contribution(commenter)
                    notify.send(related_comment.user, recipient=commenter, verb=EmailManager.VERB_REQUEST_FOR_CONTRIBUTION, action_object=related_comment, target=comment_annotation, emailed=True)
            elif related_metacategory.triggers_notifications == MetaCategory.CONTRIBUTORS:
                for contributor in comment_annotation.stats.contributors.exclude(id=related_comment.user.id).all():
                    emailManager.request_for_contribution(contributor)
                    notify.send(related_comment.user, recipient=contributor, verb=EmailManager.VERB_REQUEST_FOR_CONTRIBUTION, action_object=related_comment, target=comment_annotation, emailed=True)
            if related_metacategory.triggers_notifications == MetaCategory.COLLECTION_ADMINS:
                for collection_admin in comment_annotation.image.item.collection.admins.all():
                    emailManager.request_for_expertise(collection_admin.user)
                    notify.send(related_comment.user, recipient=collection_admin.user, verb=EmailManager.VERB_REQUEST_FOR_EXPERTISE, action_object=related_comment, target=comment_annotation, emailed=True)

def notify_users_on_new_revision(sender, instance, **kwargs):
    from iconolab.models import AnnotationRevision
    if sender == AnnotationRevision:
        if instance.author != instance.annotation.author:
            emailManager.new_revision(instance.annotation.author)
            notify.send(instance.author, recipient=instance.annotation.author, verb=EmailManager.VERB_NEW_ANNOTATION, action_object=instance, target=instance.annotation, emailed=True)

def notify_users_on_accepted_revision(sender, instance, **kwargs):
    from iconolab.models import AnnotationRevision
    if sender == AnnotationRevision:
        if instance.author != instance.annotation.author and instance.state in [AnnotationRevision.ACCEPTED, AnnotationRevision.STUDIED]:
            emailManager.accepted_revision(instance.author)
            notify.send(instance.annotation.author, recipient=instance.author, verb=EmailManager.VERB_ACCEPTED_REVISION, action_object=instance, target=instance.annotation, emailed=True)

def create_user_profile(sender, instance, created, **kwargs):
    from iconolab.models import UserProfile
    from django.contrib.auth.models import User
    if sender == User and created:
        UserProfile.objects.create(user=instance)

# User profile connect
post_save.connect(create_user_profile)

# Stats handlers connect
post_save.connect(increment_annotations_count)
post_save.connect(increment_stats_on_new_comment)
post_save.connect(increment_stats_on_new_metacategory)
post_save.connect(update_annotation_validation_state)
revision_created.connect(increment_stats_on_new_revision)
revision_accepted.connect(increment_stats_on_accepted_revision)
revision_rejected.connect(increment_stats_on_rejected_revision)
# Notifications handlers connect
post_save.connect(notify_users_on_new_comment)
post_save.connect(notify_users_on_metacategory)
revision_created.connect(notify_users_on_new_revision)
revision_accepted.connect(notify_users_on_accepted_revision)

