import datetime

from django.conf import settings
from django.contrib import messages
from django.contrib.auth.decorators import login_required
from django.contrib.auth.models import User
from django.contrib.sites.models import Site
from django.core.exceptions import PermissionDenied
from django.core.mail import send_mail
from django.db import IntegrityError
from django.db.models import Q
from django.http import HttpResponse
from django.shortcuts import get_object_or_404, render, redirect
from django.template.loader import render_to_string
from django.urls import reverse
from django.utils import timezone
from django.utils.text import slugify
from django.views.decorators.csrf import csrf_exempt

from todo.forms import AddTaskListForm, AddEditTaskForm, AddExternalTaskForm, SearchForm
from todo.models import Task, TaskList, Comment
from todo.utils import (
    send_notify_mail,
    send_email_to_thread_participants,
    )


def staff_only(function):
    """
    Custom view decorator allows us to raise 403 on insufficient permissions,
    rather than redirect user to login view.
    """
    def wrap(request, *args, **kwargs):
        if request.user.is_staff:
            return function(request, *args, **kwargs)
        else:
            raise PermissionDenied

    wrap.__doc__ = function.__doc__
    wrap.__name__ = function.__name__
    return wrap


@login_required
def list_lists(request) -> HttpResponse:
    """Homepage view - list of lists a user can view, and ability to add a list.
    """

    thedate = datetime.datetime.now()
    searchform = SearchForm(auto_id=False)

    # Make sure user belongs to at least one group.
    if request.user.groups.all().count() == 0:
        messages.warning(request, "You do not yet belong to any groups. Ask your administrator to add you to one.")

    # Superusers see all lists
    if request.user.is_superuser:
        lists = TaskList.objects.all().order_by('group', 'name')
    else:
        lists = TaskList.objects.filter(group__in=request.user.groups.all()).order_by('group', 'name')

    list_count = lists.count()

    # superusers see all lists, so count shouldn't filter by just lists the admin belongs to
    if request.user.is_superuser:
        task_count = Task.objects.filter(completed=0).count()
    else:
        task_count = Task.objects.filter(completed=0).filter(task_list__group__in=request.user.groups.all()).count()

    context = {
       "lists": lists,
       "thedate": thedate,
       "searchform": searchform,
       "list_count": list_count,
       "task_count": task_count,
    }

    return render(request, 'todo/list_lists.html', context)


@staff_only
@login_required
def del_list(request, list_id: int, list_slug: str) -> HttpResponse:
    """Delete an entire list. Danger Will Robinson! Only staff members should be allowed to access this view.
    """
    task_list = get_object_or_404(TaskList, slug=list_slug)

    # Ensure user has permission to delete list. Admins can delete all lists.
    # Get the group this list belongs to, and check whether current user is a member of that group.
    # FIXME: This means any group member can delete lists, which is probably too permissive.
    if task_list.group not in request.user.groups.all() and not request.user.is_staff:
        raise PermissionDenied

    if request.method == 'POST':
        TaskList.objects.get(id=task_list.id).delete()
        messages.success(request, "{list_name} is gone.".format(list_name=task_list.name))
        return redirect('todo:lists')
    else:
        task_count_done = Task.objects.filter(task_list=task_list.id, completed=True).count()
        task_count_undone = Task.objects.filter(task_list=task_list.id, completed=False).count()
        task_count_total = Task.objects.filter(task_list=task_list.id).count()

    context = {
        "task_list": task_list,
        "task_count_done": task_count_done,
        "task_count_undone": task_count_undone,
        "task_count_total": task_count_total,
    }

    return render(request, 'todo/del_list.html', context)


@login_required
def list_detail(request, list_id=None, list_slug=None, view_completed=False):
    """Display and manage tasks in a todo list.
    """

    # Defaults
    task_list = None
    form = None

    # Which tasks to show on this list view?
    if list_slug == "mine":
        tasks = Task.objects.filter(assigned_to=request.user)

    else:
        # Show a specific list, ensuring permissions.
        task_list = get_object_or_404(TaskList, id=list_id)
        if task_list.group not in request.user.groups.all() and not request.user.is_staff:
            raise PermissionDenied
        tasks = Task.objects.filter(task_list=task_list.id)

    # Additional filtering
    if view_completed:
        tasks = tasks.filter(completed=True)
    else:
        tasks = tasks.filter(completed=False)

    # ######################
    #  Add New Task Form
    # ######################

    if request.POST.getlist('add_edit_task'):
        form = AddEditTaskForm(request.user, request.POST, initial={
            'assigned_to': request.user.id,
            'priority': 999,
            'task_list': task_list
        })

        if form.is_valid():
            new_task = form.save(commit=False)
            new_task.created_date = timezone.now()
            form.save()

            # Send email alert only if Notify checkbox is checked AND assignee is not same as the submitter
            if "notify" in request.POST and new_task.assigned_to and new_task.assigned_to != request.user:
                send_notify_mail(new_task)

            messages.success(request, "New task \"{t}\" has been added.".format(t=new_task.title))
            return redirect(request.path)
    else:
        # Don't allow adding new tasks on some views
        if list_slug not in ["mine", "recent-add", "recent-complete", ]:
            form = AddEditTaskForm(request.user, initial={
                'assigned_to': request.user.id,
                'priority': 999,
                'task_list': task_list,
            })

    context = {
        "list_id": list_id,
        "list_slug": list_slug,
        "task_list": task_list,
        "form": form,
        "tasks": tasks,
        "view_completed": view_completed,
    }

    return render(request, 'todo/list_detail.html', context)


@login_required
def task_detail(request, task_id: int) -> HttpResponse:
    """View task details. Allow task details to be edited. Process new comments on task.
    """

    task = get_object_or_404(Task, pk=task_id)
    comment_list = Comment.objects.filter(task=task_id)

    # Ensure user has permission to view task. Admins can view all tasks.
    # Get the group this task belongs to, and check whether current user is a member of that group.
    if task.task_list.group not in request.user.groups.all() and not request.user.is_staff:
        raise PermissionDenied

    # Save submitted comments
    if request.POST.get('add_comment'):
        Comment.objects.create(
            author=request.user,
            task=task,
            body=request.POST['comment-body'],
        )

        send_email_to_thread_participants(
            task, request.POST['comment-body'], request.user,
            subject='New comment posted on task "{}"'.format(task.title))
        messages.success(request, "Comment posted. Notification email sent to thread participants.")

    # Save task edits
    if request.POST.get('add_edit_task'):
        form = AddEditTaskForm(request.user, request.POST, instance=task, initial={'task_list': task.task_list})

        if form.is_valid():
            form.save()
            messages.success(request, "The task has been edited.")
            return redirect('todo:list_detail', list_id=task.task_list.id, list_slug=task.task_list.slug)
    else:
        form = AddEditTaskForm(request.user, instance=task, initial={'task_list': task.task_list})

    # Mark complete
    if request.POST.get('toggle_done'):
        results_changed = toggle_done([task.id, ])
        for res in results_changed:
            messages.success(request, res)

        return redirect('todo:task_detail', task_id=task.id,)

    if task.due_date:
        thedate = task.due_date
    else:
        thedate = datetime.datetime.now()

    context = {
        "task": task,
        "comment_list": comment_list,
        "form": form,
        "thedate": thedate,
    }

    return render(request, 'todo/task_detail.html', context)


@csrf_exempt
@login_required
def reorder_tasks(request) -> HttpResponse:
    """Handle task re-ordering (priorities) from JQuery drag/drop in list_detail.html
    """
    newtasklist = request.POST.getlist('tasktable[]')
    if newtasklist:
        # First task in received list is always empty - remove it
        del newtasklist[0]

        # Re-prioritize each task in list
        i = 1
        for id in newtasklist:
            task = Task.objects.get(pk=id)
            task.priority = i
            task.save()
            i += 1

    # All views must return an httpresponse of some kind ... without this we get
    # error 500s in the log even though things look peachy in the browser.
    return HttpResponse(status=201)


@staff_only
@login_required
def add_list(request) -> HttpResponse:
    """Allow users to add a new todo list to the group they're in.
    """

    if request.POST:
        form = AddTaskListForm(request.user, request.POST)
        if form.is_valid():
            try:
                newlist = form.save(commit=False)
                newlist.slug = slugify(newlist.name)
                newlist.save()
                messages.success(request, "A new list has been added.")
                return redirect('todo:lists')

            except IntegrityError:
                messages.warning(
                    request,
                    "There was a problem saving the new list. "
                    "Most likely a list with the same name in the same group already exists.")
    else:
        if request.user.groups.all().count() == 1:
            form = AddTaskListForm(request.user, initial={"group": request.user.groups.all()[0]})
        else:
            form = AddTaskListForm(request.user)

    context = {
        "form": form,
    }

    return render(request, 'todo/add_list.html', context)


@login_required
def search(request) -> HttpResponse:
    """Search for tasks user has permission to see.
    """
    if request.GET:

        query_string = ''
        found_tasks = None
        if ('q' in request.GET) and request.GET['q'].strip():
            query_string = request.GET['q']

            found_tasks = Task.objects.filter(
                Q(title__icontains=query_string) |
                Q(note__icontains=query_string)
            )
        else:
            # What if they selected the "completed" toggle but didn't enter a query string?
            # We still need found_tasks in a queryset so it can be "excluded" below.
            found_tasks = Task.objects.all()

        if 'inc_complete' in request.GET:
            found_tasks = found_tasks.exclude(completed=True)

    else:
        query_string = None
        found_tasks =None

    # Only include tasks that are in groups of which this user is a member:
    if not request.user.is_superuser:
        found_tasks = found_tasks.filter(task_list__group__in=request.user.groups.all())

    context = {
        'query_string': query_string,
        'found_tasks': found_tasks
    }
    return render(request, 'todo/search_results.html', context)


@login_required
def external_add(request) -> HttpResponse:
    """Allow authenticated users who don't have access to the rest of the ticket system to file a ticket
    in the list specified in settings (e.g. django-todo can be used a ticket filing system for a school, where
    students can file tickets without access to the rest of the todo system).

    Publicly filed tickets are unassigned unless settings.DEFAULT_ASSIGNEE exists.
    """

    if not settings.TODO_DEFAULT_LIST_SLUG:
        raise RuntimeError("This feature requires TODO_DEFAULT_LIST_SLUG: in settings. See documentation.")

    if not TaskList.objects.filter(slug=settings.TODO_DEFAULT_LIST_SLUG).exists():
        raise RuntimeError("There is no TaskList with slug specified for TODO_DEFAULT_LIST_SLUG in settings.")

    if request.POST:
        form = AddExternalTaskForm(request.POST)

        if form.is_valid():
            current_site = Site.objects.get_current()
            task = form.save(commit=False)
            task.task_list = TaskList.objects.get(slug=settings.TODO_DEFAULT_LIST_SLUG)
            task.created_by = request.user
            if settings.TODO_DEFAULT_ASSIGNEE:
                task.assigned_to = User.objects.get(username=settings.TODO_DEFAULT_ASSIGNEE)
            task.save()

            # Send email to assignee if we have one
            if task.assigned_to:
                email_subject = render_to_string("todo/email/assigned_subject.txt", {'task': task.title})
                email_body = render_to_string("todo/email/assigned_body.txt", {'task': task, 'site': current_site, })
                try:
                    send_mail(
                        email_subject, email_body, task.created_by.email,
                        [task.assigned_to.email, ], fail_silently=False)
                except ConnectionRefusedError:
                    messages.warning(request, "Task saved but mail not sent. Contact your administrator.")

            messages.success(request, "Your trouble ticket has been submitted. We'll get back to you soon.")
            return redirect(settings.TODO_PUBLIC_SUBMIT_REDIRECT)

    else:
        form = AddExternalTaskForm(initial={'priority': 999})

    context = {
        "form": form,
    }

    return render(request, 'todo/add_task_external.html', context)


@login_required
def toggle_done(request, task_id: int) -> HttpResponse:
    """Toggle the completed status of a task from done to undone, or vice versa.
    Redirect to the list from which the task came.
    """

    task = get_object_or_404(Task, pk=task_id)

    # Permissions
    if not (
        (task.created_by == request.user) or
        (task.assigned_to == request.user) or
        (task.task_list.group in request.user.groups.all())
    ):
        raise PermissionDenied

    tlist = task.task_list
    task.completed = not task.completed
    task.save()

    messages.success(request, "Task status changed for '{}'".format(task.title))
    return redirect(reverse('todo:list_detail', kwargs={"list_id": tlist.id, "list_slug": tlist.slug}))



@login_required
def delete_task(request, task_id: int) -> HttpResponse:
    """Delete specified task.
    Redirect to the list from which the task came.
    """

    task = get_object_or_404(Task, pk=task_id)

    # Permissions
    if not (
        (task.created_by == request.user) or
        (task.assigned_to == request.user) or
        (task.task_list.group in request.user.groups.all())
    ):
        raise PermissionDenied

    tlist = task.task_list
    task.delete()

    messages.success(request, "Task '{}' has been deleted".format(task.title))
    return redirect(reverse('todo:list_detail', kwargs={"list_id": tlist.id, "list_slug": tlist.slug}))
