from base64 import b64decode

from django.urls import re_path
from django.contrib.auth.decorators import permission_required
from rest_framework import routers, viewsets, status
from rest_framework.decorators import api_view, permission_classes
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from asgiref.sync import async_to_sync
from channels.layers import get_channel_layer

from core.settings import MAIL_DOMAIN
from inventory.models import Event
from mail.models import Email
from mail.protocol import send_smtp, make_reply, collect_references
from notify_sessions.models import SystemEvent
from tickets.models import IssueThread, Comment, STATE_CHOICES, ShippingVoucher, ItemRelation
from tickets.serializers import IssueSerializer, CommentSerializer, ShippingVoucherSerializer, SearchResultSerializer
from tickets.shared_serializers import RelationSerializer


class IssueViewSet(viewsets.ModelViewSet):
    serializer_class = IssueSerializer

    def get_queryset(self):
        queryset = IssueThread.objects.all()
        serializer = self.get_serializer_class()
        if hasattr(serializer, 'Meta') and hasattr(serializer.Meta, 'prefetch_related_fields'):
            queryset = queryset.prefetch_related(*serializer.Meta.prefetch_related_fields)
        return queryset


class RelationViewSet(viewsets.ModelViewSet):
    serializer_class = RelationSerializer
    queryset = ItemRelation.objects.all()


class CommentViewSet(viewsets.ModelViewSet):
    serializer_class = CommentSerializer
    queryset = Comment.objects.all()


class ShippingVoucherViewSet(viewsets.ModelViewSet):
    serializer_class = ShippingVoucherSerializer
    queryset = ShippingVoucher.objects.all()


@api_view(['POST'])
@permission_classes([IsAuthenticated])
@permission_required('tickets.add_issuethread', raise_exception=True)
def reply(request, pk):
    issue = IssueThread.objects.get(pk=pk)
    # email = issue.reply(request.data['body']) # TODO evaluate if this is a useful abstraction
    references = collect_references(issue)
    first_mail = Email.objects.filter(issue_thread=issue, recipient__endswith='@' + MAIL_DOMAIN).order_by(
        'timestamp').first()
    if not first_mail:
        return Response({'status': 'error', 'message': 'no previous mail found, reply would not make sense.'},
                        status=status.HTTP_400_BAD_REQUEST)
    mail = Email.objects.create(
        issue_thread=issue,
        sender=first_mail.recipient,
        recipient=first_mail.sender,
        subject=f'Re: {issue.name} [#{issue.short_uuid()}]',
        body=request.data['message'],
        in_reply_to=first_mail.reference,
    )
    async_to_sync(send_smtp)(make_reply(mail, references))

    return Response({'status': 'ok'}, status=status.HTTP_201_CREATED)


@api_view(['POST'])
@permission_classes([IsAuthenticated])
@permission_required('tickets.add_issuethread_manual', raise_exception=True)
def manual_ticket(request, event_slug):
    if 'name' not in request.data:
        return Response({'status': 'error', 'message': 'missing name'}, status=status.HTTP_400_BAD_REQUEST)
    if 'sender' not in request.data:
        return Response({'status': 'error', 'message': 'missing sender'}, status=status.HTTP_400_BAD_REQUEST)
    if 'recipient' not in request.data:
        return Response({'status': 'error', 'message': 'missing recipient'}, status=status.HTTP_400_BAD_REQUEST)
    if 'body' not in request.data:
        return Response({'status': 'error', 'message': 'missing body'}, status=status.HTTP_400_BAD_REQUEST)

    event = None
    if event_slug != 'none':
        try:
            event = Event.objects.get(slug=event_slug)
        except:
            return Response({'status': 'error', 'message': 'invalid event'}, status=status.HTTP_400_BAD_REQUEST)

    issue = IssueThread.objects.create(
        name=request.data['name'],
        event=event,
        manually_created=True,
    )
    email = Email.objects.create(
        issue_thread=issue,
        sender=request.data['sender'],
        recipient=request.data['recipient'],
        subject=request.data['name'],
        body=request.data['body'],
    )

    return Response(IssueSerializer(issue).data, status=status.HTTP_201_CREATED)


@api_view(['GET'])
@permission_classes([IsAuthenticated])
def get_available_states(request):
    def get_state_choices():
        for state in STATE_CHOICES:
            yield {'value': list(state)[0], 'text': list(state)[1]}

    return Response(get_state_choices())


@api_view(['POST'])
@permission_classes([IsAuthenticated])
@permission_required('tickets.add_comment', raise_exception=True)
def add_comment(request, pk):
    issue = IssueThread.objects.get(pk=pk)
    if 'comment' not in request.data or request.data['comment'] == '':
        return Response({'status': 'error', 'message': 'missing comment'}, status=status.HTTP_400_BAD_REQUEST)
    comment = Comment.objects.create(
        issue_thread=issue,
        comment=request.data['comment'],
    )
    return Response(CommentSerializer(comment).data, status=status.HTTP_201_CREATED)


def filter_issues(issues, query):
    query_tokens = query.lower().split(' ')
    matches = []
    for issue in issues:
        value = 0
        if "T#" + issue.short_uuid() in query:
            value += 12
            matches.append(
                {'type': 'ticket_uuid', 'text': f'is exactly {issue.short_uuid()} and matched "T#{issue.short_uuid()}"'})
        elif "#" + issue.short_uuid() in query:
            value += 11
            matches.append(
                {'type': 'ticket_uuid', 'text': f'is exactly {issue.short_uuid()} and matched "#{issue.short_uuid()}"'})
        elif issue.short_uuid() in query:
            value += 10
            matches.append({'type': 'ticket_uuid', 'text': f'is exactly {issue.short_uuid()}'})
        if "T#" + str(issue.id) in query:
            value += 10
            matches.append({'type': 'ticket_id', 'text': f'is exactly {issue.id} and matched "T#{issue.id}"'})
        elif "#" + str(issue.id) in query:
            value += 7
            matches.append({'type': 'ticket_id', 'text': f'is exactly {issue.id} and matched "#{issue.id}"'})
        elif str(issue.id) in query:
            value += 4
            matches.append({'type': 'ticket_id', 'text': f'is exactly {issue.id}'})
        for item in issue.related_items:
            if "I#" + str(item.id) in query:
                value += 8
                matches.append({'type': 'item_id', 'text': f'is exactly {item.id} and matched "I#{item.id}"'})
            elif "#" + str(item.id) in query:
                value += 5
                matches.append({'type': 'item_id', 'text': f'is exactly {item.id} and matched "#{item.id}"'})
            elif str(item.id) in query:
                value += 3
                matches.append({'type': 'item_id', 'text': f'is exactly {item.id}'})
            for token in query_tokens:
                if token in item.description.lower():
                    value += 1
                    matches.append({'type': 'item_description', 'text': f'contains {token}'})
            for comment in item.comments.all():
                for token in query_tokens:
                    if token in comment.comment.lower():
                        value += 1
                        matches.append({'type': 'item_comment', 'text': f'contains {token}'})
        for token in query_tokens:
            if token in issue.name.lower():
                value += 1
                matches.append({'type': 'ticket_name', 'text': f'contains {token}'})
        for comment in issue.comments.all():
            for token in query_tokens:
                if token in comment.comment.lower():
                    value += 1
                    matches.append({'type': 'ticket_comment', 'text': f'contains {token}'})
        for email in issue.emails.all():
            for token in query_tokens:
                if token in email.subject.lower():
                    value += 1
                    matches.append({'type': 'email_subject', 'text': f'contains {token}'})
                if token in email.body.lower():
                    value += 1
                    matches.append({'type': 'email_body', 'text': f'contains {token}'})
                if token in email.sender.lower():
                    value += 1
                    matches.append({'type': 'email_sender', 'text': f'contains {token}'})
        if value > 0:
            yield {'search_score': value, 'issue': issue, 'search_matches': matches}


@api_view(['GET'])
@permission_classes([IsAuthenticated])
def search_issues(request, event_slug, query):
    try:
        event = Event.objects.get(slug=event_slug)
        if not request.user.has_event_perm(event, 'view_issuethread'):
            return Response(status=403)
        serializer = IssueSerializer()
        queryset = IssueThread.objects.filter(event=event)
        items = filter_issues(queryset.prefetch_related(*serializer.Meta.prefetch_related_fields),
                              b64decode(query).decode('utf-8'))
        return Response(SearchResultSerializer(items, many=True).data)
    except Event.DoesNotExist:
        return Response(status=404)


router = routers.SimpleRouter()
router.register(r'tickets', IssueViewSet, basename='issues')
router.register(r'matches', RelationViewSet, basename='matches')
router.register(r'shipping_vouchers', ShippingVoucherViewSet, basename='shipping_vouchers')

urlpatterns = ([
                   re_path(r'tickets/states/$', get_available_states, name='get_available_states'),
                   re_path(r'^tickets/(?P<pk>\d+)/reply/$', reply, name='reply'),
                   re_path(r'^tickets/(?P<pk>\d+)/comment/$', add_comment, name='add_comment'),
                   re_path(r'^(?P<event_slug>[\w-]+)/tickets/manual/$', manual_ticket, name='manual_ticket'),
                   re_path(r'^(?P<event_slug>[\w-]+)/tickets/(?P<query>[-A-Za-z0-9+/]*={0,3})/$', search_issues,
                           name='search_issues'),
               ] + router.urls)