from datetime import datetime

from Acquisition import aq_inner, aq_parent

from Products.Five.browser import BrowserView

from Products.CMFCore.utils import getToolByName

from Products.CMFCore.interfaces._content import IDiscussionResponse

import transaction

from plone.app.discussion.comment import CommentFactory

from plone.app.discussion.interfaces import IConversation, IReplies, IComment

from types import TupleType
from DateTime import DateTime


def DT2dt(DT):
    """Convert a Zope DateTime (with timezone) into a Python datetime (GMT)."""
    DT = DT.toZone('GMT')
    return datetime(
        DT.year(),
        DT.month(),
        DT.day(),
        DT.hour(),
        DT.minute(),
        int(DT.second()))


class View(BrowserView):
    """Migration View
    """

    def __call__(self, filter_callback=None):

        context = aq_inner(self.context)
        out = []
        self.total_comments_migrated = 0
        self.total_comments_deleted = 0

        dry_run = "dry_run" in self.request

        # This is for testing only.
        # Do not use transactions during a test.
        test = "test" in self.request

        if not test:
            transaction.begin()  # pragma: no cover

        catalog = getToolByName(context, 'portal_catalog')

        def log(msg):
            # encode string before sending it to external world
            if isinstance(msg, unicode):
                msg = msg.encode('utf-8')  # pragma: no cover
            context.plone_log(msg)
            out.append(msg)

        def migrate_replies(context, in_reply_to, replies,
                            depth=0, just_delete=0):
            # Recursive function to migrate all direct replies
            # of a comment. Returns True if there are no replies to
            # this comment left, and therefore the comment can be removed.
            if len(replies) == 0:
                return True

            workflow = context.portal_workflow
            oldchain = workflow.getChainForPortalType('Discussion Item')
            new_workflow = workflow.comment_review_workflow
            mt = getToolByName(self.context, 'portal_membership')

            if type(oldchain) == TupleType and len(oldchain) > 0:
                oldchain = oldchain[0]

            for reply in replies:
                # log
                indent = "  "
                for i in range(depth):
                    indent += "  "
                log("%smigrate_reply: '%s'." % (indent, reply.title))

                should_migrate = True
                if filter_callback and not filter_callback(reply):
                    should_migrate = False
                if just_delete:
                    should_migrate = False

                new_in_reply_to = None
                if should_migrate:
                    # create a reply object
                    comment = CommentFactory()
                    comment.title = reply.Title()
                    comment.text = reply.cooked_text
                    comment.mime_type = 'text/html'
                    comment.creator = reply.Creator()

                    try:
                        comment.author_username = reply.author_username
                    except AttributeError:
                        comment.author_username = reply.Creator()

                    member = mt.getMemberById(comment.author_username)
                    if member:
                        comment.author_name = member.fullname

                    if not comment.author_name:
                        # In migrated site member.fullname = ''
                        # while member.getProperty('fullname') has the
                        # correct value
                        if member:
                            comment.author_name = member.getProperty(
                                'fullname'
                            )
                        else:
                            comment.author_name = comment.author_username

                    try:
                        comment.author_email = reply.email
                    except AttributeError:
                        comment.author_email = None

                    comment.creation_date = DT2dt(reply.creation_date)
                    comment.modification_date = DT2dt(reply.modification_date)

                    comment.reply_to = in_reply_to

                    if in_reply_to == 0:
                        # Direct reply to a content object
                        new_in_reply_to = conversation.addComment(comment)
                    else:
                        # Reply to another comment
                        comment_to_reply_to = conversation.get(in_reply_to)
                        replies = IReplies(comment_to_reply_to)
                        new_in_reply_to = replies.addComment(comment)

                    # migrate the review state
                    old_status = workflow.getStatusOf(oldchain, reply)
                    new_status = {
                        'action': None,
                        'actor': None,
                        'comment': 'Migrated workflow state',
                        'review_state': old_status and old_status.get(
                            'review_state',
                            new_workflow.initial_state
                        ) or 'published',
                        'time': DateTime()
                    }
                    workflow.setStatusOf('comment_review_workflow',
                                         comment,
                                         new_status)

                    auto_transition = new_workflow._findAutomaticTransition(
                        comment,
                        new_workflow._getWorkflowStateOf(comment))
                    if auto_transition is not None:
                        new_workflow._changeStateOf(comment, auto_transition)
                    else:
                        new_workflow.updateRoleMappingsFor(comment)
                    comment.reindexObject(idxs=['allowedRolesAndUsers',
                                                'review_state'])

                self.total_comments_migrated += 1

                # migrate all talkbacks of the reply
                talkback = getattr(reply, 'talkback', None)
                no_replies_left = migrate_replies(
                    context,
                    new_in_reply_to,
                    talkback.getReplies(),
                    depth=depth + 1,
                    just_delete=not should_migrate)

                if no_replies_left:
                    # remove reply and talkback
                    talkback.deleteReply(reply.id)
                    obj = aq_parent(talkback)
                    obj.talkback = None
                    log("%sremove %s" % (indent, reply.id))
                    self.total_comments_deleted += 1

            # Return True when all comments on a certain level have been
            # migrated.
            return True

        # Find content
        brains = catalog.searchResults(
            object_provides='Products.CMFCore.interfaces._content.IContentish')
        log("Found %s content objects." % len(brains))

        count_discussion_items = len(
            catalog.searchResults(Type='Discussion Item')
        )
        count_comments_pad = len(
            catalog.searchResults(object_provides=IComment.__identifier__)
        )
        count_comments_old = len(
            catalog.searchResults(
                object_provides=IDiscussionResponse.__identifier__
            )
        )

        log("Found %s Discussion Item objects." % count_discussion_items)
        log("Found %s old discussion items." % count_comments_old)
        log("Found %s plone.app.discussion comments." % count_comments_pad)

        log("\n")
        log("Start comment migration.")

        # This loop is necessary to get all contentish objects, but not
        # the Discussion Items. This wouldn't be necessary if the
        # zcatalog would support NOT expressions.
        new_brains = []
        for brain in brains:
            if brain.portal_type != 'Discussion Item':
                new_brains.append(brain)

        # Recursively run through the comment tree and migrate all comments.
        for brain in new_brains:
            obj = brain.getObject()
            talkback = getattr(obj, 'talkback', None)
            if talkback:
                replies = talkback.getReplies()
                if replies:
                    conversation = IConversation(obj)
                log("\n")
                log("Migrate '%s' (%s)" % (obj.Title(),
                                           obj.absolute_url(relative=1)))
                migrate_replies(context, 0, replies)
                obj = aq_parent(talkback)
                obj.talkback = None

        if self.total_comments_deleted != self.total_comments_migrated:
            log(
                "Something went wrong during migration. The number of " +
                "migrated comments (%s) differs from the number of deleted " +
                "comments (%s)." % (
                    self.total_comments_migrated,
                    self.total_comments_deleted
                )
            )
            if not test:  # pragma: no cover
                transaction.abort()  # pragma: no cover
            log("Abort transaction")  # pragma: no cover

        log("\n")
        log("Comment migration finished.")
        log("\n")

        log("%s of %s comments migrated."
            % (self.total_comments_migrated, count_comments_old))

        if self.total_comments_migrated != count_comments_old:
            log(
                "%s comments could not be migrated." % (
                    count_comments_old - self.total_comments_migrated
                )
            )  # pragma: no cover
            log("Please make sure your " +
                "portal catalog is up-to-date.")  # pragma: no cover

        if dry_run and not test:
            transaction.abort()  # pragma: no cover
            log("Dry run")  # pragma: no cover
            log("Abort transaction")  # pragma: no cover
        if not test:
            transaction.commit()  # pragma: no cover
        return '\n'.join(out)
