from AccessControl import Unauthorized
from hashlib import sha256
from plone import api
from plone.app.layout.navigation.interfaces import INavigationRoot
from plone.base.utils import safe_bytes
from plone.memoize.instance import memoizedproperty
from plone.memoize.view import memoize
from plone.memoize.view import memoize_contextless
from plone.protect.interfaces import IDisableCSRFProtection
from plone.uuid.interfaces import IUUID
from ploneintranet import api as pi_api
from ploneintranet.calendar.config import TZ_COOKIE_NAME
from ploneintranet.calendar.content import ICentralCalendar
from ploneintranet.core import _
from ploneintranet.layout.browser.base import BasePanel
from ploneintranet.notifications.sql import saconfig
from ploneintranet.workspace.interfaces import IBaseWorkspaceFolder
from Products.CMFCore.WorkflowCore import WorkflowException
from Products.Five import BrowserView
from urllib.parse import urlencode
from zExceptions import NotFound
from zope.interface import alsoProvides

import hmac
import logging

log = logging.getLogger(__name__)


class SetTimezoneView(BrowserView):
    def __call__(self):
        self.set_timezone_cookie(self.request.get("timezone"))
        return

    def set_timezone_cookie(self, tz):
        if tz:
            cookie_path = "/" + api.portal.get().absolute_url(1)
            self.request.response.setCookie(TZ_COOKIE_NAME, tz, path=cookie_path)


class CalendarMoreMenu(BrowserView):
    @property
    @memoize
    def target(self):
        """The target for the calendar export view"""
        for obj in self.context.aq_chain:
            if (
                IBaseWorkspaceFolder.providedBy(obj)
                or ICentralCalendar.providedBy(obj)
                or INavigationRoot.providedBy(obj)
            ):
                return obj

    @memoize_contextless
    def get_user(self):
        """Get the requested user"""
        user = pi_api.userprofile.get_current()
        if not user:
            raise Unauthorized("Only valid users can access this menu")
        return user

    @memoize
    def get_key(self):
        """Get the key for the current user and context

        Currently it is generated with the UIDs of the user and the target,
        but we may change this adding, e.g., a secret stored in the registry
        """
        user = self.get_user()
        if not user:
            raise Unauthorized("Access denied to anonymous")
        return safe_bytes(IUUID(user, "") + IUUID(self.target, ""))

    @memoize
    def get_token(self):
        """Return the token for the given userid in this context"""
        key = self.get_key()
        user = self.get_user()
        message = safe_bytes(user.id)
        return hmac.new(key, message, sha256).hexdigest()

    def webcal_url(self, flavour=None):
        """Return the webcal url"""
        target_url = self.target.absolute_url()
        prot = "webcal"
        if flavour == "outlook":
            if target_url.startswith("https"):
                prot = "webcals"
        schemaless_absolute_url = target_url.partition("://")[-1]
        params = {"uid": self.get_user().id, "token": self.get_token()}
        url = "{prot}://{schemaless_absolute_url}/ics_export?{params}".format(
            prot=prot,
            schemaless_absolute_url=schemaless_absolute_url,
            params=urlencode(params),
        )
        if flavour == "google":
            return "https://www.google.com/calendar/render?{params}".format(
                params=urlencode({"cid": url})
            )
        return url

    def can_edit(self):
        return api.user.has_permission("Modify portal content", obj=self.context)

    def can_delete(self):
        return api.user.has_permission("Delete objects", obj=self.context)


class CalendarAppMoreMenu(CalendarMoreMenu):
    """View class to provide a calendar context menu for the whole calendar
    app that has just a slightly different template.
    """


class CentralCalendarView(BrowserView):
    def __call__(self):
        app = api.portal.get()["apps"]["calendar"]
        app_view = api.content.get_view(context=app, name="view", request=self.request)
        return app_view()


class RedirectToCalendarAppView(BrowserView):
    def __call__(self):
        return self.request.response.redirect(
            api.portal.get().absolute_url() + "/apps/calendar"
        )


class IcsExport(CalendarMoreMenu):
    """We need an authorization token to allow the user to
    retrieve the ics_view output
    """

    @memoize_contextless
    def get_user(self, userid=None):
        """Override the base class method to work for anonymous users"""
        if userid is None:
            userid = self.request.get("uid")
        portal = api.portal.get()
        profiles = portal.get("profiles", {})
        user = profiles.get(userid, None)
        return user

    def validate(self):
        """Check that the token passed in the request is equal
        to the one generate in our secret way
        """
        token = self.request.get("token")
        if token != self.get_token():
            raise Unauthorized("Access denied to anonymous")

    @property
    def ics_view(self):
        """the ics_view for the target"""
        return api.content.get_view("ics_view", self.target, self.request)

    def __call__(self):
        """If called directly check a token and return the ics_view
        for the requested user
        """
        self.validate()
        user = self.get_user()
        with api.env.adopt_user(user=user):
            return self.ics_view()


class PanelSharedEvent(BasePanel):
    """ """

    title = _("Event sharing request")
    panel_size = "medium"
    _form_data_pat_inject_parts = ("#document-body",)
    show_default_cancel_button = False
    # We use content_macros/event_fields, which requires can_edit
    # - Filename:   ... neintranet/workspace/browser/templates/content_macros.pt
    # - Location:   (line 269: col 36)
    # - Source:     read_only python:not view.can_edit;
    can_edit = False

    @property
    @memoize_contextless
    def event(self):
        """User may have lost access: returns None in that case"""
        event_uid = self.request.get("uid")
        if not event_uid:
            return None
        try:
            return api.content.get(UID=event_uid)  # may be None
        except (Unauthorized, NotFound):
            return None

    @memoizedproperty
    def event_title(self):
        return self.event.Title()

    @memoizedproperty
    def calendar_title(self):
        calendar = self.event.shared_calendar
        if not calendar:
            return None
        title = calendar.to_object.Title()
        return title

    @memoizedproperty
    def actor(self):
        workflow = api.portal.get_tool("portal_workflow")
        try:
            actor_id = workflow.getInfoFor(ob=self.event, name="actor")
        except WorkflowException:
            actor_id = self.event.getOwner().getId()
        return pi_api.userprofile.get(actor_id)

    @memoizedproperty
    def actor_title(self):
        return self.actor.Title()

    @memoizedproperty
    def actor_url(self):
        return self.actor.absolute_url()

    @memoizedproperty
    def request_pending(self):
        return self.event and api.content.get_state(self.event) == "pending"

    @memoizedproperty
    def can_review(self):
        return (
            self.request_pending
            and self.event
            and self.event.shared_calendar
            and api.user.has_permission(
                "Add portal content", obj=self.event.shared_calendar.to_object
            )
        )

    @memoizedproperty
    def recipients(self):
        wf = api.portal.get_tool("portal_workflow")
        try:
            actor = wf.getInfoFor(self.event, "actor")
        except WorkflowException:
            log.info(
                "Could not get actor info for {}".format(
                    "/".join(self.event.getPhysicalPath())
                )
            )
            return []

        userprofile = pi_api.userprofile.get(actor)
        if not userprofile:
            return []

        tool = api.portal.get_tool("ploneintranet_notifications")
        user_id = userprofile.getId()
        settings = tool._notification_settings.get(user_id, {}).get("events", None)
        if settings is not None and "email" not in settings:
            return []
        return [user_id]

    def _do_action(self, transition=None, message=None):
        api.content.transition(obj=self.event, transition=transition)
        api.portal.show_message(message=message, request=self.request, type="success")

    def do_accept(self):
        self._do_action(
            transition="accept",
            message="Request accepted",
        )
        saconfig.log_event(self.event, "event_accepted")
        return self.request.response.redirect(
            self.event.absolute_url() + "?hide_sidebar=1"
        )

    def do_decline(self):
        microblog_key = "ploneintranet.microblog.content_statechanged"
        self.request.form[microblog_key] = False
        self._do_action(
            transition="reject",
            message="Request declined",
        )
        # We need the shared_calendar to be set for the notification.
        # Only clear it afterwards.
        saconfig.log_event(
            self.event, "event_declined", {"calendar_title": self.calendar_title}
        )
        self.event.shared_calendar = None
        return self.request.response.redirect(
            api.portal.get().absolute_url() + "/apps/calendar"
        )

    def __call__(self):
        if self.request.method == "POST":
            if "accept" in self.request.form:
                return self.do_accept()
            elif "decline" in self.request.form:
                return self.do_decline()
            elif "close" in self.request.form:
                return self.request.response.redirect(
                    api.portal.get().absolute_url() + "/apps/calendar"
                )

        return self.index()


class ReindexObjectedSecurityForShared(BrowserView):
    """This view reindexes the object related events.

    This is meant to be called by an async task.
    """

    def __call__(self):
        alsoProvides(self.request, IDisableCSRFProtection)
        brains = api.content.find(shared_calendar=self.context.UID())
        for brain in brains:
            try:
                obj = brain.getObject()
            except (AttributeError, KeyError):
                log.warning("Could not get Event from brain: %r", brain.getPath())
                continue
            obj.reindexObjectSecurity()
        return "OK"
