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'],
    )
    systemevent = SystemEvent.objects.create(type='email received', reference=email.id)
    channel_layer = get_channel_layer()
    async_to_sync(channel_layer.group_send)(
        'general', {"type": "generic.event", "name": "send_message_to_frontend", "event_id": systemevent.id,
                    "message": "email received"}
    )

    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'],
    )
    systemevent = SystemEvent.objects.create(type='comment added', reference=comment.id)
    channel_layer = get_channel_layer()
    async_to_sync(channel_layer.group_send)(
        'general', {"type": "generic.event", "name": "send_message_to_frontend", "event_id": systemevent.id,
                    "message": "comment added"}
    )
    return Response(CommentSerializer(comment).data, status=status.HTTP_201_CREATED)


def filter_issues(issues, query):
    query_tokens = query.split(' ')
    for issue in issues:
        value = 0
        for token in query_tokens:
            if token in issue.description:
                value += 1
        if value > 0:
            yield {'search_score': value, 'issue': issue}


@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)
        items = filter_issues(IssueThread.objects.filter(event=event), 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)