import os
from io import BytesIO
from tempfile import TemporaryDirectory
from typing import Optional

from django.contrib.auth.decorators import login_required
from django.http import FileResponse, HttpRequest, HttpResponse
from django.shortcuts import get_object_or_404, redirect, render
from django.utils.text import slugify
from django.utils.translation import ugettext as _

import labels
import PIL
from django_tables2 import RequestConfig
from reportlab.graphics import barcode, shapes

from biscuit.core.models import School
from biscuit.core.util import messages

from .forms import (
    BookAddISBNForm,
    BookCopiesBulkAddForm,
    BookCopyEditForm,
    BookEditForm,
    BookGetCopyForm,
    PersonBorrowForm,
)
from .models import Book, BookCopy
from .tables import BookCopiesTable, BooksTable
from .util import get_book_meta


@login_required
def books(request: HttpRequest) -> HttpResponse:
    context = {}

    # Get all books
    books = Book.objects.all()

    # Build table
    books_table = BooksTable(books)
    RequestConfig(request).configure(books_table)
    context["books_table"] = books_table

    return render(request, "exlibris/books.html", context)


@login_required
def add_book(request: HttpRequest) -> HttpResponse:
    context = {}

    book_add_isbn_form = BookAddISBNForm(request.POST or None)

    if request.method == "POST":
        if book_add_isbn_form.is_valid():
            return redirect(
                "edit_book_by_isbn", isbn=book_add_isbn_form.cleaned_data["isbn"]
            )

    context["book_add_isbn_form"] = book_add_isbn_form

    return render(request, "exlibris/add_book.html", context)


@login_required
def edit_book(
    request: HttpRequest, id_: Optional[int] = None, isbn: Optional[str] = None
) -> HttpResponse:
    context = {}

    if id_:
        # If an id is passed, get this book to edit it

        book = get_object_or_404(Book, pk=id_)
        book_edit_form = BookEditForm(
            request.POST or None, request.FILES or None, instance=book
        )

        context["book"] = book
    else:
        # If no id was passed, create a new book

        # An ISBN can be passed as argument. If so, the
        # form will be pre-populated with data on that book.
        initial = get_book_meta(isbn)

        if initial:
            messages.info(request, _("Information found for this ISBN was pre-filled."))
        else:
            messages.warning(request, _("No information found for this ISBN."))

        book_edit_form = BookEditForm(
            request.POST or None, request.FILES or None, initial=initial
        )

    if request.method == "POST":
        if book_edit_form.is_valid():
            book_edit_form.save(commit=True)

            messages.success(request, _("The book has been saved."))
            return redirect("books")

    context["book_edit_form"] = book_edit_form

    return render(request, "exlibris/edit_book.html", context)


@login_required
def delete_book(request: HttpRequest, id_: int) -> HttpResponse:
    book = get_object_or_404(Book, pk=id_)
    title = book.title

    if book.is_deleteable():
        book.delete()
        messages.success(request, _("Book %s has been deleted.") % title)
    else:
        messages.error(
            request, _("Books with copies that have been borrowed cannot be deleted.")
        )

    return redirect("books")


@login_required
def book(request: HttpRequest, id_: int, template: str) -> HttpResponse:
    context = {}

    book = get_object_or_404(Book, pk=id_)
    context["book"] = book

    # Get all copies
    copies = book.copies.all()

    # Build table
    copies_table = BookCopiesTable(copies)
    RequestConfig(request).configure(copies_table)
    context["copies_table"] = copies_table

    return render(request, "exlibris/book_%s.html" % template, context)


@login_required
def add_book_copies(request: HttpRequest, id_: int) -> HttpResponse:
    context = {}

    book = get_object_or_404(Book, pk=id_)
    context["book"] = book

    bulk_add_form = BookCopiesBulkAddForm(request.POST or None)

    if request.method == "POST":
        if bulk_add_form.is_valid():
            new_copies = [
                BookCopy(book=book) for _ in range(bulk_add_form.cleaned_data["count"])
            ]
            for new_copy in new_copies:
                new_copy.save()

            messages.success(
                request,
                _("%d copies have been created.") % bulk_add_form.cleaned_data["count"],
            )
            return redirect("book_by_id", id_=book.pk)

    context["book"] = book
    context["bulk_add_form"] = bulk_add_form

    return render(request, "exlibris/add_book_copies.html", context)


@login_required
def edit_book_copy(request: HttpRequest, id_: int) -> HttpResponse:
    context = {}

    book_copy = get_object_or_404(BookCopy, pk=id_)
    book_copy_edit_form = BookCopyEditForm(request.POST or None, instance=book_copy)

    context["book_copy"] = book_copy

    if request.method == "POST":
        if book_copy_edit_form.is_valid():
            book_copy_edit_form.save(commit=True)

            messages.success(request, _("The book copy has been saved."))
            return redirect("book_by_id", id_=book_copy.book.pk)

    context["book_copy_edit_form"] = book_copy_edit_form

    return render(request, "exlibris/edit_book_copy.html", context)


@login_required
def delete_book_copy(request: HttpRequest, id_: int) -> HttpResponse:
    book_copy = get_object_or_404(BookCopy, pk=id_)
    book = book_copy.book

    if book_copy.is_deleteable():
        book_copy.delete()
        messages.success(request, _("Book copy of %s has been deleted.") % book.title)
    else:
        messages.error(
            request, _("Book copies that have been borrowed cannot be deleted.")
        )

    return redirect("book_by_id", id_=book.id)


@login_required
def book_copies_labels(request: HttpRequest, id_: int) -> HttpResponse:
    book = get_object_or_404(Book, pk=id_)
    copies = book.copies.all()

    with TemporaryDirectory() as temp_dir:

        def draw_label(label, width, height, obj):
            title = shapes.String(0, height - 10, obj.book.title, fontSize=10)
            label.add(title)

            copy_desc = shapes.String(
                0, height - 20, _("Copy number: %d") % obj.copy_num, fontSize=8
            )
            label.add(copy_desc)

            barcode_raw = barcode.createBarcodeImageInMemory(
                "Code128", value=obj.barcode, width=400, height=120
            )
            barcode_image = PIL.Image.open(BytesIO(barcode_raw))
            barcode_file = os.path.join(temp_dir, "%s.png" % obj.barcode)
            barcode_image.save(barcode_file, "png")
            label.add(
                shapes.Image(0, 30, width, 30, os.path.join(temp_dir, barcode_file))
            )

            school = shapes.String(0, 8, School.objects.first().name, fontSize=10)
            label.add(school)

            bottom_line = shapes.String(
                0, 0, "BiscuIT, the Free School Information System", fontSize=6
            )
            label.add(bottom_line)

        specs = labels.Specification(
            210,
            297,
            3,
            7,
            63,
            40,
            left_padding=3,
            top_padding=3,
            bottom_padding=3,
            right_padding=3,
        )
        sheet = labels.Sheet(specs, draw_label)

        sheet.add_labels(copies)

        pdf_file = os.path.join(temp_dir, "label_%s.pdf" % slugify(book.title))
        sheet.save(pdf_file)

        return FileResponse(open(pdf_file, "rb"), as_attachment=True)


@login_required
def get_copy(request: HttpRequest) -> HttpResponse:
    context = {}

    book_copy_form = BookGetCopyForm(request.POST or None)

    if request.method == "POST":
        if book_copy_form.is_valid():
            return redirect(
                "book_copy_by_barcode", barcode=book_copy_form.cleaned_data["barcode"]
            )

    context["book_copy_form"] = book_copy_form

    return render(request, "exlibris/get_copy.html", context)


@login_required
def book_copy(request: HttpRequest, barcode: str, template: str) -> HttpResponse:
    context = {}

    book_copy = get_object_or_404(BookCopy, barcode=barcode)
    context["book_copy"] = book_copy

    return render(request, "exlibris/copy_%s.html" % template, context)


@login_required
def person_borrow(request: HttpRequest) -> HttpResponse:
    context = {}

    person_borrow_form = PersonBorrowForm(request.POST or None)

    if request.method == "POST":
        if person_borrow_form.is_valid():
            person = person_borrow_form.cleaned_data["borrower"]

            for barcode_line in person_borrow_form.cleaned_data[
                "barcodes"
            ].splitlines():
                try:
                    book_copy = BookCopy.objects.get(barcode=barcode_line)
                except BookCopy.DoesNotExist:
                    messages.error(
                        request,
                        _("Barcode %(barcode)s is invalid.")
                        % {"barcode": barcode_line},
                    )
                    continue

                book_copy.borrower = person
                book_copy.borrow_count = book_copy.borrow_count + 1
                book_copy.save()

                messages.success(
                    request,
                    _("Book %(title)s borrowed to %(person)s.")
                    % {"title": book_copy.book.title, "person": person},
                )

            person_borrow_form = PersonBorrowForm()

    context["person_borrow_form"] = person_borrow_form

    return render(request, "exlibris/person_borrow.html", context)