/*
 * Copyright (C) 2003-2019 eXo Platform SAS.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */
package org.exoplatform.social.service.rest;

import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.annotation.security.RolesAllowed;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriInfo;

import static org.exoplatform.social.service.rest.RestChecker.checkAuthenticatedUserPermission;
import org.exoplatform.commons.api.notification.NotificationContext;
import org.exoplatform.commons.api.notification.NotificationMessageUtils;
import org.exoplatform.commons.api.notification.channel.AbstractChannel;
import org.exoplatform.commons.api.notification.channel.template.AbstractTemplateBuilder;
import org.exoplatform.commons.api.notification.model.ChannelKey;
import org.exoplatform.commons.api.notification.model.MessageInfo;
import org.exoplatform.commons.api.notification.model.NotificationInfo;
import org.exoplatform.commons.api.notification.model.PluginKey;
import org.exoplatform.commons.api.notification.model.WebNotificationFilter;
import org.exoplatform.commons.api.notification.plugin.BaseNotificationPlugin;
import org.exoplatform.commons.api.notification.service.storage.WebNotificationStorage;
import org.exoplatform.web.security.csrf.ExoCSRFCheck;
import org.exoplatform.commons.notification.channel.WebChannel;
import org.exoplatform.commons.notification.impl.NotificationContextImpl;
import org.exoplatform.commons.notification.net.WebNotificationSender;
import org.exoplatform.services.log.ExoLogger;
import org.exoplatform.services.log.Log;
import org.exoplatform.services.rest.resource.ResourceContainer;
import org.exoplatform.social.core.identity.model.Identity;
import org.exoplatform.social.core.identity.provider.OrganizationIdentityProvider;
import org.exoplatform.social.core.manager.IdentityManager;
import org.exoplatform.social.core.manager.RelationshipManager;
import org.exoplatform.social.core.relationship.model.Relationship;
import org.exoplatform.social.core.space.model.Space;
import org.exoplatform.social.core.space.spi.SpaceService;
import org.exoplatform.social.core.storage.impl.AbstractStorage;

/**
 * Created by The eXo Platform SAS
 * Author : eXoPlatform
 *          exo@exoplatform.com
 * Nov 26, 2014  
 */
@Path("social/intranet-notification")
public class IntranetNotificationRestService extends AbstractStorage implements ResourceContainer {

  private static final Log LOG = ExoLogger.getLogger(IntranetNotificationRestService.class);

  private IdentityManager identityManager;
  private RelationshipManager relationshipManager;
  private SpaceService spaceService;
  private WebNotificationStorage webNotificationStorage;

  public final static String MESSAGE_JSON_FILE_NAME = "message.json";

  public IntranetNotificationRestService(IdentityManager identityManager, RelationshipManager relationshipManager,
                                         SpaceService spaceService, WebNotificationStorage webNotificationStorage) {
    this.identityManager = identityManager;
    this.relationshipManager = relationshipManager;
    this.spaceService = spaceService;
    this.webNotificationStorage = webNotificationStorage;
  }

  /**
   * Processes the "Accept the invitation to connect" action between 2 users and update notification.
   *
   * @param senderId The remote Id of the identity who sent the invitation.
   * @param receiverId The remote Id of the identity who received the invitation.
   * @notificationId Id of the web notification message
   * @authentication
   * @request
   * GET: {@code http://localhost:8080/rest/social/intranet-notifications/confirmInvitationToConnect/john/root/<notificationId>/message.json}
   * @throws Exception
   */
  @GET
  @RolesAllowed("users")
  @ExoCSRFCheck
  @Path("confirmInvitationToConnect/{senderId}/{receiverId}/{notificationId}/message.{format}")
  public Response confirmInvitationToConnect(@Context UriInfo uriInfo,
                                             @PathParam("senderId") String senderId,
                                             @PathParam("receiverId") String receiverId,
                                             @PathParam("notificationId") String notificationId,
                                             @PathParam("format") String format) throws Exception {
    //Check authenticated user
    checkAuthenticatedUserPermission(receiverId);

    Identity sender = identityManager.getOrCreateIdentity(OrganizationIdentityProvider.NAME, senderId, true);
    Identity receiver = identityManager.getOrCreateIdentity(OrganizationIdentityProvider.NAME, receiverId, true);
    if (sender == null || receiver == null) {
      throw new WebApplicationException(Response.Status.BAD_REQUEST);
    }

    Relationship invitation = relationshipManager.get(sender, receiver);
    if(invitation == null || !invitation.getStatus().equals(Relationship.Type.PENDING) || !invitation.isReceiver(receiver)) {
      throw new WebApplicationException(Response.Status.FORBIDDEN);
    }

    //
    String[] mediaTypes = new String[] { "json", "xml" };
    MediaType mediaType = Util.getMediaType(format, mediaTypes);
    //update notification
    NotificationInfo info = webNotificationStorage.get(notificationId);
    info.key(new PluginKey("RelationshipReceivedRequestPlugin"));
    info.setFrom(senderId);
    info.setTo(receiverId);
    Map<String, String> ownerParameter = new HashMap<>();
    ownerParameter.put("sender", senderId);
    ownerParameter.put("status", "accepted");
    info.setOwnerParameter(ownerParameter);
    MessageInfo messageInfo = sendBackNotif(info);
    if (messageInfo == null) {
      throw new WebApplicationException(Response.Status.INTERNAL_SERVER_ERROR);
    }

    relationshipManager.confirm(sender, receiver);

    return Util.getResponse(messageInfo, uriInfo, mediaType, Response.Status.OK);
  }
  /**
   * Processes the "Deny the invitation to connect" action between 2 users
   *
   * @param senderId The sender's remote Id.
   * @param receiverId The receiver's remote Id.
   * @authentication
   * @request
   * GET: localhost:8080/rest/social/intranet-notifications/ignoreInvitationToConnect/john/root
   * @throws Exception
   */
  @GET
  @RolesAllowed("users")
  @ExoCSRFCheck
  @Path("ignoreInvitationToConnect/{senderId}/{receiverId}/{notificationId}/message.{format}")
  public Response ignoreInvitationToConnect(@Context UriInfo uriInfo,
                                          @PathParam("senderId") String senderId,
                                          @PathParam("receiverId") String receiverId,
                                          @PathParam("notificationId") String notificationId,
                                          @PathParam("format") String format) throws Exception {
    //Check authenticated user
    checkAuthenticatedUserPermission(receiverId);
    //
    String[] mediaTypes = new String[] { "json", "xml" };
    MediaType mediaType = Util.getMediaType(format, mediaTypes);
    //
    Identity sender = identityManager.getOrCreateIdentity(OrganizationIdentityProvider.NAME, senderId, true);
    Identity receiver = identityManager.getOrCreateIdentity(OrganizationIdentityProvider.NAME, receiverId, true);
    if (sender == null || receiver == null) {
      throw new WebApplicationException(Response.Status.BAD_REQUEST);
    }
    relationshipManager.deny(sender, receiver);
    webNotificationStorage.remove(notificationId);
    //
    return Util.getResponse(getUserWebNotification(receiverId), uriInfo, mediaType, Response.Status.OK);
  }

  /**
   * Processes the "Accept the invitation to join a space" action and update notification.
   *
   * @param userId The invitee's remote Id.
   * @param spaceId Id of the space.
   * @notificationId of the web notification message
   * @authentication
   * @request
   * GET: {@code localhost:8080/rest/social/intranet-notifications/acceptInvitationToJoinSpace/e1cacf067f0001015ac312536462fc6b/john/<notificationId>/message.json}
   * @throws Exception
   */
  @GET
  @RolesAllowed("users")
  @ExoCSRFCheck
  @Path("acceptInvitationToJoinSpace/{spaceId}/{userId}/{notificationId}/message.{format}")
  public Response acceptInvitationToJoinSpace(@Context UriInfo uriInfo,
                                              @PathParam("spaceId") String spaceId,
                                               @PathParam("userId") String userId,
                                               @PathParam("notificationId") String notificationId,
                                               @PathParam("format") String format) throws Exception {
    //Check authenticated user
    checkAuthenticatedUserPermission(userId);
    //
    Space space = spaceService.getSpaceById(spaceId);
    if (space == null) {
      throw new WebApplicationException(Response.Status.BAD_REQUEST);
    }

    List<String> invitedUsers = Arrays.asList(space.getInvitedUsers());
    if (!invitedUsers.contains(userId)) {
      throw new WebApplicationException(Response.Status.FORBIDDEN);
    }
    //
    String[] mediaTypes = new String[] { "json", "xml" };
    MediaType mediaType = Util.getMediaType(format, mediaTypes);

    //update notification
    NotificationInfo info = webNotificationStorage.get(notificationId);
    info.setTo(userId);
    info.key(new PluginKey("SpaceInvitationPlugin"));
    Map<String, String> ownerParameter = new HashMap<String, String>();
    ownerParameter.put("spaceId", spaceId);
    ownerParameter.put("status", "accepted");
    info.setOwnerParameter(ownerParameter);
    MessageInfo messageInfo = sendBackNotif(info);
    if (messageInfo == null) {
      throw new WebApplicationException(Response.Status.INTERNAL_SERVER_ERROR);
    }

    spaceService.addMember(space, userId);
    return Util.getResponse(messageInfo, uriInfo, mediaType, Response.Status.OK);
  }
  
  /**
   * Processes the "Deny the invitation to join a space" action.
   *
   * @param userId The invitee's remote Id.
   * @param spaceId Id of the space.
   * @authentication
   * @request
   * GET: localhost:8080/rest/social/intranet-notifications/ignoreInvitationToJoinSpace/e1cacf067f0001015ac312536462fc6b/john
   * @throws Exception
   */
  @GET
  @RolesAllowed("users")
  @ExoCSRFCheck
  @Path("ignoreInvitationToJoinSpace/{spaceId}/{userId}/{notificationId}/message.{format}")
  public Response ignoreInvitationToJoinSpace(@Context UriInfo uriInfo,
                                           @PathParam("spaceId") String spaceId,
                                           @PathParam("userId") String userId,
                                           @PathParam("notificationId") String notificationId,
                                           @PathParam("format") String format) throws Exception {
    //Check authenticated user
    checkAuthenticatedUserPermission(userId);
    //
    String[] mediaTypes = new String[] { "json", "xml" };
    MediaType mediaType = Util.getMediaType(format, mediaTypes);
    //
    Space space = spaceService.getSpaceById(spaceId);
    if (space == null) {
      throw new WebApplicationException(Response.Status.BAD_REQUEST);
    }
    spaceService.removeInvitedUser(space, userId);
    //
    webNotificationStorage.remove(notificationId);
    
    return Util.getResponse(getUserWebNotification(userId), uriInfo, mediaType, Response.Status.OK);
  }
  
  /**
   * Adds a member to a space and update notification.
   *
   * @param uriInfo
   * @param spaceId Id of the space.
   * @param requestUserId The remote Id of the user who requests for joining the space.
   * @param currentUserId the userId
   * @param notificationId
   * @param format
   * @notificationId of the web notification message
   * @authentication
   * @request
   * GET: {@code localhost:8080/rest/social/intranet-notifications/validateRequestToJoinSpace/e1cacf067f0001015ac312536462fc6b/john/<notificationId>/message.json}
   * @throws Exception
   */
  @GET
  @RolesAllowed("users")
  @ExoCSRFCheck
  @Path("validateRequestToJoinSpace/{spaceId}/{requestUserId}/{currentUserId}/{notificationId}/message.{format}")
  public Response validateRequestToJoinSpace(@Context UriInfo uriInfo,
                                         @PathParam("spaceId") String spaceId,
                                         @PathParam("requestUserId") String requestUserId,
                                         @PathParam("currentUserId") String currentUserId,
                                         @PathParam("notificationId") String notificationId,
                                         @PathParam("format") String format) throws Exception {
    //Check authenticated user
    checkAuthenticatedUserPermission(currentUserId);

    //check space existence
    Space space = spaceService.getSpaceById(spaceId);
    if (space == null) {
      throw new WebApplicationException(Response.Status.BAD_REQUEST);
    }

    //check that caller is manager
    List<String> managers = Arrays.asList(space.getManagers());
    if (!managers.contains(currentUserId)) {
      throw new WebApplicationException(Response.Status.UNAUTHORIZED);
    }

    //check that requestUserId is in the pending user list
    List<String> pendingUsers = Arrays.asList(space.getPendingUsers());
    if (!pendingUsers.contains(requestUserId)) {
      throw new WebApplicationException(Response.Status.FORBIDDEN);
    }

    //
    String[] mediaTypes = new String[] { "json", "xml" };
    MediaType mediaType = Util.getMediaType(format, mediaTypes);
    //update notification
    NotificationInfo info = webNotificationStorage.get(notificationId);
    info.setTo(currentUserId);
    info.key(new PluginKey("RequestJoinSpacePlugin"));
    Map<String, String> ownerParameter = new HashMap<String, String>();
    ownerParameter.put("spaceId", spaceId);
    ownerParameter.put("request_from", requestUserId);
    ownerParameter.put("status", "accepted");
    info.setOwnerParameter(ownerParameter);
    MessageInfo messageInfo = sendBackNotif(info);
    if (messageInfo == null) {
      throw new WebApplicationException(Response.Status.INTERNAL_SERVER_ERROR);
    }


    spaceService.addMember(space, requestUserId);
    return Util.getResponse(messageInfo, uriInfo, mediaType, Response.Status.OK);
  }
  
  /**
   * Refuses a user's request for joining a space. 
   *
   * @param uriInfo
   * @param spaceId Id of the space.
   * @param requestUserId The remote Id of the user who requests for joining the space.
   * @param currentUserId
   * @param notificationId
   * @param format
   * @authentication
   * @request
   * GET: localhost:8080/rest/social/intranet-notifications/refuseRequestToJoinSpace/e1cacf067f0001015ac312536462fc6b/john
   * @throws Exception
   */
  @GET
  @RolesAllowed("users")
  @ExoCSRFCheck
  @Path("refuseRequestToJoinSpace/{spaceId}/{requestUserId}/{currentUserId}/{notificationId}/message.{format}")
  public Response refuseRequestToJoinSpace(@Context UriInfo uriInfo,
                                        @PathParam("spaceId") String spaceId,
                                        @PathParam("requestUserId") String requestUserId,
                                        @PathParam("currentUserId") String currentUserId,
                                        @PathParam("notificationId") String notificationId,
                                        @PathParam("format") String format) throws Exception {
    //Check authenticated user
    checkAuthenticatedUserPermission(currentUserId);
    //
    String[] mediaTypes = new String[] { "json", "xml" };
    MediaType mediaType = Util.getMediaType(format, mediaTypes);
    //
    Space space = spaceService.getSpaceById(spaceId);
    if (space == null) {
      throw new WebApplicationException(Response.Status.BAD_REQUEST);
    }
    spaceService.removePendingUser(space, requestUserId);
    webNotificationStorage.remove(notificationId);
    //
    return Util.getResponse(getUserWebNotification(currentUserId), uriInfo, mediaType, Response.Status.OK);
  }
  
  private MessageInfo sendBackNotif(NotificationInfo notification) {
    NotificationContext nCtx = NotificationContextImpl.cloneInstance().setNotificationInfo(notification);
    BaseNotificationPlugin plugin = nCtx.getPluginContainer().getPlugin(notification.getKey());
    if (plugin == null) {
      return null;
    }
    try {
      AbstractChannel channel = nCtx.getChannelManager().getChannel(ChannelKey.key(WebChannel.ID));
      AbstractTemplateBuilder builder = channel.getTemplateBuilder(notification.getKey());
      MessageInfo msg = builder.buildMessage(nCtx);
      msg.setMoveTop(false);
      WebNotificationSender.sendJsonMessage(notification.getTo(), msg);
      notification.setTitle(msg.getBody());
      notification.with(NotificationMessageUtils.SHOW_POPOVER_PROPERTY.getKey(), "true")
                  .with(NotificationMessageUtils.READ_PORPERTY.getKey(), "false");
      webNotificationStorage.update(notification, false);
      return msg;
    } catch (Exception e) {
      LOG.error("Can not send the message to Intranet.", e.getMessage());
      return null;
    }
  }

  private Map<String, Boolean> getUserWebNotification(String userId) throws Exception {
    Map<String, Boolean> data = new HashMap<String, Boolean>();
    List<NotificationInfo> notifications = webNotificationStorage.get(new WebNotificationFilter(userId), 0, 1);
    data.put("showViewAll", (notifications.size() > 0));
    return data;
  }

}
