from AccessControl import Unauthorized
from plone import api
from plone.event.utils import pydt
from plone.uuid.interfaces import IUUID
from ploneintranet import api as pi_api
from ploneintranet.microblog.browser.interfaces import IPloneIntranetMicroblogLayer
from zope.globalrequest import getRequest

import datetime
import logging

logger = logging.getLogger(__name__)


def content_created(obj, event):
    """Add a status update relating to content creation"""
    if not IPloneIntranetMicroblogLayer.providedBy(obj.REQUEST):
        # We are not installed
        return

    event_key = "ploneintranet.microblog.content_created"
    if not getRequest().get(event_key, True):  # default is enabled
        logger.debug("%s disabled", event_key)
        return

    whitelist = api.portal.get_registry_record(
        "ploneintranet.microblog.whitelisted_types",
        default=("Document", "File", "Image", "News Item", "Event"),
    )
    try:
        if obj.portal_type not in whitelist:
            return
    except AttributeError:
        return

    # microblog_context is automatically derived from content_context
    # which is why this listens to IObjectAddedEvent not IObjectCreatedEvent
    try:
        pi_api.microblog.statusupdate.create(
            content_context=obj, action_verb="created", tags=obj.Subject() or None
        )
    except Unauthorized:
        # on content tree copy the IObjectAdded event is fired before the
        # child content obj is properly uuid indexed
        logger.warning("No statusupdate created for %s", obj.absolute_url())


def newsitem_modified(obj, event):
    if not IPloneIntranetMicroblogLayer.providedBy(obj.REQUEST):
        # We are not installed
        return

    event_key = "ploneintranet.microblog.newsitem_modified"
    if not getRequest().get(event_key, True):  # default is enabled
        logger.debug("%r disabled", event_key)
        return

    # Only reschedule future "published" announcements
    if api.content.get_state(obj) != "published":
        return

    # Delete and re-issue posts for future news item publications
    # if the future effective date is changed, but only if this
    # publication has not already been announced.
    # I.e. max 1 newsitem_published statusupdate per news item.
    for description in event.descriptions:
        if "effective" in description.attributes:
            # This SHOULD indicate that the effective date has changed.
            # However, dexterity_update always includes `effective` in the
            # modified fields.

            # Cancel all future announcements (should be max 1 only)
            pi_api.microblog.statusupdate.delete_future_for(obj, "published")
            # If there's a past publication announcement, bail out
            if len(pi_api.microblog.statusupdate.get_for(obj, "published")) > 0:
                return

            effective_utc = pydt(obj.effective())
            pi_api.microblog.statusupdate.create(
                content_context=obj,
                action_verb="published",
                tags=obj.Subject() or None,
                time=effective_utc,
            )
            return


def content_statechanged(obj, event):
    """Add a status update relating to state change events"""
    if not IPloneIntranetMicroblogLayer.providedBy(obj.REQUEST):
        # We are not installed
        return

    event_key = "ploneintranet.microblog.content_statechanged"
    if not getRequest().get(event_key, True):  # default is enabled
        logger.debug("%s disabled", event_key)
        return

    whitelist = api.portal.get_registry_record(
        "ploneintranet.microblog.whitelisted_types",
        default=("Document", "File", "Image", "News Item", "Event"),
    )
    try:
        if obj.portal_type not in whitelist:
            return
    except AttributeError:
        return

    # Remove any existing future publication announcements. If the current state
    # transition is "published", we will re-issue a new one below, if that has
    # not already been announced in the past.
    pi_api.microblog.statusupdate.delete_future_for(obj, "published")

    action_verb = event.new_state.id
    if action_verb != "published":
        return
    # Don't announce the same transition twice
    if len(pi_api.microblog.statusupdate.get_for(obj, action_verb)) > 0:
        return

    effective_utc = pydt(obj.effective())
    if effective_utc is not None and effective_utc > datetime.datetime.now(
        datetime.UTC
    ):
        publication_time = effective_utc
    else:
        publication_time = None  # Fall back to StatusUpdate.__init__ default: now

    # microblog_context is automatically derived from content_context
    pi_api.microblog.statusupdate.create(
        content_context=obj,
        action_verb=action_verb,
        tags=obj.Subject() or None,
        time=publication_time,
    )


def content_removed(obj, event):
    """
    Archive all statusupdates referencing a deleted content
    object as microblog_context or content_context.
    """
    if not IPloneIntranetMicroblogLayer.providedBy(obj.REQUEST):
        # We are not installed
        return

    tool = api.portal.get_tool("ploneintranet_microblog")
    keys = set(tool.content_keys(obj))
    keys.update(tool.context_keys(obj))

    if not keys:
        return

    # obj can be already detached from parent. reconstruct url
    logger.info(
        "Archiving statusupdates referencing uuid {} -> {}/{}".format(
            IUUID(obj), event.oldParent.absolute_url(), obj.id
        )
    )
    for key in keys:
        tool.delete(key, restricted=False)
