"""A Cart Action for downloading all items listed in cart as a ZIP file."""

from datetime import datetime
from io import BytesIO
from plone import api
from plone.api.exc import InvalidParameterError
from plone.memoize.view import memoize
from ploneintranet.core import _
from ploneintranet.workspace.browser.cart_actions.base import BaseCartView
from Products.Five.browser.pagetemplatefile import ViewPageTemplateFile

import logging
import zipfile

logger = logging.getLogger(__name__)


class DownloadView(BaseCartView):
    """Download Action implementation for downloading items listed in cart."""

    title = _("Download")
    form_data_pat_inject = None
    panel_size = "small"

    WRONG_TYPE = 1
    EMPTY_CONTENT = 2
    NO_PDF = 3

    def confirm(self):
        index = ViewPageTemplateFile("templates/download_confirmation.pt")
        return index(self)

    @property
    @memoize
    def html_types(self):
        try:
            html_types = api.portal.get_registry_record(
                "ploneintranet.docconv.html_types"
            )
        except InvalidParameterError:
            html_types = ["Document"]
        return html_types

    def is_file(self, obj):
        _marker = object()
        file_obj = getattr(obj, "file", _marker)
        return file_obj is not _marker

    def is_image(self, obj):
        return getattr(obj, "image", None)

    def is_pdf_available(self, obj):
        return (
            obj.portal_type in self.html_types
            and obj.restrictedTraverse("pdf").has_pdf()
        )

    def _skip_reason(self, obj):
        if self.is_file(obj):
            if not (obj.file and obj.file.filename):
                return self.EMPTY_CONTENT
            return None
        if obj.portal_type in self.html_types:
            if not self.is_pdf_available(obj):
                return self.NO_PDF
            return None
        if self.is_image(obj):
            return None
        return self.WRONG_TYPE

    def _expand_items(self, items):
        downloadable = []
        skipped = []
        for obj in items:
            if obj.portal_type == "Folder":
                expanded = self._expand_items(obj.objectValues())
                downloadable.extend(expanded[0])
                skipped.extend(expanded[1])
            elif self._skip_reason(obj):
                skipped.append(obj)
            else:
                downloadable.append(obj)
        return downloadable, skipped

    @property
    @memoize
    def _expanded_items(self):
        return self._expand_items(self.items)

    @property
    def downloadable_items(self):
        return self._expanded_items[0]

    @property
    def skipped_items(self):
        return self._expanded_items[1]

    def display_title(self, obj):
        cutoff = obj.aq_chain.index(self.context)
        parents = obj.aq_chain[:cutoff]
        return " → ".join([item.Title() for item in reversed(parents)])

    def download(self):
        """Download cart content.

        Before downloading items are packed into a zip archive (only the
        items that are files are included).

        """
        output = BytesIO()
        zf = zipfile.ZipFile(output, mode="w")

        downloadable_items = self.downloadable_items
        path_cutoff = len(self.context.getPhysicalPath())
        try:
            for obj in downloadable_items:
                if self.is_file(obj):
                    filename = obj.file.filename
                    data = obj.file.data
                elif self.is_image(obj):
                    filename = obj.image.filename
                    data = obj.image.data
                elif self.is_pdf_available(obj):
                    filename = obj.getId() + ".pdf"
                    pdf_view = obj.restrictedTraverse("pdf")
                    data = pdf_view.get_pdf()
                else:
                    logger.warning("Unhandled type - should have been skipped: %r", obj)
                    continue
                base_path = obj.getPhysicalPath()[path_cutoff:-1]
                path = "/".join(base_path + (filename,))
                zf.writestr(path, data)
        finally:
            zf.close()

        if not zf.filelist:
            api.portal.show_message(
                message=_("There are no downloadable items in your cart."),
                request=self.request,
                type="warning",
            )
            return self.redirect()

        self.request.response.setHeader("Content-Type", "application/zip")
        now = datetime.now()
        zipfilename = f"download-{now.year}-{now.month}-{now.day}.zip"
        self.request.response.setHeader(
            "Content-Disposition", "attachment; filename=%s" % zipfilename
        )
        return output.getvalue()
