from datetime import datetime
from datetime import timezone
from DateTime import DateTime
from plone import api
from plone.event.utils import pydt
from ploneintranet.microblog.interfaces import IMicroblogTool
from Products.CMFPlone.interfaces import IPloneSiteRoot
from zope.component import queryUtility

import logging
import pytz

logger = logging.getLogger(__name__)


def get(status_id):
    """Get a status update by id.

    :param status_id: The id of the status update
    :type status_id: int|str
    :returns: The matching StatusUpdate
    :rtype: StatusUpdate
    """
    if isinstance(status_id, str):
        status_id = int(status_id)
    microblog = queryUtility(IMicroblogTool)
    return microblog.get(status_id)


def unrestricted_get(status_id):
    """Get a status update by id, bypassing security checks.

    :param status_id: The id of the status update
    :type status_id: int|str
    :returns: The matching StatusUpdate
    :rtype: StatusUpdate
    """
    if isinstance(status_id, str):
        status_id = int(status_id)
    microblog = queryUtility(IMicroblogTool)
    return microblog._get(status_id)


def create(
    text="",
    microblog_context=None,
    thread_id=None,
    mention_ids=None,
    tags=None,
    user=None,
    userid=None,
    time=None,
    content_context=None,
    action_verb=None,
):
    """Create a status update (post).

    :param text: text of the post
    :type text: Unicode object

    :param microblog_context: Container of the post
    :type microblog_context: Content object

    :param user: User who should post. By default the current user posts.
    :type user: user object

    :param userid: userid of the user who should post.
    :type userid: string

    :param time: time when the post should happen. By default the current time.
    :type time: timezone aware datetime object

    :param content_context: a content referenced we are talking about
    :type content_context: content object

    :param action_verb: indicate event source (posted, created, published)
    :type action_verb: string

    :returns: Newly created statusupdate
    :rtype: StatusUpdate object

    Note that you can add attachments to statusupdates by calling
    .add_attachment(filename, data)
    on the returned StatusUpdate.
    """
    if IPloneSiteRoot.providedBy(microblog_context):
        microblog_context = None
    if IPloneSiteRoot.providedBy(content_context):
        content_context = None

    # Avoid circulat dependencies
    from ploneintranet.microblog.statusupdate import StatusUpdate

    status_obj = StatusUpdate(
        text=text,
        microblog_context=microblog_context,
        thread_id=thread_id,
        mention_ids=mention_ids,
        tags=tags,
        content_context=content_context,
        action_verb=action_verb,
    )
    # By default the post is done by the current user
    # Passing a userid or user allows to post as a different user
    if user is None and userid is not None:
        user = api.user.get(userid=userid)
    if user is not None:
        status_obj.userid = user.getId()
        status_obj.creator = user.getId()

    # By default the post happens now
    # Passing a time (as a datetime-object) the id and the date can be set
    if time is not None:
        assert isinstance(time, datetime)
        if not time.tzinfo or time.tzinfo.utcoffset(time) is None:
            raise ValueError("Naive datetime not supported")
        UTC = pytz.timezone("UTC")
        epoch = UTC.localize(datetime.utcfromtimestamp(0))
        delta = time - epoch
        status_obj.id = int(delta.total_seconds() * 1e6)
        status_obj.date = DateTime(time)

    microblog = queryUtility(IMicroblogTool)
    microblog.add(status_obj)
    # delay logging until after the statusupdate is added
    # because storing it may change the id
    if time is not None and pydt(time) > datetime.now(timezone.utc):
        logger.info(
            "%r created a future post <%r> at %r",
            status_obj.creator,
            status_obj.id,
            time.strftime("%Y-%m-%d %H:%M:%S"),
        )
    # take care - statusupdate may still be queued for storage
    # and not actually written into the container yet
    # this may change the status_obj.id
    return status_obj


def get_for(content_context, action_verb=None):
    """Get status updates for a content object.

    :param content_context: The content object
    :type content_context: Content object

    :param action_verb: The action verb, e.g. "published"
    :type action_verb: string

    :returns: List of status updates
    :rtype: list of StatusUpdate objects

    We return a list, not an iterable, so consumers can
    call len() on the result.
    """
    microblog = queryUtility(IMicroblogTool)
    if not action_verb:
        return list(microblog.content_values(content_context))
    return [
        status_update
        for status_update in microblog.content_values(content_context)
        if status_update.action_verb == action_verb
    ]


def delete_future_for(content_context, action_verb=None):
    """Delete future status updates for a content object.

    :param content_context: The content object
    :type content_context: Content object

    :param action_verb: The action verb, e.g. "published"
    :type action_verb: string
    """
    microblog = queryUtility(IMicroblogTool)
    for statusupdate in get_for(content_context, action_verb):
        if pydt(statusupdate.date) > datetime.now(timezone.utc):
            microblog.delete(statusupdate.id, restricted=False)
            logger.info(
                "Deleted future %r post for %r",
                action_verb,
                content_context,
            )
