#       Licensed to the Apache Software Foundation (ASF) under one
#       or more contributor license agreements.  See the NOTICE file
#       distributed with this work for additional information
#       regarding copyright ownership.  The ASF licenses this file
#       to you under the Apache License, Version 2.0 (the
#       "License"); you may not use this file except in compliance
#       with the License.  You may obtain a copy of the License at
#
#         http://www.apache.org/licenses/LICENSE-2.0
#
#       Unless required by applicable law or agreed to in writing,
#       software distributed under the License is distributed on an
#       "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
#       KIND, either express or implied.  See the License for the
#       specific language governing permissions and limitations
#       under the License.

import logging
import re

import pymongo
from allura.lib.search import mapped_artifacts_from_index_ids

from tg import expose, validate, redirect
from tg import tmpl_context as c, app_globals as g
from webob import exc
from formencode import validators

from allura.lib import helpers as h
from allura.lib import utils
from allura import model as M
from allura.lib.security import has_access, require_access
from allura.lib.decorators import require_post
from allura.controllers import DiscussionController, ThreadController, PostController, ModerationController
from allura.controllers import discuss as controllers_discuss
from allura.lib.widgets import discuss as DW
from allura.lib.widgets.subscriptions import SubscribeForm

from forgediscussion import model as DM
from forgediscussion import widgets as FW
from forgediscussion import tasks

log = logging.getLogger(__name__)


class pass_validator:
    def validate(self, v, s=None):
        return v


pass_validator = pass_validator()


class ModelConfig(controllers_discuss.ModelConfig):
    Discussion = DM.Forum
    Thread = DM.ForumThread
    Post = DM.ForumPost
    Attachment = M.DiscussionAttachment


class WidgetConfig(controllers_discuss.WidgetConfig):
    # Forms
    subscription_form = DW.SubscriptionForm()
    subscribe_form = SubscribeForm()
    edit_post = DW.EditPost(show_subject=True)
    moderate_thread = FW.ModerateThread()
    post_filter = DW.PostFilter()
    moderate_posts = DW.ModeratePosts()
    # Other widgets
    discussion = FW.Forum()
    thread = FW.Thread()
    post = FW.Post()
    thread_header = FW.ThreadHeader()
    announcements_table = FW.AnnouncementsTable()
    discussion_header = FW.ForumHeader()


class ForumController(DiscussionController):
    M = ModelConfig
    W = WidgetConfig

    def _check_security(self):
        require_access(self.discussion, 'read')

    def __init__(self, forum_id):
        self.ThreadController = ForumThreadController
        self.PostController = ForumPostController
        self.moderate = ForumModerationController(self)
        self.discussion = DM.Forum.query.get(
            app_config_id=c.app.config._id,
            shortname=forum_id)
        if not self.discussion:
            raise exc.HTTPNotFound()
        super().__init__()

    @expose()
    def _lookup(self, id=None, *remainder):
        if id and self.discussion:
            return ForumController(self.discussion.shortname + '/' + id), remainder
        else:
            raise exc.HTTPNotFound()

    @expose('jinja:forgediscussion:templates/index.html')
    @validate(dict(page=validators.Int(if_empty=0, if_invalid=0),
                   limit=validators.Int(if_empty=None, if_invalid=None)))
    def index(self, threads=None, limit=None, page=0, count=0, **kw):
        if self.discussion.deleted:
            raise exc.HTTPNotFound()
        limit, page, start = g.handle_paging(limit, page)
        if not c.user.is_anonymous():
            c.subscribed = M.Mailbox.subscribed(artifact=self.discussion)
            c.tool_subscribed = M.Mailbox.subscribed()
        threads = DM.ForumThread.query.find(dict(discussion_id=self.discussion._id, num_replies={'$gt': 0})) \
                                      .sort([('flags', pymongo.DESCENDING), ('last_post_date', pymongo.DESCENDING)])
        c.discussion = self.W.discussion
        c.discussion_header = self.W.discussion_header
        c.whole_forum_subscription_form = self.W.subscribe_form
        return dict(
            discussion=self.discussion,
            count=threads.count(),
            threads=threads.skip(start).limit(int(limit)).all(),
            limit=limit,
            page=page)

    @expose('json:')
    @require_post()
    @validate(W.subscribe_form)
    def subscribe_to_forum(self, subscribe=None, unsubscribe=None, shortname=None, **kw):
        if subscribe:
            self.discussion.subscribe(type='direct')

            # unsubscribe from all individual threads that are part of this forum, so you don't have overlapping subscriptions
            forumthread_index_prefix = (DM.ForumThread.__module__ + '.' + DM.ForumThread.__name__).replace('.', '/') + '#'
            thread_mboxes = M.Mailbox.query.find(dict(
                user_id=c.user._id,
                project_id=c.project._id,
                app_config_id=c.app.config._id,
                artifact_index_id={'$regex': '^' + re.escape(forumthread_index_prefix)},
            )).all()
            # get the ForumThread objects from the subscriptions
            thread_index_ids = [mbox.artifact_index_id for mbox in thread_mboxes]
            threads_by_id = mapped_artifacts_from_index_ids(thread_index_ids, DM.ForumThread, objectid_id=False)
            for mbox in thread_mboxes:
                thread_id = mbox.artifact_index_id.split('#')[1]
                thread = threads_by_id[thread_id]
                # only delete if the ForumThread is part of this forum
                if thread.discussion_id == self.discussion._id:
                    mbox.delete()

        elif unsubscribe:
            self.discussion.unsubscribe()

        return {
            'status': 'ok',
            'subscribed': M.Mailbox.subscribed(artifact=self.discussion),
            'subscribed_to_tool': M.Mailbox.subscribed(),
        }


class ForumThreadController(ThreadController):
    W = WidgetConfig

    @expose('jinja:forgediscussion:templates/discussionforums/thread.html')
    @validate(dict(page=validators.Int(if_empty=0, if_invalid=0),
                   limit=validators.Int(if_empty=25, if_invalid=25)))
    def index(self, limit=25, page=0, count=0, **kw):
        if self.thread.discussion.deleted and not has_access(c.app, 'configure'):
            raise exc.HTTPNotFound()
        c.thread_subscription_form = self.W.subscribe_form
        return super().index(limit=limit, page=page, count=count, show_moderate=True, **kw)

    @h.vardec
    @expose()
    @require_post()
    @validate(pass_validator, index)
    def moderate(self, **kw):
        require_access(self.thread, 'moderate')
        if self.thread.discussion.deleted and not has_access(c.app, 'configure'):
            raise exc.HTTPNotFound()
        args = self.W.moderate_thread.validate(kw, None)
        tasks.calc_forum_stats.post(self.thread.discussion.shortname)
        if args.pop('delete', None):
            url = self.thread.discussion.url()
            self.thread.delete()
            redirect(url)
        forum = args.pop('discussion')
        if forum != self.thread.discussion:
            tasks.calc_forum_stats.post(forum.shortname)
            self.thread.set_forum(forum)
        self.thread.flags = args.pop('flags', [])
        self.thread.subject = args.pop('subject', self.thread.subject)
        redirect(self.thread.url())

    @expose('json:')
    @require_post()
    @validate(W.subscribe_form)
    def subscribe(self, subscribe=None, unsubscribe=None, **kw):
        if subscribe:
            self.thread.subscribe()
        elif unsubscribe:
            self.thread.unsubscribe()

        sub_tool = M.Mailbox.subscribed()
        sub_forum = M.Mailbox.subscribed(artifact=self.discussion)
        return {
            'status': 'ok',
            'subscribed': M.Mailbox.subscribed(artifact=self.thread),
            'subscribed_to_tool': sub_tool or sub_forum,
            'subscribed_to_entire_name': 'forum' if sub_forum else 'discussion tool',
        }


class ForumPostController(PostController):

    @h.vardec
    @expose('jinja:allura:templates/discussion/post.html')
    @validate(pass_validator)
    @utils.AntiSpam.validate('Spambot protection engaged')
    def index(self, **kw):
        if self.thread.discussion.deleted and not has_access(c.app, 'configure'):
            raise exc.HTTPNotFound()
        return super().index(**kw)

    @expose()
    @require_post()
    @validate(pass_validator, error_handler=index)
    def moderate(self, **kw):
        require_access(self.post.thread, 'moderate')
        if self.thread.discussion.deleted and not has_access(c.app, 'configure'):
            raise exc.HTTPNotFound()
        tasks.calc_thread_stats.post(self.post.thread._id)
        tasks.calc_forum_stats(self.post.discussion.shortname)
        super().moderate(**kw)


class ForumModerationController(ModerationController):
    PostModel = DM.ForumPost
