from itertools import groupby

from django.utils import timezone
from django.db import models
from django_softdelete.models import SoftDeleteModel

from authentication.models import ExtendedUser
from inventory.models import Event, Item
from django.db.models.signals import post_save, pre_save
from django.dispatch import receiver

STATE_CHOICES = (
    ('pending_new', 'New'),
    ('pending_open', 'Open'),
    ('pending_shipping', 'Needs to be shipped'),
    ('pending_physical_confirmation', 'Needs to be confirmed physically'),
    ('pending_return', 'Needs to be returned'),
    ('pending_postponed', 'Postponed'),
    ('waiting_details', 'Waiting for details'),
    ('waiting_pre_shipping', 'Waiting for Address/Shipping Info'),
    ('closed_returned', 'Closed: Returned'),
    ('closed_shipped', 'Closed: Shipped'),
    ('closed_not_found', 'Closed: Not found'),
    ('closed_not_our_problem', 'Closed: Not our problem'),
    ('closed_duplicate', 'Closed: Duplicate'),
    ('closed_timeout', 'Closed: Timeout'),
    ('closed_spam', 'Closed: Spam'),
    ('closed_nothing_missing', 'Closed: Nothing missing'),
    ('closed_wtf', 'Closed: WTF'),
    ('found_open', 'Item Found and stored externally'),
    ('found_closed', 'Item Found and stored externally and closed'),
)

RELATION_STATUS_CHOICES = (
    ('possible', 'Possible'),
    ('confirmed', 'Confirmed'),
    ('discarded', 'Discarded'),
    ('provided', 'Provided'),
)


class IssueThread(SoftDeleteModel):
    id = models.AutoField(primary_key=True)
    uuid = models.CharField(max_length=255, unique=True, null=False, blank=False)
    name = models.CharField(max_length=255)
    event = models.ForeignKey(Event, null=True, on_delete=models.SET_NULL, related_name='issue_threads')
    manually_created = models.BooleanField(default=False)

    def short_uuid(self):
        return self.uuid[:8]

    @property
    def state(self):
        try:
            state_changes = sorted(self.state_changes.all(), key=lambda x: x.timestamp, reverse=True)
            if state_changes:
                return state_changes[0].state
            else:
                return None
        except AttributeError:
            return 'none'

    @state.setter
    def state(self, value):
        if self.state == value:
            return
        self.state_changes.create(state=value)
        if value == 'closed_spam' and self.emails.exists():
            self.emails.first().train_spam()

    @property
    def assigned_to(self):
        try:
            assignments = sorted(self.assignments.all(), key=lambda x: x.timestamp, reverse=True)
            if assignments:
                return assignments[0].assigned_to
            else:
                return None
        except AttributeError:
            return None

    @assigned_to.setter
    def assigned_to(self, value):
        if self.assigned_to == value:
            return
        self.assignments.create(assigned_to=value)

    @property
    def related_items(self):
        groups = groupby(self.item_relation_changes.all(), lambda rel: rel.item.id)
        return [sorted(v, key=lambda r: r.timestamp)[0].item for k, v in groups]

    def __str__(self):
        return '[' + str(self.id) + '][' + self.short_uuid() + '] ' + self.name

    class Meta:
        permissions = [
            ('send_mail', 'Can send mail'),
            ('add_issuethread_manual', 'Can add issue thread manually'),
            ('assign_issuethread', 'Can assign issue thread'),
        ]


@receiver(pre_save, sender=IssueThread)
def set_uuid(sender, instance, **kwargs):
    import uuid
    if instance.uuid is None or instance.uuid == '':
        instance.uuid = str(uuid.uuid4())


@receiver(post_save, sender=IssueThread)
def create_issue_thread(sender, instance, created, **kwargs):
    if created:
        StateChange.objects.create(issue_thread=instance, state='pending_new')


class Comment(models.Model):
    id = models.AutoField(primary_key=True)
    issue_thread = models.ForeignKey(IssueThread, on_delete=models.CASCADE, related_name='comments')
    comment = models.TextField()
    timestamp = models.DateTimeField(auto_now_add=True)

    def __str__(self):
        return str(self.issue_thread) + ' comment #' + str(self.id)


class StateChange(models.Model):
    id = models.AutoField(primary_key=True)
    issue_thread = models.ForeignKey(IssueThread, on_delete=models.CASCADE, related_name='state_changes')
    state = models.CharField(max_length=255, choices=STATE_CHOICES, default='pending_new')
    timestamp = models.DateTimeField(auto_now_add=True)

    def __str__(self):
        return str(self.issue_thread) + ' state change to ' + self.state


class Assignment(models.Model):
    id = models.AutoField(primary_key=True)
    issue_thread = models.ForeignKey(IssueThread, on_delete=models.CASCADE, related_name='assignments')
    assigned_to = models.ForeignKey(ExtendedUser, on_delete=models.CASCADE, related_name='assigned_tickets')
    timestamp = models.DateTimeField(auto_now_add=True)

    def __str__(self):
        return str(self.issue_thread) + ' assigned to ' + self.assigned_to.username


class ItemRelation(models.Model):
    id = models.AutoField(primary_key=True)
    issue_thread = models.ForeignKey(IssueThread, on_delete=models.CASCADE, related_name='item_relation_changes')
    item = models.ForeignKey(Item, on_delete=models.CASCADE, related_name='issue_relation_changes')
    timestamp = models.DateTimeField(auto_now_add=True)
    status = models.CharField(max_length=255, choices=RELATION_STATUS_CHOICES, default='possible')

    def __str__(self):
        return str(self.issue_thread) + ' related to ' + str(self.item)


class ShippingVoucher(models.Model):
    id = models.AutoField(primary_key=True)
    issue_thread = models.ForeignKey(IssueThread, on_delete=models.CASCADE, related_name='shipping_vouchers', null=True)
    voucher = models.CharField(max_length=255)
    type = models.CharField(max_length=255)
    timestamp = models.DateTimeField(auto_now_add=True)
    used_at = models.DateTimeField(null=True)

    def __str__(self):
        return self.voucher + ' (' + self.type + ')'

    def save(self, *args, **kwargs):
        if self.used_at is None and self.issue_thread is not None:
            self.used_at = timezone.now()
        super().save(*args, **kwargs)