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

'use strict';

const express = require('express');

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

class ChatController extends SiteController {

  constructor (dtp) {
    super(dtp, module.exports);
  }

  async start ( ) {
    const {
      chat: chatService,
      limiter: limiterService,
      session: sessionService,
    } = this.dtp.services;

    const upload = this.createMulter();

    const router = express.Router();
    this.dtp.app.use('/chat', router);

    router.use(
      sessionService.authCheckMiddleware({ requireLogin: true }),
      chatService.middleware({ maxOwnedRooms: 25, maxJoinedRooms: 50 }),
      async (req, res, next) => {
        res.locals.currentView = 'chat';
        return next();
      },
    );

    router.param('roomId', this.populateRoomId.bind(this));
    router.param('inviteId', this.populateInviteId.bind(this));

    router.post(
      '/room/:roomId/invite/:inviteId/action',
      limiterService.createMiddleware(limiterService.config.chat.postRoomInviteAction),
      upload.none(),
      this.postRoomInviteAction.bind(this),
    );

    router.post(
      '/room/:roomId/invite',
      limiterService.createMiddleware(limiterService.config.chat.postRoomInvite),
      upload.none(),
      this.postRoomInvite.bind(this),
    );

    router.post(
      '/room/:roomId',
      limiterService.createMiddleware(limiterService.config.chat.postRoomUpdate),
      this.postRoomUpdate.bind(this),
    );

    router.post(
      '/room',
      limiterService.createMiddleware(limiterService.config.chat.postRoomCreate),
      this.postRoomCreate.bind(this),
    );

    router.get(
      '/room/create',
      this.getRoomEditor.bind(this),
    );

    router.get(
      '/room/:roomId/form/:formName',
      limiterService.createMiddleware(limiterService.config.chat.getRoomForm),
      this.getRoomForm.bind(this),
    );

    router.get(
      '/room/:roomId/invite/:inviteId',
      limiterService.createMiddleware(limiterService.config.chat.getRoomInviteView),
      this.getRoomInviteView.bind(this),
    );

    router.get(
      '/room/:roomId/invite',
      limiterService.createMiddleware(limiterService.config.chat.getRoomInviteView),
      this.getRoomInviteHome.bind(this),
    );

    router.get(
      '/room/:roomId/settings',
      limiterService.createMiddleware(limiterService.config.chat.getRoomSettings),
      this.getRoomSettings.bind(this),
    );

    router.get(
      '/room/:roomId',
      limiterService.createMiddleware(limiterService.config.chat.getRoomView),
      this.getRoomView.bind(this),
    );

    router.get(
      '/room',
      limiterService.createMiddleware(limiterService.config.chat.getRoomHome),
      this.getRoomHome.bind(this),
    );

    router.get(
      '/',
      limiterService.createMiddleware(limiterService.config.chat.getHome),
      this.getHome.bind(this),
    );

    /*
     * DELETE operations
     */

    router.delete(
      '/room/:roomId/invite/:inviteId',
      limiterService.createMiddleware(limiterService.config.chat.deleteInvite),
      this.deleteInvite.bind(this),
    );

    router.delete(
      '/room/:roomId',
      limiterService.createMiddleware(limiterService.config.chat.deleteRoom),
      this.deleteInvite.bind(this),
    );

    return router;
  }

  async populateRoomId (req, res, next, roomId) {
    const { chat: chatService } = this.dtp.services;
    try {
      res.locals.room = await chatService.getRoomById(roomId);
      if (!res.locals.room) {
        throw new SiteError(404, 'Room not found');
      }
      return next();
    } catch (error) {
      this.log.error('failed to populate roomId', { roomId, error });
      return next(error);
    }
  }

  async populateInviteId (req, res, next, inviteId) {
    const { chat: chatService } = this.dtp.services;
    try {
      res.locals.invite = await chatService.getRoomInviteById(inviteId);
      if (!res.locals.invite) {
        throw new SiteError(404, 'Invite not found');
      }
      return next();
    } catch (error) {
      this.log.error('failed to populate inviteId', { inviteId, error });
      return next(error);
    }
  }

  async postRoomInviteAction (req, res) {
    const { chat: chatService } = this.dtp.services;
    try {
      const { response } = req.body;
      const displayList = this.createDisplayList('room-invite-action');
      this.log.debug('room invite action', { message: req.body });
      switch (response) {
        case 'accept':
          await chatService.acceptRoomInvite(res.locals.invite);
          displayList.navigateTo(`/chat/room/${res.locals.invite.room._id}`);
          break;

        case 'reject':
          await chatService.rejectRoomInvite(res.locals.invite);
          displayList.showNotification(
            `Chat room invite rejected`,
            'success',
            'top-center',
            5000,
          );
          break;

        default:
          throw new SiteError(400, 'Must specify invite action');
      }

      res.status(200).json({ success: true, displayList });
    } catch (error) {
      this.log.error('failed to execute room invite action', {
        inviteId: res.locals.invite._id,
        response: req.body.response,
        error,
      });
      return res.status(error.statusCode || 500).json({
        success: false,
        message: error.message,
      });
    }
  }

  async postRoomInvite (req, res) {
    const { chat: chatService, user: userService } = this.dtp.services;
    this.log.debug('room invite received', { invite: req.body });
    if (!req.body.username || !req.body.username.length) {
      return res.status(400).json({ success: false, message: 'Please provide a username' });
    }
    try {
      req.body.username = req.body.username.trim().toLowerCase();
      while (req.body.username[0] === '@') {
        req.body.username = req.body.username.slice(1);
      }

      if (!req.body.username || !req.body.username.length) {
        throw new SiteError(400, 'Please provide a username');
      }

      const member = await userService.getPublicProfile(req.body.username);
      if (!member) {
        throw new SiteError(404, `There is no account with username @${req.body.username}`);
      }
      if (member._id.equals(res.locals.room.owner._id)) {
        throw new SiteError(400, "You can't invite yourself.");
      }

      await chatService.sendRoomInvite(res.locals.room, member, req.body);

      const displayList = this.createDisplayList('invite create');
      displayList.showNotification(
        `Chat room invite sent to ${member.displayName || member.username}!`,
        'success',
        'top-left',
        5000,
      );
      res.status(200).json({ success: true, displayList });
    } catch (error) {
      this.log.error('failed to create room invitation', { error });
      return res.status(error.statusCode || 500).json({
        success: false,
        message: error.message,
      });
    }
  }

  async postRoomUpdate (req, res, next) {
    const { chat: chatService } = this.dtp.services;
    try {
      res.locals.room = await chatService.updateRoom(res.locals.room, req.body);
      res.redirect(`/chat/room/${res.locals.room._id}`);
    } catch (error) {
      this.log.error('failed to update chat room', {
        // roomId: res.locals.room._id,
        error,
      });
      return next(error);
    }
  }

  async postRoomCreate (req, res, next) {
    const { chat: chatService } = this.dtp.services;
    try {
      res.locals.room = await chatService.createRoom(req.user, req.body);
      res.redirect(`/chat/room/${res.locals.room._id}`);
    } catch (error) {
      this.log.error('failed to create chat room', { error });
      return next(error);
    }
  }

  async getRoomEditor (req, res) {
    res.render('chat/room/editor');
  }

  async getRoomForm (req, res, next) {
    const validFormNames = [
      'invite-member',
    ];
    const formName = req.params.formName;
    if (validFormNames.indexOf(formName) === -1) {
      return next(new SiteError(404, 'Form not found'));
    }
    try {
      res.render(`chat/room/form/${formName}`);
    } catch (error) {
      this.log.error('failed to render form', { formName, error });
      return next(error);
    }
  }

  async getRoomInviteView (req, res) {
    res.render('chat/room/invite/view');
  }

  async getRoomInviteHome (req, res, next) {
    const { chat: chatService } = this.dtp.services;
    try {
      res.locals.invites = {
        new: await chatService.getRoomInvites(res.locals.room, 'new'),
        accepted: await chatService.getRoomInvites(res.locals.room, 'accepted'),
        rejected: await chatService.getRoomInvites(res.locals.room, 'rejected'),
      };
      res.render('chat/room/invite');
    } catch (error) {
      this.log.error('failed to render the room invites view', { error });
      return next(error);
    }
  }

  async getRoomSettings (req, res) {
    res.render('chat/room/editor');
  }

  async getRoomView (req, res, next) {
    const { chat: chatService } = this.dtp.services;
    try {
      res.locals.pageTitle = res.locals.room.name;

      const pagination = { skip: 0, cpp: 20 };
      res.locals.chatMessages = await chatService.getChannelHistory(res.locals.room, pagination);

      res.render('chat/room/view');
    } catch (error) {
      this.log.error('failed to render chat room view', { roomId: req.params.roomId, error });
      return next(error);
    }
  }

  async getRoomHome (req, res, next) {
    const { chat: chatService } = this.dtp.services;
    try {
      res.locals.pagination = this.getPaginationParameters(req, 20);
      res.locals.publicRooms = await chatService.getPublicRooms(req.user, res.locals.pagination);
      res.render('chat/room/index');
    } catch (error) {
      this.log.error('failed to render room home', { error });
      return next(error);
    }
  }

  async getHome (req, res, next) {
    const { chat: chatService } = this.dtp.services;
    try {
      res.locals.pageTitle = 'Chat Home';

      res.locals.pagination = this.getPaginationParameters(req, 20);

      const roomIds = [ ];
      res.locals.ownedChatRooms.forEach((room) => roomIds.push(room._id));
      res.locals.joinedChatRooms.forEach((room) => roomIds.push(room._id));
      res.locals.timeline = await chatService.getMultiRoomTimeline(roomIds, res.locals.pagination);

      res.render('chat/index');
    } catch (error) {
      this.log.error('failed to render chat home', { error });
      return next(error);
    }
  }

  async deleteInvite (req, res, next) {
    const { chat: chatService } = this.dtp.services;
    try {
      if (res.locals.room.owner._id.equals(req.user._id)) {
        throw new SiteError(403, 'This is not your invitiation');
      }

      await chatService.deleteRoomInvite(res.locals.invite);

      const displayList = this.createDisplayList('delete chat invite');
      displayList.removeElement(`li[data-invite-id="${res.locals.invite._id}"]`);
      displayList.showNotification(
        `Invitation to ${res.locals.invite.member.displayName || res.locals.invite.member.username} deleted successfully`,
        'success',
        'top-left',
        5000,
      );

      res.status(200).json({ success: true, displayList });
    } catch (error) {
      this.log.error('failed to delete chat room invite', { error });
      return next(error);
    }
  }

  async deleteRoom (req, res, next) {
    const { chat: chatService } = this.dtp.services;
    try {
      if (res.locals.room.owner._id.equals(req.user._id)) {
        throw new SiteError(403, 'This is not your chat room');
      }

      await chatService.deleteRoom(res.locals.room);

      const displayList = this.createDisplayList('delete chat invite');
      displayList.navigateTo('/chat');

      res.status(200).json({ success: true, displayList });
    } catch (error) {
      this.log.error('failed to delete chat room invite', { error });
      return next(error);
    }
  }
}

module.exports = {
  slug: 'chat',
  name: 'chat',
  create: async (dtp) => { return new ChatController(dtp); },
};