"""Webapp views."""

from jwt import decode
from jwt.exceptions import InvalidSignatureError
from django.utils import timezone
from dealer.git import git
from django.conf import settings
from django.http import HttpResponse, JsonResponse
from django.shortcuts import render, get_object_or_404
from django.views.decorators.http import require_http_methods
from django_filters import rest_framework as filters
from django.core.exceptions import ObjectDoesNotExist
from rest_framework import routers, viewsets
from rest_framework.filters import OrderingFilter, SearchFilter
from rest_framework.decorators import action
from rest_framework.permissions import IsAuthenticatedOrReadOnly, BasePermission, AllowAny
from jsonschema import validate
from jsonschema.exceptions import ValidationError

from webapp.models import Event, SignupForm, Signup, TemplateQuestion, Feed, Committee, Occupation, Tag
from webapp.serializers import (EventSerializer, SignupFormSerializer, SignupSerializer,
                                SavedQuestionsSerializer, FeedSerializer, CommitteeSerializer,
                                OccupationSerializer, TagSerializer)


class SignupPermission(BasePermission):

    def has_permission(self, request, view):
        if request.method == 'POST':
            return True
        return request.user and request.user.is_authenticated


# -- REST API -- #
class RootView(routers.APIRootView):
    permission_classes = [IsAuthenticatedOrReadOnly]


class EventViewSet(viewsets.ModelViewSet):
    queryset = Event.objects.all()
    ordering = ["start_time"]
    serializer_class = EventSerializer
    permission_classes = [IsAuthenticatedOrReadOnly]
    filter_backends = (filters.DjangoFilterBackend, SearchFilter, OrderingFilter)
    filter_fields = '__all__'
    search_fields = '__all__'

    def get_queryset(self):
        if self.request.user.is_authenticated:
            return Event.objects.all()

        since = self.request.query_params.get('since', None)
        if since:
            return Event.objects.filter(visible=True, end_time__gt=since).order_by('start_time')
        return Event.objects.filter(visible=True, end_time__gt=timezone.now()).order_by('start_time')


class SignupFormViewSet(viewsets.ModelViewSet):
    queryset = SignupForm.objects.all()
    serializer_class = SignupFormSerializer
    permission_classes = [IsAuthenticatedOrReadOnly]

    def create(self, request, *args, **kwargs):
        try:
            schema = {
                "type": "array",
            }
            validate(instance=request.data["questions"], schema=schema)
            return super().create(request, *args, **kwargs)
        except ValidationError as err:
            return JsonResponse(status=400, data={"error": err.message})

    def get_queryset(self):
        if self.request.user.is_authenticated:
            return SignupForm.objects.all().order_by('start_time')
        return SignupForm.objects.filter(visible=True, end_time__gt=timezone.now()).order_by('start_time')


class SignupViewSet(viewsets.ModelViewSet):
    queryset = Signup.objects.all()
    serializer_class = SignupSerializer
    permission_classes = [SignupPermission]

    @action(detail=True, methods=['get', 'post'], permission_classes=[AllowAny])
    def edit(self, request, pk=None, *args, **kwargs):
        uuid = request.query_params.get("uuid", None)
        queryset = self.filter_queryset(self.get_queryset())
        filter = {'pk': pk, 'uuid': uuid}
        signup = get_object_or_404(queryset, **filter)
        if request.method == 'GET':
            return self.retrieve(request, *args, **kwargs)
        elif request.method == 'POST':
            return self.partial_update(request, *args, **kwargs)

    def create(self, request, *args, **kwargs):
        id = request.data["signupForm_id"]
        try:
            answer = request.data["answer"]
            form = SignupForm.objects.get(id=id)
            if (form.visible):
                # Throws ValidationError if not valid
                validate(instance=answer, schema=form.schema)
                return super().create(request, *args, **kwargs)
        except ValidationError as inst:
            return JsonResponse(status=400, data={"error": inst.message})
        except ObjectDoesNotExist:
            return JsonResponse(status=404, data={"error": f"SignupForm {id} not found"})
        else:
            return JsonResponse(status=404, data={"error": f"SignupForm {id} not found"})

    def partial_update(self, request, pk=None, *args, **kwargs):
        try:
            # ID & UUID validated in edit @action for normal users.
            # This is otherwise open for authenticated users.
            signup = self.get_object()
            answer = request.data["answer"]
            form = SignupForm.objects.get(id=signup.signupForm_id)

            if (form.visible):
                # Throws ValidationError if not valid
                validate(instance=answer, schema=form.schema)
                return super().partial_update(request, *args, **kwargs)
        except ValidationError as inst:
            return JsonResponse(status=400, data={"error": inst.message})
        except ObjectDoesNotExist:
            return JsonResponse(status=404, data={"error": f"SignupForm {id} not found"})
        else:
            return JsonResponse(status=404, data={"error": f"SignupForm {id} not found"})


class SavedQuestionsViewSet(viewsets.ModelViewSet):
    queryset = TemplateQuestion.objects.all()
    serializer_class = SavedQuestionsSerializer
    permission_classes = [IsAuthenticatedOrReadOnly]


class FeedViewSet(viewsets.ModelViewSet):
    queryset = Feed.objects.all()
    serializer_class = FeedSerializer
    permission_classes = [IsAuthenticatedOrReadOnly]
    filter_backends = (filters.DjangoFilterBackend, SearchFilter, OrderingFilter)
    filter_fields = '__all__'
    search_fields = '__all__'

    def get_queryset(self):
        if self.request.user.is_authenticated:
            return Feed.objects.all().order_by('publish_time')
        else:
            objs = Feed.objects.filter(visible=True).order_by('publish_time')

        result_ids = []
        for obj in objs:
            if obj.autohide_enabled:
                if obj.autohide > timezone.now():
                    result_ids.append(obj.id)
            else:
                result_ids.append(obj.id)

        return Feed.objects.filter(id__in=result_ids)


class ContactsViewSet(viewsets.ReadOnlyModelViewSet):
    queryset = Occupation.objects.all()
    serializer_class = OccupationSerializer
    permission_classes = [IsAuthenticatedOrReadOnly]

    def get_queryset(self):
        year = self.request.query_params.get('year')
        if not year:
            return Occupation.by_year(timezone.now().year)
        return Occupation.by_year(int(year))


class CommitteeViewSet(viewsets.ReadOnlyModelViewSet):
    queryset = Committee.objects.all()
    serializer_class = CommitteeSerializer
    permission_classes = [IsAuthenticatedOrReadOnly]


class TagsViewSet(viewsets.ReadOnlyModelViewSet):
    queryset = Tag.objects.all()
    serializer_class = TagSerializer
    permission_classes = [IsAuthenticatedOrReadOnly]


@require_http_methods(["GET"])
def about_view(request, *args, **kwargs):
    """Render about page."""
    latest_commit = "Not found"
    latest_date = "Not found"
    latest_tag = "Not found"
    try:
        repo = git.init_repo()
        latest_commit = repo.git("rev-parse HEAD").decode('utf-8')
        latest_date = repo.git("show -s --format=%ci " + latest_commit).decode('utf-8')
        latest_tag = repo.git("describe --tags " + repo.git("rev-list --tags --max-count=1").decode('utf-8')).decode('utf-8')
    except Exception as e:
        print(f"Git failed:\n{e}")

    context = {
        'commit': latest_commit,
        'date': latest_date,
        'tag': latest_tag
    }
    return render(request, "about.html", context)


@require_http_methods(["GET"])
def nginx_jwt_resp(request, *args, **kwargs):
    cookie = request.COOKIES.get("jwt", None)
    if not cookie:
        return HttpResponse("", status=401)
    try:
        token = decode(cookie, settings.SECRET_KEY)
    except InvalidSignatureError:
        return HttpResponse("", status=403)
    user = 'admin' if token.get('username', '') == 'admin' else 'moderator'
    resp = HttpResponse("", status=200)
    resp['X-FBrowser-User'] = user
    return resp
