""" the good stuff! the books! """
from re import sub, findall
from django.contrib.auth.decorators import login_required, permission_required
from django.contrib.postgres.search import SearchRank, SearchVector
from django.db import transaction
from django.http import HttpResponseBadRequest
from django.shortcuts import get_object_or_404, redirect
from django.template.response import TemplateResponse
from django.utils.decorators import method_decorator
from django.views.decorators.http import require_POST
from django.views import View

from bookwyrm import book_search, forms, models

# from bookwyrm.activitypub.base_activity import ActivityObject
from bookwyrm.utils.isni import (
    find_authors_by_name,
    build_author_from_isni,
    augment_author_metadata,
)
from bookwyrm.views.helpers import get_edition
from .books import set_cover_from_url

# pylint: disable=no-self-use
@method_decorator(login_required, name="dispatch")
@method_decorator(
    permission_required("bookwyrm.edit_book", raise_exception=True), name="dispatch"
)
class EditBook(View):
    """edit a book"""

    def get(self, request, book_id):
        """info about a book"""
        book = get_edition(book_id)
        # This doesn't update the sort title, just pre-populates it in the form
        if book.sort_title in ["", None]:
            book.sort_title = book.guess_sort_title()
        if not book.description:
            book.description = book.parent_work.description
        data = {"book": book, "form": forms.EditionForm(instance=book)}
        return TemplateResponse(request, "book/edit/edit_book.html", data)

    def post(self, request, book_id):
        """edit a book cool"""
        book = get_object_or_404(models.Edition, id=book_id)

        form = forms.EditionForm(request.POST, request.FILES, instance=book)

        data = {"book": book, "form": form}
        ensure_transient_values_persist(request, data)
        if not form.is_valid():
            ensure_transient_values_persist(request, data, add_author=True)
            return TemplateResponse(request, "book/edit/edit_book.html", data)

        data = add_authors(request, data)

        # either of the above cases requires additional confirmation
        if data.get("add_author"):
            return TemplateResponse(request, "book/edit/edit_book.html", data)

        remove_authors = request.POST.getlist("remove_authors")
        for author_id in remove_authors:
            book.authors.remove(author_id)

        book = form.save(request, commit=False)

        url = request.POST.get("cover-url")
        if url:
            image = set_cover_from_url(url)
            if image:
                book.cover.save(*image, save=False)

        book.save()
        return redirect(f"/book/{book.id}")


@method_decorator(login_required, name="dispatch")
@method_decorator(
    permission_required("bookwyrm.edit_book", raise_exception=True), name="dispatch"
)
class CreateBook(View):
    """brand new book"""

    def get(self, request):
        """info about a book"""
        data = {"form": forms.EditionForm()}
        return TemplateResponse(request, "book/edit/edit_book.html", data)

    # pylint: disable=too-many-locals
    def post(self, request):
        """create a new book"""
        # returns None if no match is found
        form = forms.EditionForm(request.POST, request.FILES)
        data = {"form": form}

        # collect data provided by the work or import item
        parent_work_id = request.POST.get("parent_work")
        authors = None
        if request.POST.get("authors"):
            author_ids = findall(r"\d+", request.POST["authors"])
            authors = models.Author.objects.filter(id__in=author_ids)

        # fake book in case we need to keep editing
        if parent_work_id:
            data["book"] = {
                "parent_work": {"id": parent_work_id},
                "authors": authors,
            }

        if not form.is_valid():
            ensure_transient_values_persist(request, data, form=form)
            return TemplateResponse(request, "book/edit/edit_book.html", data)

        # we have to call this twice because it requires form.cleaned_data
        # which only exists after we validate the form
        ensure_transient_values_persist(request, data, form=form)
        data = add_authors(request, data)

        # check if this is an edition of an existing work
        author_text = ", ".join(data.get("add_author", []))
        data["book_matches"] = book_search.search(
            f'{form.cleaned_data.get("title")} {author_text}',
            min_confidence=0.1,
        )[:5]

        # go to confirm mode
        if not parent_work_id or data.get("add_author"):
            data["confirm_mode"] = True
            return TemplateResponse(request, "book/edit/edit_book.html", data)

        with transaction.atomic():
            book = form.save(request)
            parent_work = get_object_or_404(models.Work, id=parent_work_id)
            book.parent_work = parent_work

            if authors:
                book.authors.add(*authors)

            url = request.POST.get("cover-url")
            if url:
                image = set_cover_from_url(url)
                if image:
                    book.cover.save(*image, save=False)

            book.save()
        return redirect(f"/book/{book.id}")


def ensure_transient_values_persist(request, data, **kwargs):
    """ensure that values of transient form fields persist when re-rendering the form"""
    data["cover_url"] = request.POST.get("cover-url")
    if kwargs and kwargs.get("form"):
        data["book"] = data.get("book") or {}
        data["book"]["subjects"] = kwargs["form"].cleaned_data["subjects"]
        data["add_author"] = request.POST.getlist("add_author")
    elif kwargs and kwargs.get("add_author") is True:
        data["add_author"] = request.POST.getlist("add_author")


def add_authors(request, data):
    """helper for adding authors"""
    add_author = [author for author in request.POST.getlist("add_author") if author]
    if not add_author:
        data["add_author"] = []
        return data

    data["add_author"] = add_author
    data["author_matches"] = []
    data["isni_matches"] = []

    # creating a book or adding an author to a book needs another step
    data["confirm_mode"] = True
    # this isn't preserved because it isn't part of the form obj
    data["remove_authors"] = request.POST.getlist("remove_authors")

    for author in add_author:
        # filter out empty author fields
        if not author:
            continue
        # check for existing authors
        vector = SearchVector("name", weight="A") + SearchVector("aliases", weight="B")

        author_matches = (
            models.Author.objects.annotate(search=vector)
            .annotate(rank=SearchRank(vector, author))
            .filter(rank__gt=0.4)
            .order_by("-rank")[:5]
        )

        isni_authors = find_authors_by_name(
            author, description=True
        )  # find matches from ISNI API

        # dedupe isni authors we already have in the DB
        exists = [
            i
            for i in isni_authors
            for a in author_matches
            if sub(r"\D", "", str(i.isni)) == sub(r"\D", "", str(a.isni))
        ]

        # pylint: disable=cell-var-from-loop
        matches = list(filter(lambda x: x not in exists, isni_authors))
        # combine existing and isni authors
        matches.extend(author_matches)

        data["author_matches"].append(
            {
                "name": author.strip(),
                "matches": matches,
                "existing_isnis": exists,
            }
        )
    return data


@require_POST
@permission_required("bookwyrm.edit_book", raise_exception=True)
def create_book_from_data(request):
    """create a book with starter data"""
    author_ids = findall(r"\d+", request.POST.get("authors"))
    book = {
        "parent_work": {"id": request.POST.get("parent_work")},
        "authors": models.Author.objects.filter(id__in=author_ids).all(),
        "subjects": request.POST.getlist("subjects"),
    }

    data = {"book": book, "form": forms.EditionForm(request.POST)}
    return TemplateResponse(request, "book/edit/edit_book.html", data)


@method_decorator(login_required, name="dispatch")
@method_decorator(
    permission_required("bookwyrm.edit_book", raise_exception=True), name="dispatch"
)
class ConfirmEditBook(View):
    """confirm edits to a book"""

    # pylint: disable=too-many-locals
    # pylint: disable=too-many-branches
    def post(self, request, book_id=None):
        """edit a book cool"""
        # returns None if no match is found
        book = models.Edition.objects.filter(id=book_id).first()
        form = forms.EditionForm(request.POST, request.FILES, instance=book)

        data = {"book": book, "form": form}
        if not form.is_valid():
            return TemplateResponse(request, "book/edit/edit_book.html", data)

        with transaction.atomic():
            # save book
            book = form.save(request)

            # add known authors
            authors = None
            if request.POST.get("authors"):
                author_ids = findall(r"\d+", request.POST["authors"])
                authors = models.Author.objects.filter(id__in=author_ids)
                book.authors.add(*authors)

            # get or create author as needed
            for i in range(int(request.POST.get("author-match-count", 0))):
                match = request.POST.get(f"author_match-{i}")
                if not match:
                    return HttpResponseBadRequest()
                try:
                    # if it's an int, it's an ID
                    match = int(match)
                    author = get_object_or_404(
                        models.Author, id=request.POST[f"author_match-{i}"]
                    )
                    # update author metadata if the ISNI record is more complete
                    isni = request.POST.get(f"isni-for-{match}", None)
                    if isni is not None:
                        augment_author_metadata(author, isni)
                except ValueError:
                    # otherwise it's a new author
                    isni_match = request.POST.get(f"author_match-{i}")
                    author_object = build_author_from_isni(isni_match)
                    # with author data class from isni id
                    if "author" in author_object:
                        skeleton = models.Author.objects.create(
                            name=author_object["author"].name
                        )
                        author = author_object["author"].to_model(
                            model=models.Author, overwrite=True, instance=skeleton
                        )
                    else:
                        # or it's just a name
                        author = models.Author.objects.create(name=match)
                book.authors.add(author)

            # create work, if needed
            if not book.parent_work:
                work_match = request.POST.get("parent_work")
                if work_match and work_match != "0":
                    work = get_object_or_404(models.Work, id=work_match)
                else:
                    work = models.Work.objects.create(title=form.cleaned_data["title"])
                    work.authors.set(book.authors.all())
                book.parent_work = work

            for author_id in request.POST.getlist("remove_authors"):
                book.authors.remove(author_id)

            # import cover, if requested
            url = request.POST.get("cover-url")
            if url:
                image = set_cover_from_url(url)
                if image:
                    book.cover.save(*image, save=False)

            # we don't tell the world when creating a book
            book.save(broadcast=False)

        return redirect(f"/book/{book.id}")
