from datetime import datetime
from django.contrib.auth.decorators import login_required, permission_required
from django.contrib.auth.mixins import LoginRequiredMixin
from django.core.exceptions import PermissionDenied
from django.db.models import Count
from django.shortcuts import redirect, get_object_or_404, resolve_url
from django.contrib import messages
from django.views.generic import ListView, CreateView
from django.utils.translation import ugettext as _
from django.conf import settings
from django.views.generic.edit import FormMixin, DeleteView, UpdateView

from guardian.shortcuts import assign_perm
from abonapp.models import Abon
from djing import httpresponse_to_referrer
from djing.lib import safe_int, MultipleException
from djing.lib.decorators import only_admins
from djing.lib.mixins import LoginAdminMixin, LoginAdminPermissionMixin
from .handle import TaskException
from .models import Task, ExtraComment
from .forms import TaskFrm, ExtraCommentForm


class NewTasksView(LoginAdminPermissionMixin, ListView):
    """
    Show new tasks
    """
    http_method_names = ('get',)
    paginate_by = getattr(settings, 'PAGINATION_ITEMS_PER_PAGE', 10)
    template_name = 'taskapp/tasklist.html'
    context_object_name = 'tasks'
    permission_required = 'taskapp.view_task'

    def get_queryset(self):
        return Task.objects.filter(
            recipients=self.request.user, state='S'
        ).annotate(
            comment_count=Count('extracomment')
        ).select_related(
            'abon', 'abon__street', 'abon__group', 'author'
        )


class FailedTasksView(NewTasksView):
    """
    Show crashed tasks
    """
    template_name = 'taskapp/tasklist_failed.html'
    context_object_name = 'tasks'

    def get_queryset(self):
        return Task.objects.filter(
            recipients=self.request.user, state='C'
        ).select_related(
            'abon', 'abon__street', 'abon__group', 'author'
        )


class FinishedTaskListView(NewTasksView):
    template_name = 'taskapp/tasklist_finish.html'

    def get_queryset(self):
        return Task.objects.filter(
            recipients=self.request.user, state='F'
        ).select_related(
            'abon', 'abon__street', 'abon__group', 'author'
        )


class OwnTaskListView(NewTasksView):
    template_name = 'taskapp/tasklist_own.html'

    def get_queryset(self):
        # Attached and not finished tasks
        return Task.objects.filter(
            author=self.request.user
        ).exclude(state='F').select_related(
            'abon', 'abon__street', 'abon__group'
        )


class MyTaskListView(NewTasksView):
    template_name = 'taskapp/tasklist.html'

    def get_queryset(self):
        # Tasks in which I participated
        return Task.objects.filter(
            recipients=self.request.user
        ).select_related(
            'abon', 'abon__street', 'abon__group', 'author'
        )


class AllTasksListView(LoginAdminMixin, LoginRequiredMixin, ListView):
    http_method_names = ('get',)
    paginate_by = getattr(settings, 'PAGINATION_ITEMS_PER_PAGE', 10)
    template_name = 'taskapp/tasklist_all.html'
    context_object_name = 'tasks'
    permission_required = 'taskapp.can_viewall'

    def get_queryset(self):
        return Task.objects.annotate(
            comment_count=Count('extracomment')
        ).select_related(
            'abon', 'abon__street', 'abon__group', 'author'
        )


class AllNewTasksListView(AllTasksListView):

    def get_queryset(self):
        return super(AllNewTasksListView, self).get_queryset().filter(state='S')


class EmptyTasksListView(NewTasksView):
    template_name = 'taskapp/tasklist_empty.html'

    def get_queryset(self):
        return Task.objects.annotate(
            reccount=Count('recipients')
        ).filter(reccount__lt=1)


@login_required
@only_admins
@permission_required('taskapp.delete_task')
def task_delete(request, task_id):
    task = get_object_or_404(Task, id=task_id)
    # prevent to delete task that assigned to me
    if request.user.is_superuser or request.user not in task.recipients.all():
        task.delete()
    else:
        messages.warning(
            request, _('You cannot delete task that assigned to you')
        )
    return redirect('taskapp:home')


class TaskUpdateView(LoginAdminMixin, UpdateView):
    http_method_names = ('get', 'post')
    template_name = 'taskapp/add_edit_task.html'
    form_class = TaskFrm
    context_object_name = 'task'

    def get_object(self, queryset=None):
        task_id = safe_int(self.kwargs.get('task_id'))
        if task_id == 0:
            uname = self.request.GET.get('uname')
            if uname:
                self.selected_abon = Abon.objects.get(username=uname)
            return
        else:
            task = get_object_or_404(Task, pk=task_id)
            self.selected_abon = task.abon
            return task

    def dispatch(self, request, *args, **kwargs):
        task_id = safe_int(self.kwargs.get('task_id', 0))
        if task_id == 0:
            if not request.user.has_perm('taskapp.add_task'):
                raise PermissionDenied
        else:
            if not request.user.has_perm('taskapp.change_task'):
                raise PermissionDenied

        # check if new task with user already exists
        uname = request.GET.get('uname')
        if uname and self.kwargs.get('task_id') is None:
            exists_task = Task.objects.filter(abon__username=uname, state='S')
            if exists_task.exists():
                messages.info(request, _('New task with this user already exists.'
                                         ' You are redirected to it.'))
                return redirect('taskapp:edit', exists_task.first().pk)

        try:
            return super(TaskUpdateView, self).dispatch(request, *args, **kwargs)
        except TaskException as e:
            messages.error(request, e)
        return httpresponse_to_referrer(request)

    def get_form_kwargs(self):
        kwargs = super(TaskUpdateView, self).get_form_kwargs()
        if hasattr(self, 'selected_abon'):
            kwargs.update({'initial_abon': self.selected_abon})
        return kwargs

    def form_valid(self, form):
        # check if new task with picked user already exists
        if form.cleaned_data['state'] == 'S' and self.kwargs.get('task_id') is None:
            exists_task = Task.objects.filter(abon=form.cleaned_data['abon'], state='S')
            if exists_task.exists():
                messages.info(self.request, _('New task with this user already exists.'
                                              ' You are redirected to it.'))
                return redirect('taskapp:edit', exists_task.first().pk)

        try:
            self.object = form.save()
            if self.object.author is None:
                self.object.author = self.request.user
                self.object.save(update_fields=('author',))
            task_id = safe_int(self.kwargs.get('task_id', 0))
            if task_id == 0:
                log_text = _('Task has successfully created')
            else:
                log_text = _('Task has changed successfully')
            messages.add_message(self.request, messages.SUCCESS, log_text)
            self.object.send_notification()
        except MultipleException as e:
            for err in e.err_list:
                messages.add_message(self.request, messages.WARNING, err)
        except TaskException as e:
            messages.add_message(self.request, messages.ERROR, e)
        return FormMixin.form_valid(self, form)

    def get_context_data(self, **kwargs):
        if hasattr(self, 'selected_abon'):
            selected_abon = self.selected_abon
        else:
            selected_abon = None

        now_date = datetime.now().date()
        task = self.object
        if task:
            if task.out_date > now_date:
                time_diff = "%s: %s" % (_('time left'), (task.out_date - now_date))
            else:
                time_diff = _("Expired timeout -%(time_left)s") % {
                    'time_left': (now_date - task.out_date)
                }
        else:
            time_diff = None

        context = {
            'selected_abon': selected_abon,
            'time_diff': time_diff,
            'comments': ExtraComment.objects.filter(task=task),
            'comment_form': ExtraCommentForm()
        }
        context.update(kwargs)
        return super(TaskUpdateView, self).get_context_data(**context)

    def get_success_url(self):
        task_id = safe_int(self.kwargs.get('task_id'))
        if task_id == 0:
            return resolve_url('taskapp:own_tasks')
        else:
            return resolve_url('taskapp:edit', task_id)

    def form_invalid(self, form):
        messages.add_message(
            self.request, messages.ERROR,
            _('fix form errors')
        )
        return super(TaskUpdateView, self).form_invalid(form)


@login_required
@only_admins
def task_finish(request, task_id):
    try:
        task = get_object_or_404(Task, id=task_id)
        task.finish(request.user)
        task.send_notification()
    except MultipleException as errs:
        for err in errs.err_list:
            messages.add_message(request, messages.constants.ERROR, err)
    except TaskException as e:
        messages.error(request, e)
    return redirect('taskapp:home')


@login_required
@only_admins
def task_failed(request, task_id):
    try:
        task = get_object_or_404(Task, id=task_id)
        task.do_fail(request.user)
        task.send_notification()
    except TaskException as e:
        messages.error(request, e)
    return redirect('taskapp:home')


@login_required
@only_admins
@permission_required('taskapp.can_remind')
def remind(request, task_id):
    try:
        task = get_object_or_404(Task, id=task_id)
        task.save(update_fields=('state',))
        task.send_notification()
        messages.success(request, _('Task has been reminded'))
    except MultipleException as errs:
        for err in errs.err_list:
            messages.add_message(request, messages.constants.ERROR, err)
    except TaskException as e:
        messages.error(request, e)
    return redirect('taskapp:home')


class NewCommentView(LoginAdminMixin, LoginRequiredMixin, CreateView):
    form_class = ExtraCommentForm
    model = ExtraComment
    http_method_names = ('get', 'post')
    permission_required = 'taskapp.add_extracomment'

    def form_valid(self, form):
        self.task = get_object_or_404(Task, pk=self.kwargs.get('task_id'))
        self.object = form.make_save(
            author=self.request.user,
            task=self.task
        )
        author = self.object.author
        assign_perm('taskapp.change_extracomment', author, self.object)
        assign_perm('taskapp.delete_extracomment', author, self.object)
        assign_perm('taskapp.view_extracomment', author, self.object)
        return FormMixin.form_valid(self, form)


class DeleteCommentView(LoginAdminPermissionMixin, DeleteView):
    model = ExtraComment
    pk_url_kwarg = 'comment_id'
    http_method_names = ('get', 'post')
    template_name = 'taskapp/comments/extracomment_confirm_delete.html'
    permission_required = 'taskapp.delete_extracomment'

    def get_context_data(self, **kwargs):
        context = {
            'task_id': self.kwargs.get('task_id')
        }
        context.update(kwargs)
        return super(DeleteCommentView, self).get_context_data(**context)

    def get_success_url(self):
        task_id = self.kwargs.get('task_id')
        return resolve_url('taskapp:edit', task_id)
