from AccessControl import getSecurityManager
from Acquisition import aq_parent
from plone import api
from plone.app.dexterity.interfaces import IDXFileFactory
from plone.base.permissions import AddPortalContent
from plone.protect.authenticator import createToken
from ploneintranet.core import _
from ploneintranet.layout.browser.base import BasePanel
from ploneintranet.workspace.utils import parent_workspace
from Products.CMFCore.utils import getToolByName
from Products.Five import BrowserView
from Products.Five.browser.pagetemplatefile import ViewPageTemplateFile
from zope.interface import implementer
from zope.publisher.interfaces import IPublishTraverse

import mimetypes


@implementer(IPublishTraverse)
class BaseTextEditor(BrowserView):
    """Base text editor traverser view.

    Usage:

        @@texteditor/FIELDNAME
        @@texteditor-toolbar/FIELDNAME

    Or for variants with reduced set of controls:

        @@texteditor-mini/FIELDNAME
        @@texteditor-toolbar-mini/FIELDNAME

    FILEDNAME will be used once for:
    - Connecting the toolbar with the editor
    - Accessing the editable text from the context or the request.

    If the text comes from a different source, you can set it with ``set_text``.

    Examples::

        <div class="pat-rich-editor">
          <div class="canvas-toolbar inline">
            <tal:texteditor_toolbar
                replace="structure context/@@texteditor-toolbar/text" />
          </div>
          <div class="pat-rich">
            <tal:texteditor
                replace="structure context/@@texteditor/text" />
          </div>
        </div>

    Example with customized text::

        <div class="pat-rich-editor">
          <div class="canvas-toolbar inline">
            <tal:texteditor_toolbar
                replace="structure context/@@texteditor-toolbar-mini/about_us" />
          </div>
          <div class="pat-rich">
            <tal:texteditor define="
                              texteditor nocall:context/@@texteditor-mini;
                              text request/about_us|view/about_us|nothing;
                              dummy python:texteditor.set_text(text);
                            "
                            replace="structure texteditor/about_us"
            />
          </div>
        </div>

    .. note::
       The traverser path name will be set on self.
       This could potentially clash with any attributes or methods in this class
       or subclasses.
       If you run into problems, also check this kind of problem.
    """

    pattern_options = {}
    _placeholder = None
    _text = None

    def publishTraverse(self, request, name):
        """Get name from URL traversing.

        Example: http://localhost:8080/Plone/@@texteditor/text
        """
        self.name = str(name)
        return self

    def __getitem__(self, name):
        """Get name from unrestrictedTraverse.

        Example: <tal:el replace="structure context/@@texteditor/text" />
        """
        self.name = str(name)
        return self()

    def set_placeholder(self, value):
        """Customize the placeholder text."""
        self._placeholder = value

    def get_placeholder(self):
        """Get default or customized placeholder text."""
        if self._placeholder:
            # Return customized placeholder
            return self._placeholder

        # Return default placeholder
        return _("texteditor_default_placeholder", default="Write your text here")

    def set_text(self, value):
        """Set the editable text, if it is not part of the context."""
        self._text = value

    def get_text(self):
        """Return the editable text."""
        if self._text:
            # Return customized text
            return self._text

        # Return default text from context or request
        text = getattr(self.context, self.name, "")
        text = text or self.request.form.get(self.name) or ""
        text = getattr(text, "raw", text)
        return text

    @property
    def encoded_pattern_options(self):
        """Encode an options dict to CSS like pattern options syntax.

        :returns: String with CSS like pattern option syntax with a trailing ``;``
                  in the form "KEY1: VALUE1; KEY2: VALUE2;".
        :rtype: string
        """
        options = "; ".join(": ".join(it) for it in self.pattern_options.items())
        options = options + ";" if options else ""
        return options

    @property
    def show_source_button(self):
        """Return ``True`` if HTML source button should be shown, ``False``
        otherwise."""
        if "Manager" in api.user.get_roles():
            # Always show source button for Manager
            return True

        html_allowed_users = api.portal.get_registry_record(
            "ploneintranet.layout.editor_html_allowed_users"
        )
        return api.user.get_current().getUserName() in html_allowed_users


# Redactor


class TextEditorMiniRedactor(BaseTextEditor):
    @property
    def pattern_options(self):
        """Return the options for pat-redactor."""
        options = {
            "toolbar-external": f"#texteditor-toolbar-{self.name}",
            "buttons": "undo, redo, format, bold, italic, deleted, lists, link, line",  # noqa
            "formatting": "p, h1, h2, h3, blockquote, pre",
            "plugins": "alignment, table",
        }

        if self.show_source_button:
            options["show-source-button"] = "true"
            options["buttons"] += ", html"

        return options


class TextEditorRedactor(TextEditorMiniRedactor):
    @property
    def pattern_options(self):
        """Return the options for pat-redactor."""
        options = super().pattern_options

        token = createToken()
        upload_target_url = parent_workspace(self.context) or aq_parent(self.context)
        upload_target_url = upload_target_url.absolute_url()

        options["buttons"] = f"""{options["buttons"]}, image"""
        options["plugins"] = f"""{options["plugins"]}, video, imagemanager"""
        options["imageupload"] = (
            f"{upload_target_url}/@@images-upload.json?_authenticator={token}"
        )
        options["imagegetjson"] = f"{upload_target_url}/@@images.json"
        options["imageresizable"] = "true"

        return options


# tiptap


class TextEditorMiniTiptap(BaseTextEditor):
    show_italic_button = False
    show_image_button = False
    show_embed_button = False
    show_table_button = False
    show_undo_button = False
    show_redo_button = False


class TextEditorTiptap(BaseTextEditor):
    show_italic_button = True
    show_image_button = True
    show_embed_button = True
    show_table_button = True
    show_undo_button = True
    show_redo_button = True

    @property
    def pattern_options(self):
        """Return the options for pat-tiptap."""
        pattern_options = {}

        texteditor_url_protocols = api.portal.get_registry_record(
            "ploneintranet.layout.texteditor_url_protocols", default=()
        )
        if texteditor_url_protocols:
            # If there are extra protocols, add them to the pattern options
            pattern_options["link-extra-protocols"] = ", ".join(
                texteditor_url_protocols
            )

        return pattern_options


# Factories


class TextEditor(BaseTextEditor):
    def __call__(self):
        """Factory for the text editor."""
        texteditor = api.portal.get_registry_record(
            "ploneintranet.layout.texteditor", default="tiptap"
        )

        try:
            view = api.content.get_view(
                f"texteditor-{texteditor}", self.context, self.request
            )
        except api.exc.InvalidParameterError:
            texteditor = "tiptap"
            view = api.content.get_view(
                f"texteditor-{texteditor}", self.context, self.request
            )

        return view[self.name]


class TextEditorToolbar(BaseTextEditor):
    def __call__(self):
        """Factory for the text editor toolbar."""
        texteditor = api.portal.get_registry_record(
            "ploneintranet.layout.texteditor", default="tiptap"
        )

        try:
            view = api.content.get_view(
                f"texteditor-toolbar-{texteditor}", self.context, self.request
            )
        except api.exc.InvalidParameterError:
            texteditor = "tiptap"
            view = api.content.get_view(
                f"texteditor-toolbar-{texteditor}", self.context, self.request
            )

        return view[self.name]


# Factories - mini Variant


class TextEditorMini(BaseTextEditor):
    def __call__(self):
        """Factory for the text editor."""
        texteditor = api.portal.get_registry_record(
            "ploneintranet.layout.texteditor", default="tiptap"
        )

        try:
            view = api.content.get_view(
                f"texteditor-mini-{texteditor}", self.context, self.request
            )
        except api.exc.InvalidParameterError:
            texteditor = "tiptap"
            view = api.content.get_view(
                f"texteditor-mini-{texteditor}", self.context, self.request
            )

        return view[self.name]


class TextEditorToolbarMini(BaseTextEditor):
    def __call__(self):
        """Factory for the text editor toolbar."""
        texteditor = api.portal.get_registry_record(
            "ploneintranet.layout.texteditor", default="tiptap"
        )

        try:
            view = api.content.get_view(
                f"texteditor-toolbar-mini-{texteditor}", self.context, self.request
            )
        except api.exc.InvalidParameterError:
            texteditor = "tiptap"
            view = api.content.get_view(
                f"texteditor-toolbar-mini-{texteditor}", self.context, self.request
            )

        return view[self.name]


# Panels


class ImagePanel(BasePanel):
    """Image panel that is used to upload and pick images."""

    title = _("Image")
    error = None

    @property
    def images(self):
        return api.content.find(context=self.context, portal_type="Image")

    def __call__(self):
        """Displays the image panel template (search, selection or upload) or
        uploads an image on POST."""
        self.uploaded = []
        if self.request.REQUEST_METHOD != "POST":
            return super().__call__()

        template = ViewPageTemplateFile(
            "templates/texteditor_panel_image_upload_result.pt"
        )

        # Check if user has permission to add content here
        sm = getSecurityManager()
        if not sm.checkPermission(AddPortalContent, self.context):
            response = self.request.RESPONSE
            response.setStatus(403)
            self.error = _(
                "error_upload_not_authorized",
                default="You are not authorized to add content to this folder.",
            )
            return template(self)

        files = []
        if "file" in self.request.form:
            files = self.request.form.get("file", [])
        elif "file[]" in self.request.form:
            files = self.request.form.get("file[]", [])

        if not isinstance(files, (list, tuple)):
            files = [files]

        for cnt, filedata in enumerate(files):
            filename = filedata.filename
            content_type = mimetypes.guess_type(filename)[0] or ""

            ctr = getToolByName(self.context, "content_type_registry")
            type_ = ctr.findTypeName(filename.lower(), content_type, "") or "Image"

            # Now check that the object is not restricted to be added in the
            # current context
            allowed_ids = [fti.getId() for fti in self.context.allowedContentTypes()]
            if type_ not in allowed_ids:
                response = self.request.RESPONSE
                response.setStatus(403)
                self.error = _(
                    "image_upload_not_allowed",
                    "You cannot add this Image to this folder. Try another one.",
                )
                return template(self)

            obj = IDXFileFactory(self.context)(filename, content_type, filedata)
            self.uploaded.append({"src": f"{obj.absolute_url()}/@@images/image/3col"})

        return template(self)
