import urllib.parse

from django.contrib.auth.decorators import login_required
from django.db.models import QuerySet
from django.http import HttpResponseRedirect, Http404, HttpResponseBadRequest, HttpResponseForbidden
from django.shortcuts import render
from django.urls import reverse

from bookmarks import queries
from bookmarks.models import Bookmark, BookmarkForm, BookmarkSearch, build_tag_string
from bookmarks.services.bookmarks import create_bookmark, update_bookmark, archive_bookmark, archive_bookmarks, \
    unarchive_bookmark, unarchive_bookmarks, delete_bookmarks, tag_bookmarks, untag_bookmarks, mark_bookmarks_as_read, \
    mark_bookmarks_as_unread, share_bookmarks, unshare_bookmarks
from bookmarks.utils import get_safe_return_url
from bookmarks.views.partials import contexts

_default_page_size = 30


@login_required
def index(request):
    if request.method == 'POST':
        return search_action(request)

    bookmark_list = contexts.ActiveBookmarkListContext(request)
    tag_cloud = contexts.ActiveTagCloudContext(request)
    return render(request, 'bookmarks/index.html', {
        'bookmark_list': bookmark_list,
        'tag_cloud': tag_cloud,
    })


@login_required
def archived(request):
    if request.method == 'POST':
        return search_action(request)

    bookmark_list = contexts.ArchivedBookmarkListContext(request)
    tag_cloud = contexts.ArchivedTagCloudContext(request)
    return render(request, 'bookmarks/archive.html', {
        'bookmark_list': bookmark_list,
        'tag_cloud': tag_cloud,
    })


def shared(request):
    if request.method == 'POST':
        return search_action(request)

    bookmark_list = contexts.SharedBookmarkListContext(request)
    tag_cloud = contexts.SharedTagCloudContext(request)
    public_only = not request.user.is_authenticated
    users = queries.query_shared_bookmark_users(request.user_profile, bookmark_list.search, public_only)
    return render(request, 'bookmarks/shared.html', {
        'bookmark_list': bookmark_list,
        'tag_cloud': tag_cloud,
        'users': users
    })


def search_action(request):
    if 'save' in request.POST:
        if not request.user.is_authenticated:
            return HttpResponseForbidden()
        search = BookmarkSearch.from_request(request.POST)
        request.user_profile.search_preferences = search.preferences_dict
        request.user_profile.save()

    # redirect to base url including new query params
    search = BookmarkSearch.from_request(request.POST, request.user_profile.search_preferences)
    base_url = request.path
    query_params = search.query_params
    query_string = urllib.parse.urlencode(query_params)
    url = base_url if not query_string else base_url + '?' + query_string
    return HttpResponseRedirect(url)


def convert_tag_string(tag_string: str):
    # Tag strings coming from inputs are space-separated, however services.bookmarks functions expect comma-separated
    # strings
    return tag_string.replace(' ', ',')


@login_required
def new(request):
    initial_url = request.GET.get('url')
    initial_title = request.GET.get('title')
    initial_description = request.GET.get('description')
    initial_auto_close = 'auto_close' in request.GET

    if request.method == 'POST':
        form = BookmarkForm(request.POST)
        auto_close = form.data['auto_close']
        if form.is_valid():
            current_user = request.user
            tag_string = convert_tag_string(form.data['tag_string'])
            create_bookmark(form.save(commit=False), tag_string, current_user)
            if auto_close:
                return HttpResponseRedirect(reverse('bookmarks:close'))
            else:
                return HttpResponseRedirect(reverse('bookmarks:index'))
    else:
        form = BookmarkForm()
        if initial_url:
            form.initial['url'] = initial_url
        if initial_title:
            form.initial['title'] = initial_title
        if initial_description:
            form.initial['description'] = initial_description
        if initial_auto_close:
            form.initial['auto_close'] = 'true'

    context = {
        'form': form,
        'auto_close': initial_auto_close,
        'return_url': reverse('bookmarks:index')
    }

    return render(request, 'bookmarks/new.html', context)


@login_required
def edit(request, bookmark_id: int):
    try:
        bookmark = Bookmark.objects.get(pk=bookmark_id, owner=request.user)
    except Bookmark.DoesNotExist:
        raise Http404('Bookmark does not exist')
    return_url = get_safe_return_url(request.GET.get('return_url'), reverse('bookmarks:index'))

    if request.method == 'POST':
        form = BookmarkForm(request.POST, instance=bookmark)
        if form.is_valid():
            tag_string = convert_tag_string(form.data['tag_string'])
            update_bookmark(form.save(commit=False), tag_string, request.user)
            return HttpResponseRedirect(return_url)
    else:
        form = BookmarkForm(instance=bookmark)

    form.initial['tag_string'] = build_tag_string(bookmark.tag_names, ' ')

    context = {
        'form': form,
        'bookmark_id': bookmark_id,
        'return_url': return_url
    }

    return render(request, 'bookmarks/edit.html', context)


def remove(request, bookmark_id: int):
    try:
        bookmark = Bookmark.objects.get(pk=bookmark_id, owner=request.user)
    except Bookmark.DoesNotExist:
        raise Http404('Bookmark does not exist')

    bookmark.delete()


def archive(request, bookmark_id: int):
    try:
        bookmark = Bookmark.objects.get(pk=bookmark_id, owner=request.user)
    except Bookmark.DoesNotExist:
        raise Http404('Bookmark does not exist')

    archive_bookmark(bookmark)


def unarchive(request, bookmark_id: int):
    try:
        bookmark = Bookmark.objects.get(pk=bookmark_id, owner=request.user)
    except Bookmark.DoesNotExist:
        raise Http404('Bookmark does not exist')

    unarchive_bookmark(bookmark)


def unshare(request, bookmark_id: int):
    try:
        bookmark = Bookmark.objects.get(pk=bookmark_id, owner=request.user)
    except Bookmark.DoesNotExist:
        raise Http404('Bookmark does not exist')

    bookmark.shared = False
    bookmark.save()


def mark_as_read(request, bookmark_id: int):
    try:
        bookmark = Bookmark.objects.get(pk=bookmark_id, owner=request.user)
    except Bookmark.DoesNotExist:
        raise Http404('Bookmark does not exist')

    bookmark.unread = False
    bookmark.save()


@login_required
def index_action(request):
    search = BookmarkSearch.from_request(request.GET)
    query = queries.query_bookmarks(request.user, request.user_profile, search)
    return action(request, query)


@login_required
def archived_action(request):
    search = BookmarkSearch.from_request(request.GET)
    query = queries.query_archived_bookmarks(request.user, request.user_profile, search)
    return action(request, query)


@login_required
def shared_action(request):
    return action(request)


def action(request, query: QuerySet[Bookmark] = None):
    # Single bookmark actions
    if 'archive' in request.POST:
        archive(request, request.POST['archive'])
    if 'unarchive' in request.POST:
        unarchive(request, request.POST['unarchive'])
    if 'remove' in request.POST:
        remove(request, request.POST['remove'])
    if 'mark_as_read' in request.POST:
        mark_as_read(request, request.POST['mark_as_read'])
    if 'unshare' in request.POST:
        unshare(request, request.POST['unshare'])

    # Bulk actions
    if 'bulk_execute' in request.POST:
        if query is None:
            return HttpResponseBadRequest('View does not support bulk actions')

        bulk_action = request.POST['bulk_action']

        # Determine set of bookmarks
        if request.POST.get('bulk_select_across') == 'on':
            # Query full list of bookmarks across all pages
            bookmark_ids = query.only('id').values_list('id', flat=True)
        else:
            # Use only selected bookmarks
            bookmark_ids = request.POST.getlist('bookmark_id')

        if 'bulk_archive' == bulk_action:
            archive_bookmarks(bookmark_ids, request.user)
        if 'bulk_unarchive' == bulk_action:
            unarchive_bookmarks(bookmark_ids, request.user)
        if 'bulk_delete' == bulk_action:
            delete_bookmarks(bookmark_ids, request.user)
        if 'bulk_tag' == bulk_action:
            tag_string = convert_tag_string(request.POST['bulk_tag_string'])
            tag_bookmarks(bookmark_ids, tag_string, request.user)
        if 'bulk_untag' == bulk_action:
            tag_string = convert_tag_string(request.POST['bulk_tag_string'])
            untag_bookmarks(bookmark_ids, tag_string, request.user)
        if 'bulk_read' == bulk_action:
            mark_bookmarks_as_read(bookmark_ids, request.user)
        if 'bulk_unread' == bulk_action:
            mark_bookmarks_as_unread(bookmark_ids, request.user)
        if 'bulk_share' == bulk_action:
            share_bookmarks(bookmark_ids, request.user)
        if 'bulk_unshare' == bulk_action:
            unshare_bookmarks(bookmark_ids, request.user)

    return_url = get_safe_return_url(request.GET.get('return_url'), reverse('bookmarks:index'))
    return HttpResponseRedirect(return_url)


@login_required
def close(request):
    return render(request, 'bookmarks/close.html')
