from AccessControl import Unauthorized
from DateTime import DateTime
from json import dumps
from os.path import splitext
from plone import api
from plone.app.event.base import default_timezone
from plone.memoize import view
from plone.namedfile.file import NamedBlobFile
from ploneintranet import api as pi_api
from ploneintranet.core import _
from ploneintranet.layout.browser.base import BasePanel
from ploneintranet.workspace.basecontent.event import EventView
from ploneintranet.workspace.basecontent.utils import dexterity_update
from ploneintranet.workspace.basecontent.utils import get_selection_classes
from ploneintranet.workspace.config import INTRANET_USERS_GROUP_ID
from ploneintranet.workspace.config import TEMPLATES_FOLDER
from ploneintranet.workspace.events import ObjectModifiedAfterCreationEvent
from ploneintranet.workspace.utils import parent_workspace
from Products.CMFCore.permissions import AddPortalContent
from Products.Five.browser.pagetemplatefile import ViewPageTemplateFile
from zope.container.interfaces import INameChooser
from zope.event import notify
from zope.lifecycleevent import Attributes
from zope.lifecycleevent import ObjectModifiedEvent


class AddBase(BasePanel):
    """Basic stuff for adding contents"""

    TEMPLATES_FOLDER = TEMPLATES_FOLDER

    title = _("Create document")
    template = ViewPageTemplateFile("templates/add_content.pt")
    panel_size = "medium"
    panel_style = "panel"
    panel_instance = ""
    can_edit = True
    suppress_messages = True
    _form_data_pat_inject_parts = (
        "#document-body",
        "#workspace-documents",
        "#global-statusmessage; loading-class: ''",
    )
    is_add_form = True

    @property
    @view.memoize_contextless
    def templates_folder(self):
        return api.portal.get().get(self.TEMPLATES_FOLDER, {})

    @property
    def allow_video_uploads(self):
        allow_video_uploads = api.portal.get_registry_record(
            "ploneintranet.workspace.allow_video_uploads"
        )
        return allow_video_uploads

    @property
    @view.memoize
    def form_data_pat_validation(self):
        """Proper pat-validation options.
        We need to match the timestamp of the create button.
        """
        return "disable-selector:#form-buttons-create-{timestamp}".format(
            timestamp=self.form_timestamp()
        )

    @property
    @view.memoize
    def portal(self):
        """Return the portal"""
        return api.portal.get()

    @property
    @view.memoize
    def workspace_container(self):
        """Return the workspace container"""
        return self.portal.workspaces

    @property
    @view.memoize
    def parent_workspace(self):
        """Return the parent workspace"""
        return parent_workspace(self.context)

    @property
    @view.memoize
    def user(self):
        """The currently authenticated ploneintranet user profile (if any)"""
        return pi_api.userprofile.get_current()

    @property
    @view.memoize
    def content_helper_view(self):
        """Use the content_helper_view"""
        return api.content.get_view("content_helper_view", self.context, self.request)

    @property
    @view.memoize
    def allusers_json_url(self):
        """Return @@allusers.json in the proper context"""
        return f"{self.parent_workspace.absolute_url()}/@@allusers.json"

    def get_data_pat_autosuggest(self, fieldname):
        """Return the data-pat-autosuggest for a fieldname"""
        user = self.user
        if fieldname == "initiator" and self.request.method == "GET" and user:
            default_field_value = user.getId()
        else:
            default_field_value = ""

        prefill_json = self.content_helper_view.safe_member_prefill(
            self.context, fieldname, default=default_field_value
        )
        if not prefill_json:
            prefill_json = "{}"

        if user and fieldname == "initiator" and prefill_json == user.getId():
            prefill_json = dumps({user.username: user.fullname})

        return "; ".join(
            (
                "ajax-data-type: json",
                "maximum-selection-size: 1",
                "selection-classes: {}",
                f"ajax-url: {self.allusers_json_url}",
                "allow-new-words: false",
                f"prefill-json: {prefill_json}",
            )
        )

    def redirect(self, url):
        """
        Has its own method to allow overriding
        """
        url = f"{url}/view?show_sidebar"
        return self.request.response.redirect(url)

    def validate(self):
        """Validate input and return a truish"""
        return True

    def get_new_unique_id(self, container):
        """This will get a new unique id according to the request"""
        form = self.request.form
        suggested_id = form.get("id") or form.get("title") or self.title
        chooser = INameChooser(container)
        new_id = chooser.chooseName(suggested_id, container)
        return new_id

    def get_new_object(self, container=None):
        """This will create a new object"""
        if not container:
            container = self.context
        extra = {}
        title = self.request.get("title")
        if title:
            extra["title"] = title
        obj = api.content.create(
            container=container,
            type=self.portal_type,
            id=self.get_new_unique_id(container),
            safe_id=False,
            **extra,
        )
        return container[obj.getId()]

    def update(self, obj):
        """Update the object and returns the modified fields and errors"""
        modified, errors = dexterity_update(obj)
        return modified, errors

    def create(self, container=None):
        """
        Create content in the given container and return url.
        Uses dexterity_update to set the
        appropriate fields after creation.
        """
        if not self.validate():
            # BBB: do something clever that works with pat-inject
            # at the moment the @@add_something form is not a complete page
            # but just some markup,
            # so we cannot show that one here
            pass

        with pi_api.env.indexing_disabled(self.request):
            new = self.get_new_object(container)

        modified, errors = self.update(new)

        if not errors:
            if not self.suppress_messages:
                api.portal.show_message(
                    _("Item created."), request=self.request, type="success"
                )
            new.reindexObject()
            descriptions = []
            if modified:
                descriptions = [
                    Attributes(interface, *fields)
                    for interface, fields in modified.items()
                ]

            notify(ObjectModifiedAfterCreationEvent(new, *descriptions))
        else:
            api.portal.show_message(
                _("There was a problem: ${errors}.", mapping={"errors": errors}),
                request=self.request,
                type="error",
            )

        return new.absolute_url()

    def __call__(self):
        """Render form, or handle POST and redirect"""
        title = self.request.form.get("title", None)
        portal_type = self.request.form.get("portal_type", "")
        container_path = self.request.form.get("container", None)
        if container_path:
            container = self.context.restrictedTraverse(container_path)
        else:
            container = self.context

        if title is not None:
            self.portal_type = portal_type.strip()
            self.title = title.strip()
            if self.portal_type in api.portal.get_tool("portal_types"):
                url = self.create(container)
                return self.redirect(url)
        return self.template()


class AddContent(AddBase):
    """"""

    title = _("Create document")
    panel_instance = "panel-create-document"

    @property
    @view.memoize
    def office_templates_folder(self):
        return self.templates_folder.get("office", {})

    @property
    @view.memoize
    def selected_template(self):
        """The available office templates (if any) in our templates folder"""
        return self.office_templates_folder.get(
            self.request.form.get("office_template")
        )

    @property
    @view.memoize
    def office_templates(self):
        """The available office templates (if any) in our templates folder"""
        if not self.office_templates_folder:
            return []
        return self.office_templates_folder.listFolderContents({"portal_type": "File"})

    @property
    @view.memoize
    def tabs(self):
        tabs = [
            {
                "css_class": "icon-doc-text current",
                "id": "sheet-text",
                "label": _("Rich text"),
            },
            {"css_class": "icon-file-image", "id": "sheet-image", "label": _("Image")},
            {
                "css_class": "icon-video",
                "id": "sheet-video",
                "is_formless": True,
                "label": _("Video"),
            },
        ]
        if self.office_templates:
            tabs.insert(
                1,
                {
                    "css_class": "icon-document",
                    "id": "sheet-office-document",
                    "label": _("Office document"),
                },
            )
        return tabs

    def get_new_object(self, container=None):
        """Override the default method to set to give the possibility
        to set a file based on a template"""
        obj = super().get_new_object(container=container)
        if obj.portal_type == "File":
            office_template = self.selected_template
            if office_template:
                # filename will be the title based id + the extension of the template
                filename = "".join(
                    (obj.getId(), splitext(office_template.file.filename)[1])
                )
                obj.file = NamedBlobFile(
                    data=office_template.file.data, filename=filename
                )
        return obj

    def redirect(self, url):
        """Redirect to the view but eventually trigger OO (if present)
        to immediately edit a file based on a template
        """
        url = f"{url}/view?show_sidebar"
        if self.selected_template:
            url += "&trigger_onlyoffice=1"
        return self.request.response.redirect(url)


class AddFolder(AddBase):
    """The add folder view"""

    template = ViewPageTemplateFile("templates/add_folder.pt")
    title = _("Create folder")
    _form_data_pat_inject_parts = ("#workspace-documents", "nav.breadcrumbs")


class AddLink(AddBase):
    """The add link view"""

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

    title = _("Create link")
    form_portal_type = "Link"
    form_input_title_placeholder = _("Link name")

    def extra_fields(self):
        """BBB: this should be done in a cleaner way..."""
        return """
            <label>
            <input name="remoteUrl"
                   type="url"
                   placeholder="http(s)://..."
                   value="{remoteUrl}"
                   required="required"
              />
            </label>
        """.format(remoteUrl=self.request.form.get("remoteUrl", ""))


class AddEvent(AddBase):
    """The add event view"""

    template = ViewPageTemplateFile("templates/add_event.pt")
    title = _("Create event")

    _add_event_tab = {
        "css_class": "icon-calendar",
        "id": "sheet-event",
        "label": _("Event"),
    }
    _add_calendar_tab = {
        "css_class": "icon-calendar-1",
        "id": "sheet-calendar",
        "label": _("Calendar"),
    }
    intranet_users_group_id = INTRANET_USERS_GROUP_ID

    @property
    @view.memoize
    def tabs(self):
        tabs = []
        if self.can_add_events():
            tabs.append(self._add_event_tab)
        if self.can_add_calendars():
            tabs.append(self._add_calendar_tab)
            self.title = _("Create event or calendar")
        if tabs:
            tabs[0]["css_class"] += " current"
        return tabs

    @property
    @view.memoize
    def panel_instance(self):
        if len(self.tabs) > 1:
            return "panel-create-event-or-calendar"
        return "panel-create-event"

    @property
    @view.memoize_contextless
    def calendar_container(self):
        return api.portal.get().get("calendars", None)

    @property
    @view.memoize_contextless
    def app_calendar_view(self):
        return api.content.get_view(
            "app-calendar", api.portal.get().apps.calendar, self.request
        )

    @view.memoize
    def can_add_events(self):
        """Check if we can add events.
        Start first checking the current context and then see
        if we can add them somewhere else
        """
        if self.parent_workspace and api.user.has_permission(
            AddPortalContent, obj=self.parent_workspace
        ):
            return True
        view = api.content.get_view(
            context=self.context, request=self.request, name="calendar_picker.json"
        )
        return view.has_writable_workspaces()

    @view.memoize
    def can_add_calendars(self):
        if not self.request.get("app", None):
            return False
        if self.request.get("in_calendar", None):
            return False
        return api.user.has_permission(
            "Add portal content", obj=self.calendar_container
        )

    def fix_start_end(self):
        """If the start date is lower than the end one,
        modify the request setting end = start + 1 hour
        """
        localized_start = DateTime(
            "%s %s"
            % (
                " ".join(self.request.get("start")),
                self.request.get("timezone", default_timezone()),
            )
        )
        localized_end = localized_start + 1.0 / 24
        # If you know a smarter way to hijack the request,
        # please modify the following lines:)
        self.request.end = [
            localized_end.strftime("%Y-%m-%d"),
            localized_end.strftime("%H:%M"),
        ]
        self.request.form["end"] = self.request.end
        self.request.other["end"] = self.request.end

        ts = api.portal.get_tool("translation_service")
        msg = _(
            "dates_hijacked",
            default=(
                "Start time should be lower than end time. "
                "The system set the end time to: ${date}"
            ),
            mapping={"date": ts.toLocalizedTime(localized_end)},
        )
        api.portal.show_message(msg, request=self.request, type="warning")

    def validate(self):
        """Override base content validation

        Return truish if valid
        """
        if "start" in self.request and "end" in self.request:
            if self.request.get("start") > self.request.get("end"):
                self.fix_start_end()

        return True

    # The following two methods are used to determine the next step
    # after adding or editing events.
    #
    # This is an issue because we can add events or edit them
    # and this can happen from either within a workspace or from the app
    # or through a modal openend through the + icon.
    # All six combinations require different redirects.
    # The whole situation is not fully trivial, because once we are on
    # the event edit form, the context is the event and not the workspace
    # or app anymore, so we don't easily know where we are.

    @property
    @view.memoize
    def _form_data_pat_inject_parts(self):
        """Returns the correct dpi config"""
        in_app = self.request.get("app")
        selectors = []

        if in_app:
            # In the app, we want to reload the left hand sidebar
            # and the calendar part in document-content
            selectors.append("#sidebar")
            selectors.append("#document-body")
        else:
            # Probably on a workspace
            selectors.append("#workspace-events")
            selectors.append("#document-body")

        parts = ["source: {0}; target: {0};".format(selector) for selector in selectors]
        # Update status message in case there was an error
        parts.append(
            "source: {0}; target: {0}; loading-class: ''".format(
                "#global-statusmessage"
            )
        )

        return parts

    def redirect(self, url):
        """Try to find the proper redirect context."""
        # See if we are adding the event from within a workspace
        # Wnen adding from a calendar, parent_workspace will be None
        workspace = self.parent_workspace
        # in_calendar tells us if the add or edit has been triggered from
        # within the calendar view. false if the add or edit was through
        # + or direct edit
        in_calendar = self.request.get("in_calendar")
        # in_app tells us if we are working from within the calendar app.
        # contains the relative path to app without leading / if set.
        in_app = self.request.get("app")
        if in_app:
            # Just redirect to the calendar app view, injection will pick
            # what it needs from it

            url = "%s/@@app-calendar" % in_app
        else:
            # somewhere else, expectedly on a workspace
            if workspace:
                if in_calendar:
                    # Stay in the calendar
                    # Go to the workspace calendar
                    container = self.request.get("container")
                    if container and container != "/".join(workspace.getPhysicalPath()):
                        url = "%s/@@workspace-calendar?all_calendars=1"

                    else:
                        url = "%s/@@workspace-calendar"
                    url = url % workspace.absolute_url()
                else:
                    # Load the event and show its form
                    # we return the original object url
                    url = "%s/event_view?show_sidebar=1" % url
            else:
                # if not render the app view
                pass

        return self.request.response.redirect(url)

    def round_date(self, dt):
        """Round the datetime minutes and seconds to the next quarter,
        i.e. '2000/01/01 00:35:21' becomes '2000/01/01 00:30:00'
        """
        remainder = float(dt) % 900
        if not remainder:
            return dt
        return dt + (900 - float(dt) % 900) / 86400

    @property
    @view.memoize
    def default_datetime(self):
        """Return the default date (the requested one or now)

        The request may come from several sources, e.g.:

        1. from the sidebar
        2. from the calendar month view
        3. from the calendar day and week views

        Each of this cases may have a date parameter in different formats.
        We try to convert it to a DateTime.
        """
        requested_date = self.request.get("date", "")
        # The requeste_date, when called by the calendar day and week view,
        # will look like '2017-03-29T13:30:00'
        if "T" in requested_date:
            # Strip the "T" to have the local timezone
            requested_date = requested_date.replace("T", " ")
        else:
            # 'T' in not there, assume we have only the date in the request
            requested_date += " 09:00"
        try:
            requested_date = " ".join((requested_date, DateTime().localZone()))
            date = DateTime(requested_date)
        except SyntaxError:
            date = DateTime()
        return date

    @property
    @view.memoize
    def default_start(self):
        """The rounded default_datetime."""
        return self.round_date(self.default_datetime)

    @property
    @view.memoize
    def default_end(self):
        """Like default_start, but add a time interval (in hours)

        We will have a 'T' in the request parameter
        if the request comes from the day or the week calendar view
        """
        delta = "T" in self.request.get("date", "") and 0.5 or 1.0
        return self.default_start + delta / 24

    def get_selection_classes(self, field, default=None):
        """identify all groups in the invitees"""
        return get_selection_classes(self.context, field, default)


class EditEvent(EventView, AddEvent):
    """Base class for editing events"""

    template = ViewPageTemplateFile("templates/panel-edit-event.pt")
    title = _("Edit event")
    is_add_form = False

    def __call__(self):
        """Render form, or handle POST and redirect"""
        if self.should_update():
            if not self.can_edit:
                raise Unauthorized("You cannot modify this event")
            self.update()
            return self.redirect(self.context.absolute_url())
        return self.template()


class EditCalendar(AddEvent):
    """ """

    template = ViewPageTemplateFile("templates/panel-edit-calendar.pt")
    title = _("Edit calendar")

    def __call__(self):
        """Render form, or handle POST and redirect"""
        if "form.buttons.edit" in self.request:
            modified, errors = self.update(self.context)
            if not errors:
                api.portal.show_message(
                    _("Your changes have been saved."),
                    request=self.request,
                    type="success",
                )
                self.context.reindexObject()
                notify(ObjectModifiedEvent(self.context))
            else:
                api.portal.show_message(
                    _("There was a problem: ${errors}.", mapping={"errors": errors}),
                    request=self.request,
                    type="error",
                )
            return self.redirect(self.request.get("app") or self.context.absolute_url())
        return self.template()
