import { pagePathUtils } from '@growi/core';
import loggerFactory from '~/utils/logger';


const logger = loggerFactory('growi:routes:apiv3:page'); // eslint-disable-line no-unused-vars

const express = require('express');
const { body, query } = require('express-validator');

const router = express.Router();
const { convertToNewAffiliationPath } = pagePathUtils;
const ErrorV3 = require('../../models/vo/error-apiv3');


/**
 * @swagger
 *  tags:
 *    name: Page
 */

/**
 * @swagger
 *
 *  components:
 *    schemas:
 *      Page:
 *        description: Page
 *        type: object
 *        properties:
 *          _id:
 *            type: string
 *            description: page ID
 *            example: 5e07345972560e001761fa63
 *          __v:
 *            type: number
 *            description: DB record version
 *            example: 0
 *          commentCount:
 *            type: number
 *            description: count of comments
 *            example: 3
 *          createdAt:
 *            type: string
 *            description: date created at
 *            example: 2010-01-01T00:00:00.000Z
 *          creator:
 *            $ref: '#/components/schemas/User'
 *          extended:
 *            type: object
 *            description: extend data
 *            example: {}
 *          grant:
 *            type: number
 *            description: grant
 *            example: 1
 *          grantedUsers:
 *            type: array
 *            description: granted users
 *            items:
 *              type: string
 *              description: user ID
 *            example: ["5ae5fccfc5577b0004dbd8ab"]
 *          lastUpdateUser:
 *            $ref: '#/components/schemas/User'
 *          liker:
 *            type: array
 *            description: granted users
 *            items:
 *              type: string
 *              description: user ID
 *            example: []
 *          path:
 *            type: string
 *            description: page path
 *            example: /
 *          redirectTo:
 *            type: string
 *            description: redirect path
 *            example: ""
 *          revision:
 *            type: string
 *            description: page revision
 *          seenUsers:
 *            type: array
 *            description: granted users
 *            items:
 *              type: string
 *              description: user ID
 *            example: ["5ae5fccfc5577b0004dbd8ab"]
 *          status:
 *            type: string
 *            description: status
 *            enum:
 *              - 'wip'
 *              - 'published'
 *              - 'deleted'
 *              - 'deprecated'
 *            example: published
 *          updatedAt:
 *            type: string
 *            description: date updated at
 *            example: 2010-01-01T00:00:00.000Z
 *
 *      LikeParams:
 *        description: LikeParams
 *        type: object
 *        properties:
 *          pageId:
 *            type: string
 *            description: page ID
 *            example: 5e07345972560e001761fa63
 *          bool:
 *            type: boolean
 *            description: boolean for like status
 *
 *      LikeInfo:
 *        description: LikeInfo
 *        type: object
 *        properties:
 *          sumOfLikers:
 *            type: number
 *            description: how many people liked the page
 *          isLiked:
 *            type: boolean
 *            description: Whether the request user liked (will be returned if the user is included in the request)
 */
module.exports = (crowi) => {
  const accessTokenParser = require('../../middlewares/access-token-parser')(crowi);
  const loginRequired = require('../../middlewares/login-required')(crowi, true);
  const loginRequiredStrictly = require('../../middlewares/login-required')(crowi);
  const csrf = require('../../middlewares/csrf')(crowi);
  const apiV3FormValidator = require('../../middlewares/apiv3-form-validator')(crowi);

  const globalNotificationService = crowi.getGlobalNotificationService();
  const socketIoService = crowi.socketIoService;
  const { Page, GlobalNotificationSetting } = crowi.models;
  const { exportService } = crowi;

  const validator = {
    likes: [
      body('pageId').isString(),
      body('bool').isBoolean(),
    ],
    likeInfo: [
      query('_id').isMongoId(),
    ],
    export: [
      query('format').isString().isIn(['md', 'pdf']),
      query('revisionId').isString(),
    ],
    archive: [
      body('rootPagePath').isString(),
      body('isCommentDownload').isBoolean(),
      body('isAttachmentFileDownload').isBoolean(),
      body('isSubordinatedPageDownload').isBoolean(),
      body('fileType').isString().isIn(['pdf', 'markdown']),
      body('hierarchyType').isString().isIn(['allSubordinatedPage', 'decideHierarchy']),
      body('hierarchyValue').isNumeric(),
    ],
    exist: [
      query('fromPath').isString(),
      query('toPath').isString(),
    ],
  };

  /**
   * @swagger
   *
   *    /page/likes:
   *      put:
   *        tags: [Page]
   *        summary: /page/likes
   *        description: Update liked status
   *        operationId: updateLikedStatus
   *        requestBody:
   *          content:
   *            application/json:
   *              schema:
   *                $ref: '#/components/schemas/LikeParams'
   *        responses:
   *          200:
   *            description: Succeeded to update liked status.
   *            content:
   *              application/json:
   *                schema:
   *                  $ref: '#/components/schemas/Page'
   */
  router.put('/likes', accessTokenParser, loginRequiredStrictly, csrf, validator.likes, apiV3FormValidator, async(req, res) => {
    const { pageId, bool: isLiked } = req.body;

    let page;
    try {
      page = await Page.findByIdAndViewer(pageId, req.user);
      if (page == null) {
        return res.apiv3Err(`Page '${pageId}' is not found or forbidden`);
      }

      if (isLiked) {
        page = await page.like(req.user);
      }
      else {
        page = await page.unlike(req.user);
      }
    }
    catch (err) {
      logger.error('update-like-failed', err);
      return res.apiv3Err(err, 500);
    }

    const result = { page };
    result.seenUser = page.seenUsers;
    res.apiv3({ result });

    if (isLiked) {
      try {
        // global notification
        await globalNotificationService.fire(GlobalNotificationSetting.EVENT.PAGE_LIKE, page, req.user);
      }
      catch (err) {
        logger.error('Like notification failed', err);
      }
    }
  });

  /**
   * @swagger
   *
   *    /page/like-info:
   *      get:
   *        tags: [Page]
   *        summary: /page/like-info
   *        description: Get like info
   *        operationId: getLikeInfo
   *        parameters:
   *          - name: _id
   *            in: query
   *            description: page id
   *            schema:
   *              type: string
   *        responses:
   *          200:
   *            description: Succeeded to get bookmark info.
   *            content:
   *              application/json:
   *                schema:
   *                  $ref: '#/components/schemas/LikeInfo'
   */
  router.get('/like-info', loginRequired, validator.likeInfo, apiV3FormValidator, async(req, res) => {
    const pageId = req.query._id;

    const responsesParams = {};

    try {
      const page = await Page.findById(pageId);
      responsesParams.sumOfLikers = page.liker.length;

      // guest user return nothing
      if (!req.user) {
        return res.apiv3(responsesParams);
      }

      responsesParams.isLiked = page.liker.includes(req.user._id);
      return res.apiv3(responsesParams);
    }
    catch (err) {
      logger.error('get-like-count-failed', err);
      return res.apiv3Err(err, 500);
    }
  });

  /**
  * @swagger
  *
  *    /pages/export:
  *      get:
  *        tags: [Export]
  *        description: return page's markdown
  *        responses:
  *          200:
  *            description: Return page's markdown
  */
  router.get('/export/:pageId', loginRequiredStrictly, validator.export, async(req, res) => {
    const { pageId } = req.params;
    const { format, revisionId = null } = req.query;
    let revision;

    try {
      const Page = crowi.model('Page');
      const page = await Page.findByIdAndViewer(pageId, req.user);

      if (page == null) {
        const isPageExist = await Page.count({ _id: pageId }) > 0;
        if (isPageExist) {
          // This page exists but req.user has not read permission
          return res.apiv3Err(new ErrorV3(`Haven't the right to see the page ${pageId}.`), 403);
        }
        return res.apiv3Err(new ErrorV3(`Page ${pageId} is not exist.`), 404);
      }

      const revisionIdForFind = revisionId || page.revision;

      const Revision = crowi.model('Revision');
      revision = await Revision.findById(revisionIdForFind);
    }
    catch (err) {
      logger.error('Failed to get page data', err);
      return res.apiv3Err(err, 500);
    }

    const fileName = revision.id;
    let stream;

    try {
      stream = exportService.getReadStreamFromRevision(revision, format);
    }
    catch (err) {
      logger.error('Failed to create readStream', err);
      return res.apiv3Err(err, 500);
    }

    res.set({
      'Content-Disposition': `attachment;filename*=UTF-8''${fileName}.${format}`,
    });

    return stream.pipe(res);
  });

  /**
   * @swagger
   *
   *    /page/exist-paths:
   *      get:
   *        tags: [Page]
   *        summary: /page/exist-paths
   *        description: Get already exist paths
   *        operationId: getAlreadyExistPaths
   *        parameters:
   *          - name: fromPath
   *            in: query
   *            description: old parent path
   *            schema:
   *              type: string
   *          - name: toPath
   *            in: query
   *            description: new parent path
   *            schema:
   *              type: string
   *        responses:
   *          200:
   *            description: Succeeded to retrieve pages.
   *            content:
   *              application/json:
   *                schema:
   *                  properties:
   *                    existPaths:
   *                      type: object
   *                      description: Paths are already exist in DB
   *          500:
   *            description: Internal server error.
   */
  router.get('/exist-paths', loginRequired, validator.exist, apiV3FormValidator, async(req, res) => {
    const { fromPath, toPath } = req.query;

    try {
      const fromPage = await Page.findByPath(fromPath);
      const fromPageDescendants = await Page.findManageableListWithDescendants(fromPage, req.user);

      const toPathDescendantsArray = fromPageDescendants.map((subordinatedPage) => {
        return convertToNewAffiliationPath(fromPath, toPath, subordinatedPage.path);
      });

      const existPages = await Page.findListByPathsArray(toPathDescendantsArray);
      const existPaths = existPages.map(page => page.path);

      return res.apiv3({ existPaths });

    }
    catch (err) {
      logger.error('Failed to get exist path', err);
      return res.apiv3Err(err, 500);
    }

  });

  // TODO GW-2746 bulk export pages
  // /**
  //  * @swagger
  //  *
  //  *    /page/archive:
  //  *      post:
  //  *        tags: [Page]
  //  *        summary: /page/archive
  //  *        description: create page archive
  //  *        requestBody:
  //  *          content:
  //  *            application/json:
  //  *              schema:
  //  *                properties:
  //  *                  rootPagePath:
  //  *                    type: string
  //  *                    description: path of the root page
  //  *                  isCommentDownload:
  //  *                    type: boolean
  //  *                    description: whether archive data contains comments
  //  *                  isAttachmentFileDownload:
  //  *                    type: boolean
  //  *                    description: whether archive data contains attachments
  //  *                  isSubordinatedPageDownload:
  //  *                    type: boolean
  //  *                    description: whether archive data children pages
  //  *                  fileType:
  //  *                    type: string
  //  *                    description: file type of archive data(.md, .pdf)
  //  *                  hierarchyType:
  //  *                    type: string
  //  *                    description: method of select children pages archive data contains('allSubordinatedPage', 'decideHierarchy')
  //  *                  hierarchyValue:
  //  *                    type: number
  //  *                    description: depth of hierarchy(use when hierarchyType is 'decideHierarchy')
  //  *        responses:
  //  *          200:
  //  *            description: create page archive
  //  *            content:
  //  *              application/json:
  //  *                schema:
  //  *                  $ref: '#/components/schemas/Page'
  //  */
  // router.post('/archive', accessTokenParser, loginRequired, csrf, validator.archive, apiV3FormValidator, async(req, res) => {
  //   const PageArchive = crowi.model('PageArchive');

  //   const {
  //     rootPagePath,
  //     isCommentDownload,
  //     isAttachmentFileDownload,
  //     fileType,
  //   } = req.body;
  //   const owner = req.user._id;

  //   const numOfPages = 1; // TODO 最終的にzipファイルに取り込むページ数を入れる

  //   const createdPageArchive = PageArchive.create({
  //     owner,
  //     fileType,
  //     rootPagePath,
  //     numOfPages,
  //     hasComment: isCommentDownload,
  //     hasAttachment: isAttachmentFileDownload,
  //   });

  //   console.log(createdPageArchive);
  //   return res.apiv3({ });

  // });

  // router.get('/count-children-pages', accessTokenParser, loginRequired, async(req, res) => {

  //   // TO DO implement correct number at another task

  //   const { pageId } = req.query;
  //   console.log(pageId);

  //   const dummy = 6;
  //   return res.apiv3({ dummy });
  // });

  return router;
};
