import json

from itertools import chain

from django import forms
from django.contrib.auth.mixins import LoginRequiredMixin
from django.core.exceptions import (
    ImproperlyConfigured,
    PermissionDenied,
    ValidationError,
)
from django.http import (
    Http404,
    HttpResponse,
    HttpResponseBadRequest,
    HttpResponseRedirect,
    JsonResponse,
)
from django.shortcuts import get_object_or_404
from django.template.defaultfilters import truncatechars
from django.template.loader import get_template
from django.urls import reverse, reverse_lazy
from django.utils.functional import cached_property
from django.utils.html import escape, format_html, linebreaks
from django.utils.safestring import mark_safe
from django.utils.translation import gettext as _, gettext_lazy
from django.views import View
from django.views.generic import ListView
from django.views.generic.detail import SingleObjectMixin

from judge import event_poster as event
from judge.models import Problem, Profile, Ticket, TicketMessage, Notification
from judge.utils.diggpaginator import DiggPaginator
from judge.utils.tickets import filter_visible_tickets, own_ticket_filter
from judge.utils.views import SingleObjectFormView, TitleMixin, paginate_query_context
from judge.views.problem import ProblemMixin
from judge.widgets import HeavyPreviewPageDownWidget
from judge.models.notification import make_notification

ticket_widget = (
    forms.Textarea()
    if HeavyPreviewPageDownWidget is None
    else HeavyPreviewPageDownWidget(
        preview=reverse_lazy("ticket_preview"),
        preview_timeout=1000,
        hide_preview_button=True,
    )
)


def add_ticket_notifications(users, author, link, ticket):
    html = f'<a href="{link}">{ticket.linked_item}</a>'
    users = set(users)
    if author in users:
        users.remove(author)
    make_notification(users, "Ticket", html, author)


class TicketForm(forms.Form):
    title = forms.CharField(max_length=100, label=gettext_lazy("Ticket title"))
    body = forms.CharField(widget=ticket_widget)

    def __init__(self, request, *args, **kwargs):
        self.request = request
        super(TicketForm, self).__init__(*args, **kwargs)
        self.fields["title"].widget.attrs.update({"placeholder": _("Ticket title")})
        self.fields["body"].widget.attrs.update({"placeholder": _("Issue description")})

    def clean(self):
        if self.request is not None and self.request.user.is_authenticated:
            profile = self.request.profile
            if profile.mute:
                raise ValidationError(_("Your part is silent, little toad."))
        return super(TicketForm, self).clean()


class NewTicketView(LoginRequiredMixin, SingleObjectFormView):
    form_class = TicketForm
    template_name = "ticket/new.html"

    def get_assignees(self):
        return []

    def get_form_kwargs(self):
        kwargs = super(NewTicketView, self).get_form_kwargs()
        kwargs["request"] = self.request
        return kwargs

    def form_valid(self, form):
        ticket = Ticket(user=self.request.profile, title=form.cleaned_data["title"])
        ticket.linked_item = self.object
        ticket.save()
        message = TicketMessage(
            ticket=ticket, user=ticket.user, body=form.cleaned_data["body"]
        )
        message.save()
        ticket.assignees.set(self.get_assignees())

        link = reverse("ticket", args=[ticket.id])

        add_ticket_notifications(ticket.assignees.all(), ticket.user, link, ticket)

        if event.real:
            event.post(
                "tickets",
                {
                    "type": "new-ticket",
                    "id": ticket.id,
                    "message": message.id,
                    "user": ticket.user_id,
                    "assignees": list(ticket.assignees.values_list("id", flat=True)),
                },
            )
        return HttpResponseRedirect(link)


class NewProblemTicketView(ProblemMixin, TitleMixin, NewTicketView):
    template_name = "ticket/new_problem.html"

    def get_assignees(self):
        return self.object.authors.all()

    def get_title(self):
        return _("New ticket for %s") % self.object.name

    def get_content_title(self):
        return mark_safe(
            escape(_("New ticket for %s"))
            % format_html(
                '<a href="{0}">{1}</a>',
                reverse("problem_detail", args=[self.object.code]),
                self.object.translated_name(self.request.LANGUAGE_CODE),
            )
        )

    def form_valid(self, form):
        if not self.object.is_accessible_by(self.request.user):
            raise Http404()
        return super().form_valid(form)


class TicketCommentForm(forms.Form):
    body = forms.CharField(widget=ticket_widget)


class TicketMixin(object):
    model = Ticket

    def get_object(self, queryset=None):
        ticket = super(TicketMixin, self).get_object(queryset)
        profile_id = self.request.profile.id
        if self.request.user.has_perm("judge.change_ticket"):
            return ticket
        if ticket.user_id == profile_id:
            return ticket
        if ticket.assignees.filter(id=profile_id).exists():
            return ticket
        linked = ticket.linked_item
        if isinstance(linked, Problem) and linked.is_editable_by(self.request.user):
            return ticket
        raise PermissionDenied()


class TicketView(TitleMixin, LoginRequiredMixin, TicketMixin, SingleObjectFormView):
    form_class = TicketCommentForm
    template_name = "ticket/ticket.html"
    context_object_name = "ticket"

    def form_valid(self, form):
        message = TicketMessage(
            user=self.request.profile,
            body=form.cleaned_data["body"],
            ticket=self.object,
        )
        message.save()

        link = "%s#message-%d" % (reverse("ticket", args=[self.object.id]), message.id)

        notify_list = list(chain(self.object.assignees.all(), [self.object.user]))
        add_ticket_notifications(notify_list, message.user, link, self.object)

        if event.real:
            event.post(
                "tickets",
                {
                    "type": "ticket-message",
                    "id": self.object.id,
                    "message": message.id,
                    "user": self.object.user_id,
                    "assignees": list(
                        self.object.assignees.values_list("id", flat=True)
                    ),
                },
            )
            event.post(
                "ticket-%d" % self.object.id,
                {
                    "type": "ticket-message",
                    "message": message.id,
                },
            )
        return HttpResponseRedirect(link)

    def get_title(self):
        return _("%(title)s - Ticket %(id)d") % {
            "title": self.object.title,
            "id": self.object.id,
        }

    def get_context_data(self, **kwargs):
        context = super(TicketView, self).get_context_data(**kwargs)
        context["ticket_messages"] = self.object.messages.select_related("user__user")
        context["assignees"] = self.object.assignees.select_related("user")
        context["last_msg"] = event.last()
        return context


class TicketStatusChangeView(LoginRequiredMixin, TicketMixin, SingleObjectMixin, View):
    open = None

    def post(self, request, *args, **kwargs):
        if self.open is None:
            raise ImproperlyConfigured("Need to define open")
        ticket = self.get_object()
        if ticket.is_open != self.open:
            ticket.is_open = self.open
            ticket.save()
            if event.real:
                event.post(
                    "tickets",
                    {
                        "type": "ticket-status",
                        "id": ticket.id,
                        "open": self.open,
                        "user": ticket.user_id,
                        "assignees": list(
                            ticket.assignees.values_list("id", flat=True)
                        ),
                        "title": ticket.title,
                    },
                )
                event.post(
                    "ticket-%d" % ticket.id,
                    {
                        "type": "ticket-status",
                        "open": self.open,
                    },
                )
        return HttpResponse(status=204)


class TicketNotesForm(forms.Form):
    notes = forms.CharField(widget=forms.Textarea(), required=False)


class TicketNotesEditView(LoginRequiredMixin, TicketMixin, SingleObjectFormView):
    template_name = "ticket/edit-notes.html"
    form_class = TicketNotesForm
    context_object_name = "ticket"

    def get_initial(self):
        return {"notes": self.get_object().notes}

    def form_valid(self, form):
        ticket = self.get_object()
        ticket.notes = notes = form.cleaned_data["notes"]
        ticket.save()
        if notes:
            return HttpResponse(linebreaks(notes, autoescape=True))
        else:
            return HttpResponse()

    def form_invalid(self, form):
        return HttpResponseBadRequest()


class TicketList(LoginRequiredMixin, ListView):
    model = Ticket
    template_name = "ticket/list.html"
    context_object_name = "tickets"
    paginate_by = 50
    paginator_class = DiggPaginator

    @cached_property
    def user(self):
        return self.request.user

    @cached_property
    def profile(self):
        return self.user.profile

    @cached_property
    def can_edit_all(self):
        return self.request.user.has_perm("judge.change_ticket")

    @cached_property
    def filter_users(self):
        return self.request.GET.getlist("user")

    @cached_property
    def filter_assignees(self):
        return self.request.GET.getlist("assignee")

    def GET_with_session(self, key):
        if not self.request.GET:
            return self.request.session.get(key, False)
        return self.request.GET.get(key, None) == "1"

    def _get_queryset(self):
        return (
            Ticket.objects.select_related("user__user")
            .prefetch_related("assignees__user")
            .order_by("-id")
        )

    def get_queryset(self):
        queryset = self._get_queryset()
        if self.GET_with_session("own"):
            queryset = queryset.filter(own_ticket_filter(self.profile.id))
        elif not self.can_edit_all:
            queryset = filter_visible_tickets(queryset, self.user, self.profile)
        if self.filter_assignees:
            queryset = queryset.filter(
                assignees__user__username__in=self.filter_assignees
            )
        if self.filter_users:
            queryset = queryset.filter(user__user__username__in=self.filter_users)
        return queryset.distinct()

    def get_context_data(self, **kwargs):
        context = super(TicketList, self).get_context_data(**kwargs)

        page = context["page_obj"]
        context["title"] = _("Tickets - Page %(number)d of %(total)d") % {
            "number": page.number,
            "total": page.paginator.num_pages,
        }
        context["can_edit_all"] = self.can_edit_all
        context["filter_status"] = {
            "own": self.GET_with_session("own"),
            "user": self.filter_users,
            "assignee": self.filter_assignees,
            "user_id": json.dumps(
                list(
                    Profile.objects.filter(
                        user__username__in=self.filter_users
                    ).values_list("id", flat=True)
                )
            ),
            "assignee_id": json.dumps(
                list(
                    Profile.objects.filter(
                        user__username__in=self.filter_assignees
                    ).values_list("id", flat=True)
                )
            ),
            "own_id": self.profile.id if self.GET_with_session("own") else "null",
        }
        context["last_msg"] = event.last()
        context.update(paginate_query_context(self.request))
        return context

    def post(self, request, *args, **kwargs):
        to_update = ("own",)
        for key in to_update:
            if key in request.GET:
                val = request.GET.get(key) == "1"
                request.session[key] = val
            else:
                request.session.pop(key, None)
        return HttpResponseRedirect(request.get_full_path())


class ProblemTicketListView(TicketList):
    def _get_queryset(self):
        problem = get_object_or_404(Problem, code=self.kwargs.get("problem"))
        if problem.is_editable_by(self.request.user):
            return problem.tickets.order_by("-id")
        elif problem.is_accessible_by(self.request.user):
            return problem.tickets.filter(own_ticket_filter(self.profile.id)).order_by(
                "-id"
            )
        raise Http404()


class TicketListDataAjax(TicketMixin, SingleObjectMixin, View):
    def get(self, request, *args, **kwargs):
        try:
            self.kwargs["pk"] = request.GET["id"]
        except KeyError:
            return HttpResponseBadRequest()
        ticket = self.get_object()
        message = ticket.messages.first()
        return JsonResponse(
            {
                "row": get_template("ticket/row.html").render(
                    {"ticket": ticket}, request
                ),
                "notification": {
                    "title": _("New Ticket: %s") % ticket.title,
                    "body": "%s\n%s"
                    % (
                        _("#%(id)d, assigned to: %(users)s")
                        % {
                            "id": ticket.id,
                            "users": (
                                _(", ").join(
                                    ticket.assignees.values_list(
                                        "user__username", flat=True
                                    )
                                )
                                or _("no one")
                            ),
                        },
                        truncatechars(message.body, 200),
                    ),
                },
            }
        )


class TicketMessageDataAjax(TicketMixin, SingleObjectMixin, View):
    def get(self, request, *args, **kwargs):
        try:
            message_id = request.GET["message"]
        except KeyError:
            return HttpResponseBadRequest()
        ticket = self.get_object()
        try:
            message = ticket.messages.get(id=message_id)
        except TicketMessage.DoesNotExist:
            return HttpResponseBadRequest()
        return JsonResponse(
            {
                "message": get_template("ticket/message.html").render(
                    {"message": message}, request
                ),
                "notification": {
                    "title": _("New Ticket Message For: %s") % ticket.title,
                    "body": truncatechars(message.body, 200),
                },
            }
        )