import logging
from os import stat
from django.shortcuts import render, get_object_or_404, redirect
from django.urls import reverse
from django.contrib.auth.decorators import login_required, permission_required
from django.utils.translation import gettext_lazy as _
from django.http import (
    HttpResponse,
    HttpResponseBadRequest,
    HttpResponseNotFound,
)
from django.core.exceptions import BadRequest, ObjectDoesNotExist, PermissionDenied
from django.db.models import Count
from django.utils import timezone
from django.utils.dateparse import parse_datetime
from django.core.paginator import Paginator
from .models import *
from django.conf import settings
from django.http import HttpResponseRedirect
from django.db.models import Q
from management.models import Announcement
from django.utils.baseconv import base62
from .forms import *
from mastodon.api import share_review, share_collection
from users.views import render_user_blocked, render_user_not_found
from users.models import User, Report, Preference
from common.utils import PageLinksGenerator
from user_messages import api as msg


_logger = logging.getLogger(__name__)
PAGE_SIZE = 10

_checkmark = "✔️".encode("utf-8")


@login_required
def wish(request, item_uuid):
    if request.method != "POST":
        return HttpResponseBadRequest(b"invalid request")
    item = get_object_or_404(Item, uid=base62.decode(item_uuid))
    if not item:
        return HttpResponseNotFound(b"item not found")
    request.user.shelf_manager.move_item(item, ShelfType.WISHLIST)
    if request.GET.get("back"):
        return HttpResponseRedirect(request.META.get("HTTP_REFERER"))
    return HttpResponse(_checkmark)


@login_required
def like(request, piece_uuid):
    if request.method != "POST":
        return HttpResponseBadRequest(b"invalid request")
    piece = get_object_or_404(Collection, uid=base62.decode(piece_uuid))
    if not piece:
        return HttpResponseNotFound(b"piece not found")
    Like.user_like_piece(request.user, piece)
    if request.GET.get("back"):
        return HttpResponseRedirect(request.META.get("HTTP_REFERER"))
    return HttpResponse(_checkmark)


@login_required
def unlike(request, piece_uuid):
    if request.method != "POST":
        return HttpResponseBadRequest(b"invalid request")
    piece = get_object_or_404(Collection, uid=base62.decode(piece_uuid))
    if not piece:
        return HttpResponseNotFound(b"piece not found")
    Like.user_unlike_piece(request.user, piece)
    if request.GET.get("back"):
        return HttpResponseRedirect(request.META.get("HTTP_REFERER"))
    return HttpResponse(_checkmark)


@login_required
def add_to_collection(request, item_uuid):
    item = get_object_or_404(Item, uid=base62.decode(item_uuid))
    if request.method == "GET":
        collections = Collection.objects.filter(owner=request.user)
        return render(
            request,
            "add_to_collection.html",
            {
                "item": item,
                "collections": collections,
            },
        )
    else:
        cid = int(request.POST.get("collection_id", default=0))
        if not cid:
            cid = Collection.objects.create(
                owner=request.user, title=f"{request.user.username}的收藏单"
            ).id
        collection = Collection.objects.get(owner=request.user, id=cid)
        collection.append_item(item, note=request.POST.get("note"))
        return HttpResponseRedirect(request.META.get("HTTP_REFERER"))


def render_relogin(request):
    return render(
        request,
        "common/error.html",
        {
            "url": reverse("users:connect") + "?domain=" + request.user.mastodon_site,
            "msg": _("信息已保存，但是未能分享到联邦网络"),
            "secondary_msg": _(
                "可能是你在联邦网络(Mastodon/Pleroma/...)的登录状态过期了，正在跳转到联邦网络重新登录😼"
            ),
        },
    )


@login_required
def mark(request, item_uuid):
    item = get_object_or_404(Item, uid=base62.decode(item_uuid))
    mark = Mark(request.user, item)
    if request.method == "GET":
        tags = TagManager.get_item_tags_by_user(item, request.user)
        shelf_types = [
            (n[1], n[2]) for n in iter(ShelfTypeNames) if n[0] == item.category
        ]
        shelf_type = request.GET.get("shelf_type", mark.shelf_type)
        return render(
            request,
            "mark.html",
            {
                "item": item,
                "mark": mark,
                "shelf_type": shelf_type,
                "tags": ",".join(tags),
                "shelf_types": shelf_types,
                "date_today": timezone.localdate().isoformat(),
            },
        )
    elif request.method == "POST":
        if request.POST.get("delete", default=False):
            mark.delete()
            return HttpResponseRedirect(request.META.get("HTTP_REFERER"))
        else:
            visibility = int(request.POST.get("visibility", default=0))
            rating = request.POST.get("rating", default=0)
            rating = int(rating) if rating else None
            status = ShelfType(request.POST.get("status"))
            text = request.POST.get("text")
            tags = request.POST.get("tags")
            tags = tags.split(",") if tags else []
            share_to_mastodon = bool(
                request.POST.get("share_to_mastodon", default=False)
            )
            mark_date = None
            if request.POST.get("mark_anotherday"):
                mark_date = timezone.get_current_timezone().localize(
                    parse_datetime(request.POST.get("mark_date") + " 20:00:00")
                )
                if mark_date and mark_date >= timezone.now():
                    mark_date = None
            TagManager.tag_item_by_user(item, request.user, tags, visibility)
            try:
                mark.update(
                    status,
                    text,
                    rating,
                    visibility,
                    share_to_mastodon=share_to_mastodon,
                    created_time=mark_date,
                )
            except Exception as e:
                _logger.warn(f"post to mastodon error {e}")
                return render_relogin(request)
            return HttpResponseRedirect(request.META.get("HTTP_REFERER"))
    return HttpResponseBadRequest()


def collection_retrieve(request, collection_uuid):
    collection = get_object_or_404(Collection, uid=base62.decode(collection_uuid))
    if not collection.is_visible_to(request.user):
        raise PermissionDenied()
    follower_count = collection.likes.all().count()
    following = (
        Like.user_liked_piece(request.user, collection) is not None
        if request.user.is_authenticated
        else False
    )
    is_featured = request.user.is_authenticated and collection.is_featured_by_user(
        request.user
    )
    available_as_featured = (
        request.user.is_authenticated
        and (following or request.user == collection.owner)
        and not is_featured
        and collection.members.all().exists()
    )
    stats = {}
    if is_featured:
        stats = collection.get_stats_for_user(request.user)
        stats["wishlist_deg"] = (
            stats["wishlist"] / stats["total"] * 360 if stats["total"] else 0
        )
        stats["progress_deg"] = (
            stats["progress"] / stats["total"] * 360 if stats["total"] else 0
        )
        stats["complete_deg"] = (
            stats["complete"] / stats["total"] * 360 if stats["total"] else 0
        )
    return render(
        request,
        "collection.html",
        {
            "collection": collection,
            "follower_count": follower_count,
            "following": following,
            "stats": stats,
            "available_as_featured": available_as_featured,
            "is_featured": is_featured,
        },
    )


def collection_add_featured(request, collection_uuid):
    if request.method != "POST":
        return HttpResponseBadRequest()
    collection = get_object_or_404(Collection, uid=base62.decode(collection_uuid))
    if not collection.is_visible_to(request.user):
        raise PermissionDenied()
    FeaturedCollection.objects.update_or_create(owner=request.user, target=collection)
    return HttpResponseRedirect(request.META.get("HTTP_REFERER"))


def collection_remove_featured(request, collection_uuid):
    if request.method != "POST":
        return HttpResponseBadRequest()
    collection = get_object_or_404(Collection, uid=base62.decode(collection_uuid))
    if not collection.is_visible_to(request.user):
        raise PermissionDenied()
    fc = FeaturedCollection.objects.filter(
        owner=request.user, target=collection
    ).first()
    if fc:
        fc.delete()
    return HttpResponseRedirect(request.META.get("HTTP_REFERER"))


def collection_share(request, collection_uuid):
    collection = (
        get_object_or_404(Collection, uid=base62.decode(collection_uuid))
        if collection_uuid
        else None
    )
    if collection and not collection.is_visible_to(request.user):
        raise PermissionDenied()
    if request.method == "GET":
        return render(request, "collection_share.html", {"collection": collection})
    elif request.method == "POST":
        visibility = int(request.POST.get("visibility", default=0))
        comment = request.POST.get("comment")
        if share_collection(collection, comment, request.user, visibility):
            return HttpResponseRedirect(request.META.get("HTTP_REFERER"))
        else:
            return render_relogin(request)
    else:
        return HttpResponseBadRequest()


def collection_retrieve_items(request, collection_uuid, edit=False, msg=None):
    collection = get_object_or_404(Collection, uid=base62.decode(collection_uuid))
    if not collection.is_visible_to(request.user):
        raise PermissionDenied()
    form = CollectionForm(instance=collection)
    return render(
        request,
        "collection_items.html",
        {
            "collection": collection,
            "form": form,
            "collection_edit": edit or request.GET.get("edit"),
            "msg": msg,
        },
    )


@login_required
def collection_append_item(request, collection_uuid):
    if request.method != "POST":
        return HttpResponseBadRequest()
    collection = get_object_or_404(Collection, uid=base62.decode(collection_uuid))
    if not collection.is_editable_by(request.user):
        raise PermissionDenied()

    url = request.POST.get("url")
    note = request.POST.get("note")
    item = Item.get_by_url(url)
    if item:
        collection.append_item(item, note=note)
        collection.save()
        msg = None
    else:
        msg = _("条目链接无法识别，请输入本站已有条目的链接。")
    return collection_retrieve_items(request, collection_uuid, True, msg)


@login_required
def collection_remove_item(request, collection_uuid, item_uuid):
    if request.method != "POST":
        return HttpResponseBadRequest()
    collection = get_object_or_404(Collection, uid=base62.decode(collection_uuid))
    item = get_object_or_404(Item, uid=base62.decode(item_uuid))
    if not collection.is_editable_by(request.user):
        raise PermissionDenied()
    collection.remove_item(item)
    return collection_retrieve_items(request, collection_uuid, True)


@login_required
def collection_move_item(request, direction, collection_uuid, item_uuid):
    if request.method != "POST":
        return HttpResponseBadRequest()
    collection = get_object_or_404(Collection, uid=base62.decode(collection_uuid))
    if not collection.is_editable_by(request.user):
        raise PermissionDenied()
    item = get_object_or_404(Item, uid=base62.decode(item_uuid))
    if direction == "up":
        collection.move_up_item(item)
    else:
        collection.move_down_item(item)
    return collection_retrieve_items(request, collection_uuid, True)


@login_required
def collection_update_item_note(request, collection_uuid, item_uuid):
    collection = get_object_or_404(Collection, uid=base62.decode(collection_uuid))
    if not collection.is_editable_by(request.user):
        raise PermissionDenied()
    item = get_object_or_404(Item, uid=base62.decode(item_uuid))
    if not collection.is_editable_by(request.user):
        raise PermissionDenied()
    if request.method == "POST":
        collection.update_item_metadata(
            item, {"note": request.POST.get("note", default="")}
        )
        return collection_retrieve_items(request, collection_uuid, True)
    elif request.method == "GET":
        member = collection.get_member_for_item(item)
        return render(
            request,
            "collection_update_item_note.html",
            {"collection": collection, "item": item, "note": member.note},
        )
    else:
        return HttpResponseBadRequest()


@login_required
def collection_edit(request, collection_uuid=None):
    collection = (
        get_object_or_404(Collection, uid=base62.decode(collection_uuid))
        if collection_uuid
        else None
    )
    if collection and not collection.is_editable_by(request.user):
        raise PermissionDenied()
    if request.method == "GET":
        form = CollectionForm(instance=collection) if collection else CollectionForm()
        return render(
            request, "collection_edit.html", {"form": form, "collection": collection}
        )
    elif request.method == "POST":
        form = (
            CollectionForm(request.POST, request.FILES, instance=collection)
            if collection
            else CollectionForm(request.POST)
        )
        if form.is_valid():
            if not collection:
                form.instance.owner = request.user
            form.instance.edited_time = timezone.now()
            form.save()
            return redirect(
                reverse("journal:collection_retrieve", args=[form.instance.uuid])
            )
        else:
            return HttpResponseBadRequest(form.errors)
    else:
        return HttpResponseBadRequest()


def review_retrieve(request, review_uuid):
    piece = get_object_or_404(Review, uid=base62.decode(review_uuid))
    if not piece.is_visible_to(request.user):
        raise PermissionDenied()
    return render(request, "review.html", {"review": piece})


@login_required
def review_edit(request, item_uuid, review_uuid=None):
    item = get_object_or_404(Item, uid=base62.decode(item_uuid))
    review = (
        get_object_or_404(Review, uid=base62.decode(review_uuid))
        if review_uuid
        else None
    )
    if review and not review.is_editable_by(request.user):
        raise PermissionDenied()
    if request.method == "GET":
        form = (
            ReviewForm(instance=review)
            if review
            else ReviewForm(initial={"item": item.id, "share_to_mastodon": True})
        )
        return render(
            request,
            "review_edit.html",
            {
                "form": form,
                "item": item,
                "date_today": timezone.localdate().isoformat(),
            },
        )
    elif request.method == "POST":
        form = (
            ReviewForm(request.POST, instance=review)
            if review
            else ReviewForm(request.POST)
        )
        print(form.instance.body)
        if form.is_valid():
            if not review:
                form.instance.owner = request.user
            form.instance.edited_time = timezone.now()
            mark_date = None
            if request.POST.get("mark_anotherday"):
                mark_date = timezone.get_current_timezone().localize(
                    parse_datetime(request.POST.get("mark_date") + " 20:00:00")
                )
                if mark_date and mark_date >= timezone.now():
                    mark_date = None
            if mark_date:
                form.instance.created_time = mark_date
            if request.POST.get("leading_space"):
                form.instance.body = re.sub(
                    r"^(\u2003*)( +)",
                    lambda s: "\u2003" * ((len(s[2]) + 1) // 2 + len(s[1])),
                    form.instance.body,
                    flags=re.MULTILINE,
                )
            form.save()
            if form.cleaned_data["share_to_mastodon"]:
                form.instance.save = lambda **args: None
                form.instance.shared_link = None
                if not share_review(form.instance):
                    return render_relogin(request)
            return redirect(
                reverse("journal:review_retrieve", args=[form.instance.uuid])
            )
        else:
            return HttpResponseBadRequest(form.errors)
    else:
        return HttpResponseBadRequest()


@login_required
def piece_delete(request, piece_uuid):
    piece = get_object_or_404(Piece, uid=base62.decode(piece_uuid))
    return_url = request.GET.get("return_url", None) or "/"
    if not piece.is_editable_by(request.user):
        raise PermissionDenied()
    if request.method == "GET":
        return render(
            request, "piece_delete.html", {"piece": piece, "return_url": return_url}
        )
    elif request.method == "POST":
        piece.delete()
        return redirect(return_url)
    else:
        return HttpResponseBadRequest()


def render_list_not_fount(request):
    msg = _("相关列表不存在")
    return render(
        request,
        "common/error.html",
        {
            "msg": msg,
        },
    )


def _render_list(
    request, user_name, type, shelf_type=None, item_category=None, tag_title=None
):
    user = User.get(user_name)
    if user is None:
        return render_user_not_found(request)
    if user != request.user and (
        request.user.is_blocked_by(user) or request.user.is_blocking(user)
    ):
        return render_user_blocked(request)
    tag = None
    if type == "mark":
        queryset = user.shelf_manager.get_members(shelf_type, item_category)
    elif type == "tagmember":
        tag = Tag.objects.filter(owner=user, title=tag_title).first()
        if not tag:
            return render_list_not_fount(request)
        if tag.visibility != 0 and user != request.user:
            return render_list_not_fount(request)
        queryset = TagMember.objects.filter(parent=tag)
    elif type == "review":
        queryset = Review.objects.filter(owner=user)
        queryset = queryset.filter(query_item_category(item_category))
    else:
        return HttpResponseBadRequest()
    queryset = queryset.filter(q_visible_to(request.user, user)).order_by(
        "-created_time"
    )
    paginator = Paginator(queryset, PAGE_SIZE)
    page_number = request.GET.get("page", default=1)
    members = paginator.get_page(page_number)
    members.pagination = PageLinksGenerator(PAGE_SIZE, page_number, paginator.num_pages)
    return render(
        request,
        f"user_{type}_list.html",
        {"user": user, "members": members, "tag": tag},
    )


@login_required
def user_mark_list(request, user_name, shelf_type, item_category):
    return _render_list(
        request, user_name, "mark", shelf_type=shelf_type, item_category=item_category
    )


@login_required
def user_tag_member_list(request, user_name, tag_title):
    return _render_list(request, user_name, "tagmember", tag_title=tag_title)


@login_required
def user_tag_edit(request):
    if request.method == "GET":
        tag_title = Tag.cleanup_title(request.GET.get("tag", ""))
        if not tag_title:
            return HttpResponseNotFound()
        tag = Tag.objects.filter(owner=request.user, title=tag_title).first()
        if not tag:
            return HttpResponseNotFound()
        return render(request, "tag_edit.html", {"tag": tag})
    elif request.method == "POST":
        tag_title = Tag.cleanup_title(request.POST.get("title", ""))
        tag_id = request.POST.get("id")
        tag = (
            Tag.objects.filter(owner=request.user, id=tag_id).first()
            if tag_id
            else None
        )
        if not tag or not tag_title:
            msg.error(request.user, _("无效标签"))
            return HttpResponseRedirect(request.META.get("HTTP_REFERER"))
        if request.POST.get("delete"):
            tag.delete()
            msg.info(request.user, _("标签已删除"))
            return redirect(
                reverse("journal:user_tag_list", args=[request.user.mastodon_username])
            )
        elif (
            tag_title != tag.title
            and Tag.objects.filter(owner=request.user, title=tag_title).exists()
        ):
            msg.error(request.user, _("标签已存在"))
            return HttpResponseRedirect(request.META.get("HTTP_REFERER"))
        tag.title = tag_title
        tag.visibility = Tag.cleanup_title(request.POST.get("visibility"))
        tag.visibility = 0 if tag.visibility == 0 else 2
        tag.save()
        msg.info(request.user, _("标签已修改"))
        return redirect(
            reverse(
                "journal:user_tag_member_list",
                args=[request.user.mastodon_username, tag.title],
            )
        )
    return HttpResponseBadRequest()


@login_required
def user_review_list(request, user_name, item_category):
    return _render_list(request, user_name, "review", item_category=item_category)


@login_required
def user_tag_list(request, user_name):
    user = User.get(user_name)
    if user is None:
        return render_user_not_found(request)
    if user != request.user and (
        request.user.is_blocked_by(user) or request.user.is_blocking(user)
    ):
        return render_user_blocked(request)
    tags = Tag.objects.filter(owner=user)
    if user != request.user:
        tags = tags.filter(visibility=0)
    tags = tags.values("title").annotate(total=Count("members")).order_by("-total")
    return render(
        request,
        "user_tag_list.html",
        {
            "user": user,
            "tags": tags,
        },
    )


@login_required
def user_collection_list(request, user_name):
    user = User.get(user_name)
    if user is None:
        return render_user_not_found(request)
    if user != request.user and (
        request.user.is_blocked_by(user) or request.user.is_blocking(user)
    ):
        return render_user_blocked(request)
    collections = Collection.objects.filter(owner=user)
    if user != request.user:
        if request.user.is_following(user):
            collections = collections.filter(visibility__in=[0, 1])
        else:
            collections = collections.filter(visibility=0)
    return render(
        request,
        "user_collection_list.html",
        {
            "user": user,
            "collections": collections,
        },
    )


@login_required
def user_liked_collection_list(request, user_name):
    user = User.get(user_name)
    if user is None:
        return render_user_not_found(request)
    if user != request.user and (
        request.user.is_blocked_by(user) or request.user.is_blocking(user)
    ):
        return render_user_blocked(request)
    collections = Collection.objects.filter(likes__owner=user)
    if user != request.user:
        collections = collections.filter(query_visible(request.user))
    return render(
        request,
        "user_collection_list.html",
        {
            "user": user,
            "collections": collections,
        },
    )


def profile_anonymous(request, id):
    login_url = settings.LOGIN_URL + "?next=" + request.get_full_path()
    try:
        username = id.split("@")[0]
        site = id.split("@")[1]
        return render(
            request,
            "users/home_anonymous.html",
            {
                "login_url": login_url,
                "username": username,
                "site": site,
            },
        )
    except Exception:
        return redirect(login_url)


def profile(request, user_name):
    if request.method != "GET":
        return HttpResponseBadRequest()
    user = User.get(user_name)
    if user is None:
        return render_user_not_found(request)
    if not request.user.is_authenticated and user.get_preference().no_anonymous_view:
        return profile_anonymous(request, user_name)
    # access one's own home page
    if user == request.user:
        reports = Report.objects.order_by("-submitted_time").filter(is_read=False)
        unread_announcements = Announcement.objects.filter(
            pk__gt=request.user.read_announcement_index
        ).order_by("-pk")
        try:
            request.user.read_announcement_index = Announcement.objects.latest("pk").pk
            request.user.save(update_fields=["read_announcement_index"])
        except ObjectDoesNotExist:
            # when there is no annoucenment
            pass
    # visit other's home page
    else:
        if user.is_blocked_by(request.user) or user.is_blocking(request.user):
            return render_user_blocked(request)
        # no these value on other's home page
        reports = None
        unread_announcements = None

    qv = q_visible_to(request.user, user)
    shelf_list = {}
    visbile_categories = [
        ItemCategory.Book,
        ItemCategory.Movie,
        ItemCategory.TV,
        ItemCategory.Music,
        # ItemCategory.Podcast,
        ItemCategory.Game,
    ]
    for category in visbile_categories:
        shelf_list[category] = {}
        for shelf_type in ShelfType:
            members = (
                user.shelf_manager.get_members(shelf_type, category)
                .filter(qv)
                .order_by("-created_time")
            )
            shelf_list[category][shelf_type] = {
                "title": user.shelf_manager.get_label(shelf_type, category),
                "count": members.count(),
                "members": members[:5].prefetch_related("item"),
            }
        reviews = (
            Review.objects.filter(owner=user)
            .filter(qv)
            .filter(query_item_category(category))
            .order_by("-created_time")
        )
        shelf_list[category]["reviewed"] = {
            "title": "评论过的" + category.label,
            "count": reviews.count(),
            "members": reviews[:5].prefetch_related("item"),
        }
    collections = (
        Collection.objects.filter(owner=user).filter(qv).order_by("-created_time")
    )
    liked_collections = (
        Like.user_likes_by_class(user, Collection)
        .order_by("-edited_time")
        .values_list("target_id", flat=True)
    )
    if user != request.user:
        liked_collections = liked_collections.filter(query_visible(request.user))

    return render(
        request,
        "profile.html",
        {
            "user": user,
            "top_tags": user.tag_manager.all_tags[:10],
            "shelf_list": shelf_list,
            "collections": collections[:5],
            "collections_count": collections.count(),
            "liked_collections": [
                Collection.objects.get(id=i)
                for i in liked_collections.order_by("-edited_time")[:5]
            ],
            "liked_collections_count": liked_collections.count(),
            "layout": user.get_preference().profile_layout,
            "reports": reports,
            "unread_announcements": unread_announcements,
        },
    )
