// post.js
// Copyright (C) 2021 Digital Telepresence, LLC
// License: Apache-2.0

'use strict';

const striptags = require('striptags');
const slug = require('slug');

const { SiteService, SiteError } = require('../../lib/site-lib');

const mongoose = require('mongoose');
const ObjectId = mongoose.Types.ObjectId;

const Post = mongoose.model('Post');
const Comment = mongoose.model('Comment'); // jshint ignore:line

const moment = require('moment');

class PostService extends SiteService {

  constructor (dtp) {
    super(dtp, module.exports);
    this.populatePost = [
      {
        path: 'author',
        select: 'core coreUserId username username_lc displayName bio picture',
        populate: [
          {
            path: 'core',
            select: 'created updated flags meta',
            strictPopulate: false,
          },
        ],
      },
      {
        path: 'image',
      },
    ];
  }

  async createPlaceholder (author) {
    const NOW = new Date();

    if (!author.flags.isAdmin){
      if (!author.permissions.canAuthorPosts) {
        throw new SiteError(403, 'You are not permitted to author posts');
      }
    }

    let post = new Post();
    post.created = NOW;
    post.authorType = author.type;
    post.author = author._id;
    post.title = "New Draft Post";
    post.slug = `draft-post-${post._id}`;
    await post.save();

    post = post.toObject();
    post.author = author; // self-populate instead of calling db

    return post;
  }

  async create (author, postDefinition) {
    const NOW = new Date();

    if (!author.flags.isAdmin){
      if (!author.permissions.canAuthorPosts) {
        throw new SiteError(403, 'You are not permitted to author posts');
      }
      if ((postDefinition.status === 'published') && !author.permissions.canPublishPosts) {
        throw new SiteError(403, 'You are not permitted to publish posts');
      }
    }

    if (postDefinition.tags) {
      postDefinition.tags = postDefinition.tags.split(',').map((tag) => striptags(tag.trim()));
    } else {
      postDefinition.tags = [ ];
    }

    const post = new Post();
    post.created = NOW;
    post.authorType = author.type;
    post.author = author._id;
    post.title = striptags(postDefinition.title.trim());
    post.slug = this.createPostSlug(post._id, post.title);
    post.summary = striptags(postDefinition.summary.trim());
    post.content = postDefinition.content.trim();
    post.tags = postDefinition.tags;
    post.status = postDefinition.status || 'draft';
    post.flags = {
      enableComments: postDefinition.enableComments === 'on',
      isFeatured: postDefinition.isFeatured === 'on',
    };

    await post.save();

    return post.toObject();
  }

  async update (user, post, postDefinition) {
    const { coreNode: coreNodeService } = this.dtp.services;

    if (!user.flags.isAdmin){
      if (!user.permissions.canAuthorPosts) {
        throw new SiteError(403, 'You are not permitted to author posts');
      }
    }
    const NOW = new Date();
    const updateOp = {
      $setOnInsert: {
        created: NOW,
      },
      $set: {
        updated: NOW,
      },
    };

    if (postDefinition.title) {
      updateOp.$set.title = striptags(postDefinition.title.trim());
      updateOp.$set.slug = this.createPostSlug(post._id, updateOp.$set.title);
    }
    if (postDefinition.slug) {
      let postSlug = striptags(slug(postDefinition.slug.trim())).split('-');
      while (ObjectId.isValid(postSlug[postSlug.length - 1])) {
        postSlug.pop();
      }
      postSlug = postSlug.splice(0, 4);
      postSlug.push(post._id.toString());
      updateOp.$set.slug = `${postSlug.join('-')}`;
    }
    if (postDefinition.summary) {
      updateOp.$set.summary = striptags(postDefinition.summary.trim());
    }
    if (postDefinition.content) {
      updateOp.$set.content = postDefinition.content.trim();
    }
 
    await this.updateTags(post._id, postDefinition.tags);

    if (!postDefinition.status) {
      throw new SiteError(406, 'Must include post status');
    }

    // const postWillBeUnpublished = post.status === 'published' && postDefinition.status !== 'published';
    const postWillBePublished = post.status !== 'published' && postDefinition.status === 'published';

    if (postWillBePublished) {
      if (!user.permissions.canPublishPosts && !user.flags.isAdmin) {
        throw new SiteError(403, 'You are not permitted to publish posts');
      }
    }
    updateOp.$set.status = striptags(postDefinition.status.trim());

    updateOp.$set['flags.enableComments'] = postDefinition.enableComments === 'on';
    updateOp.$set['flags.isFeatured'] = postDefinition.isFeatured === 'on';

    const OLD_STATUS = post.status;
    const postAuthor = post.author;

    post = await Post.findOneAndUpdate(
      { _id: post._id },
      updateOp,
      { upsert: true, new: true },
    );

    const CORE_SCHEME = coreNodeService.getCoreRequestScheme();
    const { site } = this.dtp.config;

    if ((OLD_STATUS === 'draft') && (updateOp.$set.status === 'published')) {
      const event = {
        action: 'post-create',
        emitter: postAuthor,
        label: updateOp.$set.title,
        content: updateOp.$set.summary,
        href: `${CORE_SCHEME}://${site.domain}/post/${updateOp.$set.slug}`,
      };
      if (post.image) {
        event.thumbnail = `${CORE_SCHEME}://${site.domain}/image/${post.image}`;
      }
      await coreNodeService.sendKaleidoscopeEvent(event);
    }
  }

  // pass the post._id and its tags to function
  async updateTags (id, tags) {
    if (tags) {
      tags = tags.split(',').map((tag) => striptags(tag.trim().toLowerCase()));
    } else {
      tags = [ ];
    }

    const NOW = new Date();
    const updateOp = {
      $setOnInsert: {
        created: NOW,
      },
      $set: {
        updated: NOW,
      },
    };
    updateOp.$set.tags = tags;
    await Post.findOneAndUpdate(
      { _id: id },
      updateOp,
    );
  }

  async getByTags (tag, pagination, status = ['published']) {
    if (!Array.isArray(status)) {
      status = [status];
    }
    const posts = await Post.find( { status: { $in: status }, tags: tag } )
    .sort({ created: -1 })
    .skip(pagination.skip)
    .limit(pagination.cpp)
    .populate(this.populatePost);
    return posts;
  }

  async updateImage (user, post, file) {
    const { image: imageService } = this.dtp.services;

    if (!user.permissions.canAuthorPosts) {
      throw new SiteError(403, 'You are not permitted to change posts');
    }

    const images = [
      {
        width: 960,
        height: 540,
        format: 'jpeg',
        formatParameters: {
          quality: 80,
        },
      },
    ];
    await imageService.processImageFile(user, file, images);
    await Post.updateOne(
      { _id: post._id },
      {
        $set: {
          image: images[0].image._id,
        },
      },
    );
  }

  async getPosts (pagination, status = ['published'], count = false) {
    if (!Array.isArray(status)) {
      status = [status];
    }
  
    var search = { 
      status: { $in: status },
      'flags.isFeatured': false
    };
    if ( count ) {
      search = { 
        status: { $in: status },
      };
    }
    const posts = await Post
      .find(search)
      .sort({ created: -1 })
      .skip(pagination.skip)
      .limit(pagination.cpp)
      .populate(this.populatePost)
      .lean();
    posts.forEach((post) => {
      post.author.type = post.authorType;
    });
    if (count) {
      const totalPostCount = await Post
      .countDocuments(search);
      return { posts, totalPostCount };
    }
    return posts;
  }

  async getFeaturedPosts (maxCount = 3) {
    const posts = await Post
      .find({ status: 'published', 'flags.isFeatured': true })
      .sort({ created: -1 })
      .limit(maxCount)
      .lean();
    return posts;
  }

  async getAllPosts (pagination) {
    const posts = await Post
      .find()
      .sort({ created: -1 })
      .skip(pagination.skip)
      .limit(pagination.cpp)
      .populate(this.populatePost)
      .lean();
    return posts;
  }

  async getForAuthor (author, status, pagination) {
    if (!Array.isArray(status)) {
      status = [status];
    }

    pagination = Object.assign({ skip: 0, cpp: 5 }, pagination);

    const search = {
      authorType: author.type,
      author: author._id,
      status: { $in: status },
    };

    const posts = await Post
      .find(search)
      .sort({ created: -1 })
      .skip(pagination.skip)
      .limit(pagination.cpp)
      .populate(this.populatePost)
      .lean();

    posts.forEach((post) => {
      post.author.type = post.authorType;
    });

    const totalPostCount = await Post.countDocuments(search);

    return { posts, totalPostCount };
  }

  async getCommentsForAuthor (author, pagination) {
    const { comment: commentService } = this.dtp.services;

    const NOW = new Date();
    const START_DATE = moment(NOW).subtract(3, 'days').toDate();

    const published = await this.getForAuthor(author, 'published', 5);
    if (!published || (published.length === 0)) {
      return [ ];
    }

    const postIds = published.posts.map((post) => post._id);
    const search = { // index: 'comment_replies'
      created: { $gt: START_DATE },
      status: 'published',
      resource: { $in: postIds },
    };

    let q = Comment
      .find(search)
      .sort({ created: -1 });

    if (pagination) {
      q = q.skip(pagination.skip).limit(pagination.cpp);
    } else {
      q = q.limit(20);
    }

    const comments = await q
      .populate(commentService.populateCommentWithResource)
      .lean();
    const totalCommentCount = await Comment.countDocuments(search);
    return { comments, totalCommentCount };
  }

  async getById (postId) {
    const post = await Post
      .findById(postId)
      .select('+content')
      .populate(this.populatePost)
      .lean();

    post.author.type = post.authorType;

    return post;
  }

  async getBySlug (postSlug) {
    const slugParts = postSlug.split('-');
    const postId = slugParts[slugParts.length - 1];
    return this.getById(postId);
  }

  async deletePost (post) {
    const {
      comment: commentService,
      contentReport: contentReportService,
    } = this.dtp.services;

    await commentService.deleteForResource(post);
    await contentReportService.removeForResource(post);

    this.log.info('deleting post', { postId: post._id });
    await Post.deleteOne({ _id: post._id });
  }

  createPostSlug (postId, postTitle) {
    if ((typeof postTitle !== 'string') || (postTitle.length < 1)) {
      throw new Error('Invalid input for making a post slug');
    }
    const postSlug = slug(postTitle.trim().toLowerCase()).split('-').slice(0, 4).join('-');
    return `${postSlug}-${postId}`;
  }
}

module.exports = {
  slug: 'post',
  name: 'post',
  create: (dtp) => { return new PostService(dtp); },
};
