from plone import api
from ploneintranet import api as pi_api
from ploneintranet.core import _
from ploneintranet.notifications.adapters.category_provider import BaseCategory
from ploneintranet.notifications.adapters.category_provider import BaseCategoryProvider
from ploneintranet.notifications.sql import saconfig


class BaseCalendarCategory(BaseCategory):
    _app_id = "calendar"

    @property
    def calendar_title(self) -> str:
        try:
            return self.logged_event.data["calendar_title"]
        except KeyError:
            # retain backward compatibility
            calendar = self.context.shared_calendar
            if not calendar:
                return ""
            return calendar.to_object.Title()


class EventsIAmIvolvedInCategory(BaseCalendarCategory):
    """Handles the notifications of submissions for calendar maintainers"""

    name = "events"
    default_channels = {"desktop", "email"}
    title = _("label_events_i_am_involved_in", default=("Events I am involved in"))
    description = _(
        "message_events_i_am_involved_in",
        default=(
            "You will be notified when a central calendar maintainer "
            "accepts or rejects an event you submitted to a "
            "calendar, or when events are submitted to a "
            "calendar you are maintaining."
        ),
    )

    @property
    def recipient_ids(self):
        if (
            not self.context.shared_calendar
            or not self.context.shared_calendar.to_object
        ):
            return set()
        return {
            maintainer.getId()
            for maintainer in self.context.shared_calendar.to_object.get_maintainers()
            if maintainer.getId() != self.logged_event.actor_id
        }

    @property
    def auto_accepting(self):
        # This check heroically assumes, that neither the calendar.auto_accept
        # nor the permissions of the actor have changed, between the creation
        # of the logged event and the rendering of the notification message.
        # Properly handling those complications is out of scope for now.
        # The helper view needs to be run as the actor, to inspect her permissions.
        with api.env.adopt_user(self.logged_event.actor_id):
            return api.content.get_view("shared-calendar-auto-accepts", self.context)()

    @property
    def subject(self):
        # StaleNotificationError suppresses the notification in the web view,
        # if the notification was superseded by a later event modification.
        if not self.context.shared_calendar:
            # declined/retracted
            raise saconfig.StaleNotificationError()
        if self.calendar_title != self.context.shared_calendar.to_object.Title():
            # re-submitted to another calendar
            raise saconfig.StaleNotificationError()
        if self.auto_accepting:
            return _(
                "message_event_added_to_your_calendar",
                default="${actor} added an event to a calendar you are "
                "maintaining: ${calendar}.",
                mapping={
                    "actor": self.logged_event.actor_fullname,
                    "calendar": self.calendar_title,
                },
            )
        if api.content.get_state(self.context) != "pending":
            # accepted by moderator
            raise saconfig.StaleNotificationError()
        return _(
            "message_event_requested_to_your_calendar",
            default="${actor} requested adding an event to a calendar "
            "you are maintaining: ${calendar}.",
            mapping={
                "actor": self.logged_event.actor_fullname,
                "calendar": self.calendar_title,
            },
        )

    @property
    def accept_decline_url(self):
        return "{}/apps/calendar/@@app-calendar?shared-event-uid={}".format(
            api.portal.get().absolute_url(), api.content.get_uuid(obj=self.context)
        )

    def get_message_for(self, notification, with_text=True, highlight=False):
        """Return the message for the notification"""
        if not with_text or notification.channel != "email":
            return self.subject

        if self.auto_accepting:
            action = _("You may view the event by following the link below.")
            url = "{}/apps/calendar/@@app-calendar?current_event_url=/{}".format(
                api.portal.get().absolute_url(),
                self.context.absolute_url(relative=True),
            )
        else:
            action = _(
                "calendar_event_accept_or_decline_link",
                "Please accept or decline this request via the link below.",
            )
            url = self.accept_decline_url

        subject = pi_api.userprofile.translate_message_for_user(
            self.subject, notification.recipient_id
        )
        action = pi_api.userprofile.translate_message_for_user(
            action, notification.recipient_id
        )
        message = (
            f"""<p>{subject}</p>\n"""
            f"""<p>{action}</p>\n"""
            f"""<p><a href="{url}">{url}</a></p>"""
        )
        return message

    def get_absolute_url_for(self, notification):
        return self.accept_decline_url


class CalendarCategoryProvider(BaseCategoryProvider):
    """Provide the appropriate event notification categories,
    depending on the calendar the event is associated."""

    category_factories = [EventsIAmIvolvedInCategory]


class EventAcceptedInCalendarCategory(BaseCalendarCategory):
    """Handles the notifications for events shared to a calendar"""

    name = "event_accepted"
    default_channels = {"desktop", "email"}

    @property
    def recipient_ids(self):
        for action in reversed(self.context.workflow_history.get("event_workflow", [])):
            if action.get("review_state") == "pending":
                return {action["actor"]}

    def get_message_for(self, notification, with_text=True, highlight=False):
        """Return the message for the notification"""
        # StaleNotificationError suppresses the notification in the web view,
        # if the notification was superseded by a later event modification.
        if api.content.get_state(self.context) != "central_calendar":
            # retracted/declined
            raise saconfig.StaleNotificationError()
        if not self.context.shared_calendar:
            # should not be hit; defensive coding
            raise saconfig.StaleNotificationError()
        if self.calendar_title != self.context.shared_calendar.to_object.Title():
            # published into another calendar
            raise saconfig.StaleNotificationError()

        return _(
            "message_your_event_added_to_calendar",
            default="${actor} added your event '${object}' to a calendar: "
            "${calendar}.",
            mapping={
                "actor": self.logged_event.actor_fullname,
                "object": self.maybe_process_title(self.context.Title(), highlight),
                "calendar": self.calendar_title,
            },
        )


class EventAcceptedCategoryProvider(BaseCategoryProvider):
    """Provide the appropriate event notification categories,
    depending on the calendar the event is associated."""

    category_factories = [EventAcceptedInCalendarCategory]


class EventDeclinedCategory(EventAcceptedInCalendarCategory):
    """Handles the notifications for events shared to a calendar"""

    name = "event_declined"
    default_channels = {"desktop", "email"}

    def get_message_for(self, notification, with_text=True, highlight=False):
        """Return the message for the notification"""
        # StaleNotificationError suppresses the notification in the web view,
        # if the notification was superseded by a later event modification.
        if api.content.get_state(self.context) != "published":
            # resubmitted elsewhere
            raise saconfig.StaleNotificationError()

        return _(
            "message_your_event_calendar_declined",
            default="${actor} declined your request to add your event "
            "'${object}' to a calendar: ${calendar}.",
            mapping={
                "actor": self.logged_event.actor_fullname,
                "object": self.context.Title(),
                "calendar": self.logged_event.data.get("calendar_title"),
            },
        )


class EventDeclinedCategoryProvider(BaseCategoryProvider):
    """Provide the appropriate event notification categories,
    depending on the calendar the event is associated."""

    category_factories = [EventDeclinedCategory]
