from django.dispatch import receiver
from django.db import IntegrityError
import logging

from oscar.core.loading import get_class, get_classes
from oscar.apps.search.signals import user_search
UserSearch, UserRecord, ProductRecord, UserProductView = get_classes(
    'analytics.models', ['UserSearch', 'UserRecord', 'ProductRecord',
                         'UserProductView'])
product_viewed = get_classes('catalogue.signals', ['product_viewed'])
basket_addition = get_class('basket.signals', 'basket_addition')
order_placed = get_class('order.signals', 'order_placed')

# Helpers

logger = logging.getLogger('oscar.analytics')


def _record_product_view(product):
    try:
        record, __ = ProductRecord.objects.get_or_create(product=product)
        record.num_views += 1
        record.save()
    except IntegrityError:
        # get_or_create sometimes fails due to MySQL's weird transactions, fail
        # silently
        logger.error("IntegrityError on ProductRecord.objects."
                     "get_or_create(product=product)")


def _record_user_product_view(user, product):
    if user.is_authenticated():
        # Update user record
        try:
            record, __ = UserRecord.objects.get_or_create(user=user)
            record.num_product_views += 1
            record.save()
        except IntegrityError:
            logger.error("IntegrityError on UserRecord.objects."
                         "get_or_create(user=user)")

        # Add user product view record
        UserProductView.objects.create(product=product, user=user)


def _record_basket_addition(product):
    try:
        record, __ = ProductRecord.objects.get_or_create(product=product)
        record.num_basket_additions += 1
        record.save()
    except IntegrityError:
        logger.error("IntegrityError on ProductRecord.objects."
                     "get_or_create(product=product)")


def _record_user_basket_addition(user, product):
    if user.is_authenticated():
        try:
            record, __ = UserRecord.objects.get_or_create(user=user)
            record.num_basket_additions += 1
            record.save()
        except IntegrityError:
            logger.error("IntegrityError on UserRecord.objects."
                         "get_or_create(user=user)")


def _record_products_in_order(order):
    for line in order.lines.all():
        try:
            record, __ = ProductRecord.objects.get_or_create(
                product=line.product)
            record.num_purchases += line.quantity
            record.save()
        except IntegrityError:
            logger.error("IntegrityError on ProductRecord.objects."
                         "get_or_create(product=product)")


def _record_user_order(user, order):
    if user.is_authenticated():
        try:
            record, __ = UserRecord.objects.get_or_create(user=user)
            record.num_orders += 1
            record.num_order_lines += order.num_lines
            record.num_order_items += order.num_items
            record.total_spent += order.total_incl_tax
            record.date_last_order = order.date_placed
            record.save()
        except IntegrityError:
            logger.error("IntegrityError on UserRecord.objects."
                         "get_or_create(user=user)")


def _record_user_search(user, query):
    if user.is_authenticated():
        UserSearch._default_manager.create(user=user, query=query)

# Receivers


@receiver(product_viewed)
def receive_product_view(sender, product, user, **kwargs):
    if kwargs.get('raw', False):
        return
    _record_product_view(product)
    _record_user_product_view(user, product)


@receiver(user_search)
def receive_product_search(sender, query, user, **kwargs):
    if kwargs.get('raw', False):
        return
    _record_user_search(user, query)


@receiver(basket_addition)
def receive_basket_addition(sender, product, user, **kwargs):
    if kwargs.get('raw', False):
        return
    _record_basket_addition(product)
    _record_user_basket_addition(user, product)


@receiver(order_placed)
def receive_order_placed(sender, order, user, **kwargs):
    if kwargs.get('raw', False):
        return
    _record_products_in_order(order)
    if user:
        _record_user_order(user, order)
