import * as fs from 'fs';
import path from 'path';

import { DevidedPagePath } from '@growi/core';
// eslint-disable-next-line no-restricted-imports
import axios from 'axios';
import {
  Request, Response, NextFunction,
} from 'express';
import { param, validationResult, ValidationError } from 'express-validator';

import loggerFactory from '~/utils/logger';
import { projectRoot } from '~/utils/project-dir-utils';

import { convertStreamToBuffer } from '../util/stream';

const logger = loggerFactory('growi:routes:ogp');

const DEFAULT_USER_IMAGE_URL = '/images/icons/user.svg';
const DEFAULT_USER_IMAGE_PATH = `public${DEFAULT_USER_IMAGE_URL}`;

let bufferedDefaultUserImageCache: Buffer = Buffer.from('');
fs.readFile(path.join(projectRoot, DEFAULT_USER_IMAGE_PATH), (err, buffer) => {
  if (err) throw err;
  bufferedDefaultUserImageCache = buffer;
});


module.exports = function(crowi) {

  const isUserImageAttachment = (userImageUrlCached: string): boolean => {
    return /^\/attachment\/.+/.test(userImageUrlCached);
  };

  const getBufferedUserImage = async(userImageUrlCached: string): Promise<Buffer> => {

    let bufferedUserImage: Buffer;

    if (isUserImageAttachment(userImageUrlCached)) {
      const { fileUploadService } = crowi;
      const Attachment = crowi.model('Attachment');
      const attachment = await Attachment.findById(userImageUrlCached);
      const fileStream = await fileUploadService.findDeliveryFile(attachment);
      bufferedUserImage = await convertStreamToBuffer(fileStream);
      return bufferedUserImage;
    }

    return (await axios.get(
      userImageUrlCached, {
        responseType: 'arraybuffer',
      },
    )).data;

  };

  const renderOgp = async(req: Request, res: Response) => {

    const { configManager } = crowi;
    const ogpUri = configManager.getConfig('crowi', 'app:ogpUri');
    const page = req.body.page;

    let user;
    let pageTitle: string;
    let bufferedUserImage: Buffer;

    try {
      const User = crowi.model('User');
      user = await User.findById(page.creator._id.toString());

      bufferedUserImage = user.imageUrlCached === DEFAULT_USER_IMAGE_URL ? bufferedDefaultUserImageCache : (await getBufferedUserImage(user.imageUrlCached));
      // todo: consider page title
      pageTitle = (new DevidedPagePath(page.path)).latter;
    }
    catch (err) {
      logger.error(err);
      return res.status(500).send(`error: ${err}`);
    }

    let result;
    try {
      result = await axios.post(
        ogpUri, {
          data: {
            title: pageTitle,
            userName: user.username,
            userImage: bufferedUserImage,
          },
        }, {
          responseType: 'stream',
        },
      );
    }
    catch (err) {
      logger.error(err);
      return res.status(500).send(`error: ${err}`);
    }

    res.writeHead(200, {
      'Content-Type': 'image/jpeg',
    });
    result.data.pipe(res);

  };

  const pageIdRequired = param('pageId').not().isEmpty().withMessage('page id is not included in the parameter');

  const ogpValidator = async(req:Request, res:Response, next:NextFunction) => {
    const { aclService, fileUploadService, configManager } = crowi;

    const ogpUri = configManager.getConfig('crowi', 'app:ogpUri');

    if (ogpUri == null) return res.status(400).send('OGP URI for GROWI has not been setup');
    if (!fileUploadService.getIsUploadable()) return res.status(501).send('This GROWI can not upload file');
    if (!aclService.isGuestAllowedToRead()) return res.status(501).send('This GROWI is not public');

    const errors = validationResult(req);

    if (errors.isEmpty()) {

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

        if (page == null || page.status !== Page.STATUS_PUBLISHED || (page.grant !== Page.GRANT_PUBLIC && page.grant !== Page.GRANT_RESTRICTED)) {
          return res.status(400).send('the page does not exist');
        }

        req.body.page = page;
      }
      catch (error) {
        logger.error(error);
        return res.status(500).send(`error: ${error}`);
      }

      return next();
    }

    // errors.array length is one bacause pageIdRequired is used
    const pageIdRequiredError: ValidationError = errors.array()[0];

    return res.status(400).send(pageIdRequiredError.msg);
  };

  return {
    renderOgp,
    pageIdRequired,
    ogpValidator,
  };

};
