/*
 *
 *  * Copyright (C) 2003-2016 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 General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, see<http://www.gnu.org/licenses/>.
 *
 */
package org.exoplatform.cs.service;

import org.apache.commons.fileupload.FileItem;
import org.apache.commons.lang.StringUtils;
import org.exoplatform.commons.api.notification.NotificationContext;
import org.exoplatform.commons.api.notification.model.PluginKey;
import org.exoplatform.commons.api.persistence.ExoTransactional;
import org.exoplatform.commons.notification.impl.NotificationContextImpl;
import org.exoplatform.commons.utils.CommonsUtils;
import org.exoplatform.commons.utils.HTMLSanitizer;
import org.exoplatform.cs.dao.EnvironmentDAO;
import org.exoplatform.cs.dao.LogDAO;
import org.exoplatform.cs.dao.SpaceDAO;
import org.exoplatform.cs.dao.TopicDAO;
import org.exoplatform.cs.dto.*;
import org.exoplatform.cs.entity.EnvironmentEntity;
import org.exoplatform.cs.entity.LogEntity;
import org.exoplatform.cs.entity.SpaceEntity;
import org.exoplatform.cs.entity.TopicEntity;
import org.exoplatform.cs.integration.notification.CSRatePlugin;
import org.exoplatform.cs.service.sla.Sla;
import org.exoplatform.cs.service.sla.SlaDelay;
import org.exoplatform.cs.service.sla.SlaService;
import org.exoplatform.cs.service.util.CSUtils;
import org.exoplatform.cs.service.util.CustomerRateMailUtils;
import org.exoplatform.cs.service.util.ForumUtils;
import org.exoplatform.forum.common.CommonUtils;
import org.exoplatform.forum.service.*;
import org.exoplatform.services.listener.ListenerService;
import org.exoplatform.services.organization.User;
import org.exoplatform.services.security.Identity;
import org.exoplatform.social.core.space.SpaceUtils;
import org.exoplatform.task.domain.Project;
import org.exoplatform.task.domain.Status;
import org.exoplatform.task.domain.Task;
import org.exoplatform.task.exception.EntityNotFoundException;
import org.exoplatform.task.service.ProjectService;
import org.exoplatform.task.service.StatusService;
import org.exoplatform.task.service.TaskService;
import org.exoplatform.task.util.ProjectUtil;
import org.exoplatform.task.util.TaskUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.InputStream;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.stream.Stream;

import org.exoplatform.social.core.identity.model.Profile;
import org.exoplatform.social.core.identity.provider.OrganizationIdentityProvider;
import org.exoplatform.social.core.manager.IdentityManager;

/**
 * Created by The eXo Platform SAS
 *
 * @author boubaker.khanfir@exoplatform.com
 * @since Apr 27, 2016
 */
public class TicketService extends BaseService {
  private static final Logger LOG = LoggerFactory.getLogger(TicketService.class);

  private TaskService         taskService;

  private ForumService        forumService;

  private ProjectService      projectService;

  private StatusService       statusService;

  private ListenerService listenerService;

  private SlaService    slaService;

  private LogDAO              logDAO;

  private IdentityManager identityManager;

  private static final String DATE_FORMAT_DISPLAYED_IN_TICKETS_LIST = "yyyy-MM-dd HH:mm:ss";

  private static final String TOPIC_DEFAULT_DATE_FORMAT = "EE MMM dd HH:mm:ss z yyyy";


  private static final String TASK_DEFAULT_DATE_FORMAT = "yyyy-MM-dd HH:mm:ss.SSS";

  private static final DateFormat   DATE_FORMAT_TO_CONVERT_USED_FOR_TICKETS = new SimpleDateFormat(DATE_FORMAT_DISPLAYED_IN_TICKETS_LIST);

  private static final DateFormat   DEFAULT_DATE_FORMAT_USED_FOR_TOPICS = new SimpleDateFormat(TOPIC_DEFAULT_DATE_FORMAT);

  private static final DateFormat DEFAULT_DATE_FORMAT_USED_FOR_TASKS = new SimpleDateFormat(TOPIC_DEFAULT_DATE_FORMAT);
  private static final DateFormat DATE_FORMAT_USED_FOR_TASKS = new SimpleDateFormat(TASK_DEFAULT_DATE_FORMAT);

  public TicketService(ForumService forumService,
                       ListenerService listenerService,
                       ProjectService projectService,
                       StatusService statusService,
                       TaskService taskService,
                       SlaService slaService,
                       EnvironmentDAO environmentDAO,
                       SpaceDAO spaceDAO,
                       TopicDAO topicDAO,
                       LogDAO logDAO,
                       IdentityManager identityManager) {
    super(environmentDAO, spaceDAO, topicDAO, null);
    this.taskService = taskService;
    this.forumService = forumService;
    this.statusService = statusService;
    this.projectService = projectService;
    this.listenerService = listenerService;
    this.logDAO = logDAO;
    this.identityManager = identityManager;
    this.slaService = slaService;
  }

  public EnvironmentDTO getTopicEnvironment(String topicId) {
    if (StringUtils.isBlank(topicId)) {
      throw new IllegalStateException("Parameter 'topicId' is null");
    }
    TopicEntity topicEntity = topicDAO.find(topicId);
    if (topicEntity.getEnvironment() == null) {
      return null;
    }
    return convert(topicEntity.getEnvironment());
  }

  public List<TicketDTO> getTicketsOfSpace(String id) {
    List<TopicEntity> entities = topicDAO.getTopicsBySpace(id, null);
    SpaceEntity space = spaceDAO.find(id);
    List<TicketDTO> tickets = new ArrayList<TicketDTO>();
    for (TopicEntity topicEntity : entities) {
      tickets.add(convertToDTO(topicEntity));
    }
    return tickets;
  }

  public List<TicketDTO> getTicketsByEnvironment(Long id) {
    List<TopicEntity> tickets = topicDAO.getTopicsByEnvironment(id);
    return convertToDTO(tickets);
  }
  public List<TopicEntity> getTicketsEntityByEnvironment(Long id) {
    return  topicDAO.getTopicsByEnvironment(id);
  }

  public List<TicketDTO> getTickets(String selectedSpace, boolean allTickets, String selectedStatus) {
    List<TopicEntity> entities;
    if(allTickets) {
      if(selectedSpace != null && !selectedSpace.isEmpty()) {
        entities = topicDAO.getTopicsBySpace(selectedSpace, selectedStatus);
      } else {
          if(selectedStatus != null && !selectedStatus.isEmpty()) {
              entities = topicDAO.findByStatus(selectedStatus);
          } else {
              entities = topicDAO.getAllTickets();
          }
      }
    } else {
      if(selectedSpace != null && !selectedSpace.isEmpty()) {
        entities = topicDAO.getOpenTicketsBySpace(selectedSpace,selectedStatus);
      } else {
        entities = topicDAO.getOpenTicketsByStatus(selectedStatus);
      }
    }
    return convertToDTO(entities);
  }

  public List<TopicEntity> getAllTickets() {
    return topicDAO.getAllTickets();
  }

  public List<TicketDTO> getAssignedTickets(String assignee, String selectedSpace, boolean allTickets, String selectedStatus){
    List<TopicEntity> tickets = new ArrayList<>();
    if(selectedSpace != null && !selectedSpace.isEmpty()){
      tickets = topicDAO.getTopicsByAssignee(assignee, selectedSpace, allTickets, selectedStatus);
    }else {
      tickets = topicDAO.getTopicsByAssignee(assignee, allTickets, selectedStatus);
    }
    return convertToDTO(tickets);
  }


  public List<TicketDTO> getMyTickets(String creator, String selectedSpace, boolean allTickets, String selectedStatus){
    List<TopicEntity> tickets = new ArrayList<>();
    if(selectedSpace != null && !selectedSpace.isEmpty()){
      tickets = topicDAO.getTopicsByCreator(creator, selectedSpace, allTickets, selectedStatus);
    }else {
      tickets = topicDAO.getTopicsByCreator(creator, allTickets, selectedStatus);
    }
    return convertToDTO(tickets);
  }



  public String getManagersGroupId(String spaceGroupId) throws Exception {
    SpaceEntity space = spaceDAO.find(spaceGroupId);
    return space.getManagersGroupId();
  }

  public SpaceEntity getSpaceByGroupId(String  groupID) {
    return spaceDAO.find(groupID);
  }

  /** Update Ticket
   *  @param ticketDTO the customer ticket
   *  @param user The user who updated the ticket
   *  @throws Exception if broadcasting the event failed
   *  @return TicketDTO updated
   *
   */
  public TicketDTO updateTicket(TicketDTO ticketDTO, User user) throws Exception {
    //TODO Why do we need an entity as a parameter here
    Map<String,String> changes = new HashMap<>();
    TopicEntity topicEntity = topicDAO.find(ticketDTO.getId());
    EnvironmentEntity environment = null;

    ticketDTO.setCreator(topicEntity.getCreator());
    Stream<User> users = CSUtils.getSupportMembers().stream();
    if(ticketDTO.getAssignee() != null && !users.anyMatch( user_ -> user_.getUserName().equals(ticketDTO.getAssignee()))){
      ticketDTO.setAssignee(null);
    }

    if (ticketDTO.getAssignee() != null && !(ticketDTO.getAssignee().equals(topicEntity.getAssignee()))) {
      changes.put(CSConstants.OLD_ASSIGNEE,topicEntity.getAssignee());
      changes.put(CSConstants.NEW_ASSIGNEE,ticketDTO.getAssignee());
    }

    if (ticketDTO.getOwner() != null && !(ticketDTO.getOwner().equals(topicEntity.getOwner()))) {
      changes.put(CSConstants.OLD_OWNER,topicEntity.getOwner().name());
      changes.put(CSConstants.NEW_OWNER,ticketDTO.getOwner().name());
    }
    if(!IssueType.PRODUCT.equals(ticketDTO.getType())) {
      String oldSeverity = null, oldType = null, oldStatus = null;
      if (!(ticketDTO.getSeverity().name().equals(topicEntity.getSeverity().name()))) {

        if(topicEntity.getLastWarningDate()!=null && ("open".equals(ticketDTO.getStatus())||"new".equals(ticketDTO.getStatus()))){
          SpaceEntity space = topicEntity.getSpace();
          Sla sla = slaService.getSlabyName(space.getSla());
          if(sla!=null){
            SlaDelay sDelay = null;
            for (SlaDelay slaDelay : sla.getSlaDelays()){
              if(slaDelay.getSeverity().equals(ticketDTO.getSeverity().name())){
                sDelay=slaDelay;
                break;
              }
            }
            if (sDelay!=null&&sDelay.getUnit().equals("day")){
              long delay = sDelay.getDelay() * 24 * 60 * 60 * 1000;
              ticketDTO.setFirstWarningDate(CSUtils.getNextDate(topicEntity.getCreationDate(),delay*2/3));
              ticketDTO.setLastWarningDate(CSUtils.getNextDate(topicEntity.getCreationDate(),delay));
            }
            if (sDelay!=null&&sDelay.getUnit().equals("hour")){
              long delay = sDelay.getDelay()* 60 * 60 * 1000;
              ticketDTO.setFirstWarningDate(CSUtils.getNextDate(topicEntity.getCreationDate(),delay*2/3));
              ticketDTO.setLastWarningDate(CSUtils.getNextDate(topicEntity.getCreationDate(),delay));
            }
            if (sDelay!=null&&sDelay.isBusiness()){
              Calendar first = ticketDTO.getFirstWarningDate();
              if(first.get(Calendar.HOUR_OF_DAY)>=0 && first.get(Calendar.HOUR_OF_DAY)<9) {
                first.add(Calendar.DATE, -1);
                first.set(Calendar.HOUR_OF_DAY,17);
                ticketDTO.setFirstWarningDate(first);
              }
              if(first.get(Calendar.HOUR_OF_DAY)>=18 && first.get(Calendar.HOUR_OF_DAY)<=23){
                first.set(Calendar.HOUR_OF_DAY,17);
                ticketDTO.setFirstWarningDate(first);
              }
              Calendar last = ticketDTO.getLastWarningDate();
              if(last.get(Calendar.HOUR_OF_DAY)>=0 && last.get(Calendar.HOUR_OF_DAY)<9) {
                last.set(Calendar.HOUR_OF_DAY,9);
                ticketDTO.setLastWarningDate(last);
              }
              if(last.get(Calendar.HOUR_OF_DAY)>=18 && last.get(Calendar.HOUR_OF_DAY)<=23){
                last.add(Calendar.DATE, 1);
                last.set(Calendar.HOUR_OF_DAY,9);
                ticketDTO.setLastWarningDate(last);
              }
            }

          }
          }

        oldSeverity = topicEntity.getSeverity().name();
        changes.put(CSConstants.OLD_SEVERITY,oldSeverity);
        changes.put(CSConstants.NEW_SEVERITY,ticketDTO.getSeverity().name());
      }
      if (!(ticketDTO.getType().equals(topicEntity.getType()))) {
        oldType = topicEntity.getType().name();
        changes.put(CSConstants.OLD_TYPE,oldType);
        changes.put(CSConstants.NEW_TYPE,ticketDTO.getType().name());
      }
      if (!(ticketDTO.getStatus().equals(topicEntity.getStatus()))) {
        oldStatus = topicEntity.getStatus();

          ticketDTO.setFirstWarningDate(null);
          ticketDTO.setLastWarningDate(null);

        if(ticketDTO.getTimeToFirstResponse()==null &&("considered".equals(ticketDTO.getStatus())||("new".equals(topicEntity.getStatus())&&"open".equals(ticketDTO.getStatus())))) {
          ticketDTO.setAssignee(user.getUserName());
          long timeToFirstResponse = CSUtils.getDiffBetweenTwoDates(topicEntity.getCreationDate(),Calendar.getInstance());
          ticketDTO.setTimeToFirstResponse(timeToFirstResponse);
          changes.put(CSConstants.TIME_TO_FIRST_RESPONSE,Long.toString(timeToFirstResponse));
        }
        if(ticketDTO.getTimeToResolution()==null &&("resolved_wfv".equals(ticketDTO.getStatus())||"resolved_maintenance".equals(ticketDTO.getStatus())||"resolved".equals(ticketDTO.getStatus()))) {
          long timeToResolution= CSUtils.getDiffBetweenTwoDates(topicEntity.getCreationDate(),Calendar.getInstance());
          ticketDTO.setTimeToResolution(timeToResolution);
          changes.put(CSConstants.TIME_TO_RESOLUTION,Long.toString(timeToResolution));
        }
        changes.put(CSConstants.OLD_STATUS,topicEntity.getStatus());
        changes.put(CSConstants.NEW_STATUS,ticketDTO.getStatus());
      }

      if (ticketDTO.getEnvironmentId() != null) {
        environment = environmentDAO.find(ticketDTO.getEnvironmentId());
      }
    }
    changes.put(CSConstants.USERID, user.getUserName());

    convertToEntity(ticketDTO, topicEntity, environment);
    topicEntity.setUpdateDate(Calendar.getInstance());
    topicDAO.update(topicEntity);
    listenerService.broadcast(CSConstants.TICKET_UPDATED,topicEntity,changes);
    if ( ticketDTO.getStatus().equals("closed") && ticketDTO.getCustomerSatisfied() == null ) {
      Profile reciepentProfile = identityManager.getOrCreateIdentity(OrganizationIdentityProvider.NAME,ticketDTO.getCreatorId(),false).getProfile();
      SpaceEntity spaceEntity = getSpaceByGroupId(ticketDTO.getSpaceGroupId());
      InputStream mailTemplate = this.getClass().getClassLoader().getResourceAsStream("conf/portal/customerratemailtemplate.html");

      CustomerRateMailUtils.sendCustomerRateEmail(ticketDTO,spaceEntity,reciepentProfile,mailTemplate);
    }
/*
    SpaceEntity spaceEntity = spaceDAO.find(ticketDTO.getSpaceGroupId());
*/
    return convertToDTO(topicEntity);
  }

  /**
   * Update the creator on the entity
   * @param ticketDTO the ticket to update
   * @param creator the creator of the ticket
   */
  public void updateTicketCreator(TicketDTO ticketDTO, String creator) throws Exception {

    TopicEntity topicEntity = topicDAO.find(ticketDTO.getId());
    topicEntity.setCreator(creator);
    topicDAO.update(topicEntity);
  }

  /**
   * Update the creator on the entity
   * @param ticketDTO the ticket to update
   */
  public void updateTicketSatisfaction(TicketDTO ticketDTO, String userId) throws Exception {

    TopicEntity topicEntity = topicDAO.find(ticketDTO.getId());
    topicEntity.setCustomerSatisfied(ticketDTO.getCustomerSatisfied());
    topicDAO.update(topicEntity);
    Map<String,String> changes = new HashMap<>();
    changes.put(CSConstants.USERID, userId);
    changes.put(CSConstants.NEW_CUSTOMER_SATISFACTION, Boolean.toString(ticketDTO.getCustomerSatisfied()));
    listenerService.broadcast(CSConstants.TICKET_UPDATED,topicEntity,changes);

  }

  /**
   * Update the creator on the entity
   * @param ticketDTO the ticket to update
   * @param owner the creator of the ticket
   */
  public void updateTicketOwner(TicketDTO ticketDTO, Owner owner) throws Exception {

    TopicEntity topicEntity = topicDAO.find(ticketDTO.getId());
    topicEntity.setOwner(owner);
    topicDAO.update(topicEntity);
  }

  /**
   * Update the creator on the entity
   * @param ticketDTO the ticket to update
   * @param assignee the assignee of the ticket
   */
  public void updateTicketAssignee(TicketDTO ticketDTO, String assignee) throws Exception {

    TopicEntity topicEntity = topicDAO.find(ticketDTO.getId());
    topicEntity.setAssignee(assignee);
    topicDAO.update(topicEntity);
  }

  /**
   * CancelTicketWarning
   * @param ticketDTO the ticket to update
   */
  public void cancelTicketWarning(TicketDTO ticketDTO) throws Exception {

    TopicEntity topicEntity = topicDAO.find(ticketDTO.getId());
    topicEntity.setFirstWarningDate(null);
    topicEntity.setLastWarningDate(null);
    topicDAO.update(topicEntity);
  }



  /**
   * Update the link on the entity
   * @param ticket the ticket to update
   * @param link the link of the ticket
   */
  public void updateTicketLink(TicketDTO ticket, String link){
    TopicEntity entity = topicDAO.find(ticket.getId());
    entity.setLink(link);
    topicDAO.update(entity);
  }


  @ExoTransactional
  public void createTicket(TicketDTO ticketDTO, User user, String spaceLink) throws Exception {
    if (user == null) {
      throw new IllegalArgumentException("Method saveUser - argument user is null");
    }
    String email = user.getEmail();
    if (StringUtils.isBlank(email)) {
      throw new IllegalArgumentException("Method saveUser - argument user.email is null");
    }
    if (ticketDTO == null) {
      throw new IllegalArgumentException("Method saveUser - argument ticketDTO is null");
    }
    if (!ticketDTO.verifySaveConditions()) {
      throw new IllegalArgumentException("Method saveUser - argument ticketDTO is not conform: " + ticketDTO);
    }

    SpaceEntity spaceEntity = spaceDAO.find(ticketDTO.getSpaceGroupId());
    if (spaceEntity == null) {
      throw new IllegalStateException("Method saveUser - Space is not found");
    }

    Date currentDate = CommonUtils.getGreenwichMeanTime().getTime();
    long tagIndex = spaceEntity.getTagIndex();
    TopicEntity topicEntity = new TopicEntity();

    if (IssueType.PRODUCT.equals(ticketDTO.getType())) {
      Task task = new Task();
      task.setDescription(ticketDTO.getDescription());
      task.setTitle(ticketDTO.getTitle());
      task.setAssignee(spaceEntity.getTaskDefaultAssignee());
      ticketDTO.setAssignee(spaceEntity.getTaskDefaultAssignee());
      task.setCreatedBy(user.getUserName());
      task.setPriority(spaceEntity.getTaskPriority());
      task.setCreatedTime(currentDate);
      Date startDate = (ticketDTO.getStartDate()!=null)?ticketDTO.getStartDate().getTime():null;
      task.setStartDate(startDate);
      Date endDate = (ticketDTO.getEndDate()!=null)?ticketDTO.getEndDate().getTime():null;
      task.setEndDate(endDate);
      task.setDueDate(endDate);

      Status status = getSpaceProjectStatus(ticketDTO.getSpaceGroupId(), user.getUserName());
      task.setStatus(status);
      task = taskService.createTask(task);

      ticketDTO.setId("" + task.getId());
     // String ticketDateTask = convertDate(DEFAULT_DATE_FORMAT_USED_FOR_TASKS, task.getCreatedTime().toString());
      Calendar cal = Calendar.getInstance();
      cal.setTime(task.getCreatedTime());
      ticketDTO.setStartDate(cal);
      String space = ForumUtils.getSpaceForum(forumService,spaceEntity.getGroupId()).getForumName();
      String SpaceName = SpaceUtils.cleanString(space);
      String taskLink = "/portal/g/:spaces:" + SpaceName + "/" + SpaceName + "/tasks" + TaskUtil.URL_TASK_DETAIL + task.getId();
      ticketDTO.setLink(taskLink);
      convertToEntity(ticketDTO, topicEntity, null);
      topicDAO.create(topicEntity);
      Map<String,String> data = new HashMap<>();
      data.put(CSConstants.USERID,user.getUserName());
      listenerService.broadcast(CSConstants.TICKET_CREATED,topicEntity,data);
    } else if (IssueType.INCIDENT.equals(ticketDTO.getType()) || IssueType.INFORMATION.equals(ticketDTO.getType())) {
      Topic newTopic = new Topic();
      newTopic.setTopicName(HTMLSanitizer.sanitize("[" + spaceEntity.getTagPrefix() + "-" + tagIndex + "] " + ticketDTO.getTitle()));
      List<FileItem> files = ticketDTO.getFiles();
      if (files != null && !files.isEmpty()) {
        List<ForumAttachment> forumAttachments = new ArrayList<ForumAttachment>();
        for (final FileItem fileItem : files) {
          ForumAttachment attachment = new BufferAttachment() {
            private static final long serialVersionUID = 1221108176051647731L;

            @Override
            public InputStream getInputStream() throws Exception {
              return fileItem.getInputStream();
            }
          };
          attachment.setName(fileItem.getName());
          attachment.setMimeType(fileItem.getContentType());
          forumAttachments.add(attachment);
        }
        newTopic.setAttachments(forumAttachments);
      }

      Category spaceCategory = forumService.getCategoryIncludedSpace();
      if (spaceCategory == null) {
        throw new IllegalStateException("Cannot find Forum Spaces category.");
      }
      String groupId = spaceEntity.getGroupId();

      Forum spaceForum = ForumUtils.getSpaceForum(forumService, groupId);
      String link;
      if(spaceLink != null && !spaceLink.isEmpty()){
        link = spaceLink + "/forum/topic/" + newTopic.getId();
      } else {
        link = ForumUtils.createdForumLink(ForumUtils.TOPIC, newTopic.getId(), false);
        link = link.replace("/ticket", "/forum");
      }
      ticketDTO.setLink(link);

      newTopic.setModifiedBy(user.getUserName());
      newTopic.setModifiedDate(currentDate);
      // encode XSS script
      String message = HTMLSanitizer.sanitize(ticketDTO.getDescription());
      newTopic.setDescription(message);
      newTopic.setLink(link);
      newTopic.setIsNotifyWhenAddPost(email);
      newTopic.setIsWaiting(false);
      newTopic.setIsClosed(false);
      newTopic.setIsLock(false);
      newTopic.setIsModeratePost(false);
      newTopic.setIsSticky(false);
      newTopic.setIcon("");
      newTopic.setOwner(user.getUserName());

      // TODO, do we need a specific icon ?
      // newTopic.setIcon("uiIconForumTopic uiIconForumLightGray");

      // Save Topic
      forumService.saveTopic(spaceCategory.getId(), spaceForum.getId(), newTopic, true, false, new MessageBuilder());
/*
      ForumUtils.updateForumTags(forumService, ticketDTO,newTopic,null,null, null);
*/

      // Save entity
      EnvironmentEntity environmentEntity = null;
      if (ticketDTO.getType().equals(IssueType.INCIDENT)) {
        environmentEntity = environmentDAO.find(ticketDTO.getEnvironmentId());
        if (environmentEntity == null) {
          throw new IllegalStateException("Environment with id '" + ticketDTO.getEnvironmentId() + "' is null");
        } else if (environmentEntity.getSpace() == null) {
          // The space is mandatory field, but in case the model has evolved,
          // handle
          // this
          throw new IllegalStateException("Environment's space is null");
        }
      }
      // Increment Tag index
      spaceEntity.setTagIndex(tagIndex + 1);
      spaceDAO.update(spaceEntity);

      // Save ticket
      ticketDTO.setId(newTopic.getId());
      ticketDTO.setLink(newTopic.getLink());
      ticketDTO.setTitle(newTopic.getTopicName());
      ticketDTO.setCreator(user.getUserName());
      ticketDTO.setTicketId(spaceEntity.getTagPrefix() + "-" + tagIndex);
      Calendar cal = Calendar.getInstance();
      cal.setTime(newTopic.getCreatedDate());
      ticketDTO.setStartDate(cal);
      ticketDTO.setUpdateDate(cal);
      ticketDTO.setOwner(Owner.EMPTY);

      Sla sla = slaService.getSlabyName(spaceEntity.getSla());
      if(sla!=null){
        SlaDelay sDelay = null;
        for (SlaDelay slaDelay : sla.getSlaDelays()){
          if(slaDelay.getSeverity().equals(ticketDTO.getSeverity().name())){
            sDelay=slaDelay;
            break;
          }
        }
        if (sDelay!=null && sDelay.getUnit().equals("day")){
          long delay = sDelay.getDelay() * 24 * 60 * 60 * 1000;
          ticketDTO.setFirstWarningDate(CSUtils.getNextDate(Calendar.getInstance(),delay*2/3));
          ticketDTO.setAutoAssignDate(CSUtils.getNextDate(Calendar.getInstance(),delay*1/3));
          ticketDTO.setLastWarningDate(CSUtils.getNextDate(Calendar.getInstance(),delay));
        }
        if (sDelay!=null && sDelay.getUnit().equals("hour")){
          long delay = sDelay.getDelay()* 60 * 60 * 1000;
          ticketDTO.setFirstWarningDate(CSUtils.getNextDate(Calendar.getInstance(),delay*2/3));
          ticketDTO.setAutoAssignDate(CSUtils.getNextDate(Calendar.getInstance(),delay*1/3));
          ticketDTO.setLastWarningDate(CSUtils.getNextDate(Calendar.getInstance(),delay));
        }

        if (sDelay!=null&&sDelay.isBusiness()){
          Calendar first = ticketDTO.getFirstWarningDate();
          if(first.get(Calendar.HOUR_OF_DAY)>=0 && first.get(Calendar.HOUR_OF_DAY)<9) {
            first.add(Calendar.DATE, -1);
            first.set(Calendar.HOUR_OF_DAY,17);
            ticketDTO.setFirstWarningDate(first);
          }
          if(first.get(Calendar.HOUR_OF_DAY)>=18 && first.get(Calendar.HOUR_OF_DAY)<=23){
            first.set(Calendar.HOUR_OF_DAY,17);
            ticketDTO.setFirstWarningDate(first);
          }
          Calendar last = ticketDTO.getLastWarningDate();
          if(last.get(Calendar.HOUR_OF_DAY)>=0 && last.get(Calendar.HOUR_OF_DAY)<9) {
            last.set(Calendar.HOUR_OF_DAY,9);
            ticketDTO.setLastWarningDate(last);
          }
          if(last.get(Calendar.HOUR_OF_DAY)>=18 && last.get(Calendar.HOUR_OF_DAY)<=23){
            last.add(Calendar.DATE, 1);
            last.set(Calendar.HOUR_OF_DAY,9);
            ticketDTO.setLastWarningDate(last);
          }
        }

      }
      convertToEntity(ticketDTO, topicEntity, environmentEntity);
      try {
        String ticketType = topicEntity.getType().name();
        if(ticketType.equals(IssueType.INCIDENT.name())){
          String fl = topicEntity.getSpace().getIncidentFlow();
          topicEntity.setStatus(CSUtils.getDefaultState(fl));
        }else{
          String fl = topicEntity.getSpace().getInformationFlow();
          topicEntity.setStatus(CSUtils.getDefaultState(fl));
        }
      } catch (NullPointerException e) {
        topicEntity.setStatus(CSConstants.STATUS_OPEN);
      }

      topicDAO.create(topicEntity);
      Map<String,String> data = new HashMap<>();
      data.put(CSConstants.USERID,user.getUserName());
      listenerService.broadcast(CSConstants.TICKET_CREATED,topicEntity,data);
    } else {
      // In case the types defined IssueType has evolved
      throw new UnsupportedOperationException("Method saveUser - argument ticketDTO.type is not recognized");
    }
  }


  private Status getSpaceProjectStatus(String spaceGroupId, String username) {
    Long spaceProjectId = null;
    if (spaceGroupId != null) {
      List<Project> projects = ProjectUtil.flattenTree(ProjectUtil.getProjectTree(spaceGroupId, projectService), projectService);
      for (Project p : projects) {
        if (p.canView(new Identity(username))) {
          spaceProjectId = p.getId();
        }
      }
    }
    if (spaceProjectId == null) {
      throw new IllegalStateException("Can't fin adequate tasks project for space: " + spaceGroupId);
    }
    Status status = statusService.getDefaultStatus(spaceProjectId);
    return status;
  }

  private void convertToEntity(TicketDTO ticketDTO,
                               TopicEntity topicEntity,
                               EnvironmentEntity environmentEntity) throws Exception{
    topicEntity.setId(ticketDTO.getId());
    topicEntity.setTicketId(ticketDTO.getTicketId());
    topicEntity.setEnvironment(environmentEntity);
    SpaceEntity spaceEntity = spaceDAO.find(ticketDTO.getSpaceGroupId());
    topicEntity.setSpace(spaceEntity);
    topicEntity.setType(ticketDTO.getType());
    topicEntity.setSeverity(ticketDTO.getSeverity());
    topicEntity.setInfoType(ticketDTO.getInfoType());
    topicEntity.setLink(ticketDTO.getLink());
    topicEntity.setAssignee(ticketDTO.getAssignee());
    topicEntity.setOwner(ticketDTO.getOwner());
    topicEntity.setStatus(ticketDTO.getStatus());
    topicEntity.setReason(ticketDTO.getReason());
    topicEntity.setAttachedJira(ticketDTO.getAttachedJira());
    topicEntity.setUpdateDate(ticketDTO.getUpdateDate());
    topicEntity.setCreationDate(ticketDTO.getStartDate());
    topicEntity.setTitle(ticketDTO.getTitle());
    topicEntity.setCreator(ticketDTO.getCreator());
    topicEntity.setFirstWarningDate(ticketDTO.getFirstWarningDate());
    topicEntity.setLastWarningDate(ticketDTO.getLastWarningDate());
    topicEntity.setAutoAssignDate(ticketDTO.getAutoAssignDate());
    topicEntity.setTimeToFirstResponse(ticketDTO.getTimeToFirstResponse());
    topicEntity.setTimeToResolution(ticketDTO.getTimeToResolution());
    topicEntity.setCustomerSatisfied(ticketDTO.getCustomerSatisfied());

  }

  /**
   * Converts a Topic entity to a ticket DTO
   * @param topicEntity
   * @return a ticket DTO representing the topic entity
   */
  private TicketDTO convertToDTO(TopicEntity topicEntity) {
    try {
      TicketDTO ticketDTO = new TicketDTO();
      ticketDTO.setId(topicEntity.getId());
      ticketDTO.setTicketId(topicEntity.getTicketId());
      ticketDTO.setEnvironmentName(topicEntity.getEnvironment() == null ? null : topicEntity.getEnvironment().getName());
      ticketDTO.setSeverity(topicEntity.getSeverity());
      ticketDTO.setInfoType(topicEntity.getInfoType());
      ticketDTO.setLink(topicEntity.getLink());
      ticketDTO.setType(topicEntity.getType());
      ticketDTO.setAssignee(topicEntity.getAssignee());
      ticketDTO.setOwner(topicEntity.getOwner());
      ticketDTO.setAttachedJira(topicEntity.getAttachedJira());
      ticketDTO.setSpaceGroupId(topicEntity.getSpace().getGroupId());
      ticketDTO.setFirstWarningDate(topicEntity.getFirstWarningDate());
      ticketDTO.setLastWarningDate(topicEntity.getLastWarningDate());
      ticketDTO.setLastWarningDate(topicEntity.getLastWarningDate());
      ticketDTO.setUpdateDate(topicEntity.getUpdateDate());
      ticketDTO.setStartDate(topicEntity.getCreationDate());
/*      if(topicEntity.getUpdateDate() != null) {
        String updateDate = topicEntity.getUpdateDate().getTime().toString();
        String formattedUpdateDate = convertDate(DEFAULT_DATE_FORMAT_USED_FOR_TOPICS, updateDate);
        ticketDTO.setUpdateDate(formattedUpdateDate);
      }*/
      if(topicEntity.getTitle() != null && !topicEntity.getTitle().isEmpty()){
        ticketDTO.setTitle(topicEntity.getTitle());
      }
/*      if (topicEntity.getCreationDate() != null) {
        String startDate = topicEntity.getCreationDate().getTime().toString();
        String ticketDate = convertDate(DEFAULT_DATE_FORMAT_USED_FOR_TOPICS, startDate);
        ticketDTO.setStartDate(ticketDate);
      }*/
      ticketDTO.setStatus(topicEntity.getStatus());
      ticketDTO.setReason(topicEntity.getReason());

      switch (topicEntity.getType()) {
        case INFORMATION:
          ticketDTO.setFlow(topicEntity.getSpace().getInformationFlow());
        case INCIDENT:
          ticketDTO.setFlow(topicEntity.getSpace().getIncidentFlow());
          ticketDTO.setEnvironmentId(topicEntity.getEnvironment() == null ? null : topicEntity.getEnvironment().getId());
          break;
        case PRODUCT:
          long taskId = Long.parseLong(topicEntity.getId());
          Task task = null;
          try {
            task = taskService.getTask(taskId);
          } catch (EntityNotFoundException e) {
            LOG.warn("Task with id '{}' was not found", topicEntity.getId());
          }
          if (task == null) {
            break;
          }
/*          if(topicEntity.getCreationDate() != null){
            String ticketDateTask = DEFAULT_DATE_FORMAT_USED_FOR_TASKS.format(topicEntity.getCreationDate().getTime());
            ticketDTO.setStartDate(ticketDateTask);
          }*/
          ticketDTO.setStartDate(topicEntity.getCreationDate());
          ticketDTO.setStatus(task.getStatus() == null ? "inprogress" : task.getStatus().getName());
          break;
        default:
          break;
      }
      ticketDTO.setCreatorId(topicEntity.getCreator());
      if(topicEntity.getCreator()!=null){
        try {
          Profile profile=identityManager.getOrCreateIdentity(OrganizationIdentityProvider.NAME, topicEntity.getCreator(), false).getProfile();
          ticketDTO.setCreator(profile.getFullName());
        } catch (Exception e) {
          ticketDTO.setCreator("");
        }
      }else{
        ticketDTO.setCreator("");
      }
      Calendar now = Calendar.getInstance();
      if(topicEntity.getFirstWarningDate()!=null){
        if(now.after(topicEntity.getFirstWarningDate())) ticketDTO.setWarning("firstWarning");
      }
      if(topicEntity.getLastWarningDate()!=null){
        if(now.after(topicEntity.getLastWarningDate())) ticketDTO.setWarning("lastWarning");
      }

      ticketDTO.setTimeToFirstResponse(topicEntity.getTimeToFirstResponse());
      ticketDTO.setAutoAssignDate(topicEntity.getAutoAssignDate());
      ticketDTO.setTimeToResolution(topicEntity.getTimeToResolution());
      ticketDTO.setCustomerSatisfied(topicEntity.getCustomerSatisfied());
      ticketDTO.setInternal(topicEntity.getSpace().getInternal());
      ticketDTO.setActive(topicEntity.getSpace().isSubscriptionActive());

      return ticketDTO;
    } catch (Exception e) {
    LOG.error("error converting ticket {} from space of group {}",topicEntity.getId(), topicEntity.getSpace().getGroupId(), e);
  }
    return null;
  }

  private List<TicketDTO> convertToDTO(List<TopicEntity> entities){
    List<TicketDTO> tickets = new ArrayList<TicketDTO>();
    for (TopicEntity topicEntity : entities) {
      SpaceEntity space = topicEntity.getSpace();
      String groupId = space.getGroupId();
      TicketDTO ticket = convertToDTO(topicEntity);
      if(ticket != null) {
        tickets.add(ticket);
      }
    }
    return tickets;
  }

  private String convertDate(DateFormat dateFormat, String startDate) {
    Date date = null;
    try {
      date = dateFormat.parse(startDate);
    } catch (ParseException e) {
      LOG.error("Error when parsing date",e);
    }
    if (date != null) {
      return DATE_FORMAT_TO_CONVERT_USED_FOR_TICKETS.format(date);
    } else {
      return null;
    }
  }


  private Calendar convertToCalendar(String dateToFormat){
    Calendar cal = Calendar.getInstance();
    try {
      Date date = DATE_FORMAT_TO_CONVERT_USED_FOR_TICKETS.parse(dateToFormat);
      cal.setTime(date);
    } catch (Exception e) {
      LOG.debug("Error when converting {} to a date using formatter {} ", dateToFormat, DATE_FORMAT_DISPLAYED_IN_TICKETS_LIST);
    }
    return cal;
  }

  /**
   * Delete a ticket and the related task or forum topic
   * @param ticket The ticket to delete
   */
  public void deleteTicket(TicketDTO ticket) {
    TopicEntity entity = topicDAO.find(ticket.getId());
    if(IssueType.PRODUCT == entity.getType()) {
      try {
        taskService.removeTask(Long.parseLong(entity.getId()));
      } catch (EntityNotFoundException e) {
        LOG.error("Task {} was not found, ticket entry will be deleted!",entity.getId() != null ? entity.getId():"");
      }
    } else {
      try {
        Category spaceCategory = forumService.getCategoryIncludedSpace();
        String groupId = entity.getSpace().getGroupId();
        Forum spaceForum = ForumUtils.getSpaceForum(forumService, groupId);
        forumService.removeTopic(spaceCategory.getId(),spaceForum.getId(),entity.getId());
      } catch (Exception e) {
        LOG.error("Topic {} could not be removed or is not found, ticket entry will be deleted!",entity.getId() != null ? entity.getId():"");
      }
    }
    topicDAO.delete(entity);
  }

  public List<TicketDTO> getTickets(Calendar fromDate, Calendar toDate) {
    List<TopicEntity> tickets = topicDAO.findUpdatedBetween(fromDate,toDate);
    return convertToDTO(tickets);
  }

  public List<TicketDTO> getUpdatedTickets(String status, String space, Calendar fromDate, Calendar toDate) {

    List<TopicEntity> tickets = new ArrayList<>();
    if( isEmpty(space) && isEmpty(status)){
      tickets  = topicDAO.findUpdatedBetween(fromDate,toDate);
    }else if( isEmpty(space) && !isEmpty(status)){
      tickets  = topicDAO.getUpdatedTicketsByStatus(status, fromDate,toDate);
    }else if( !isEmpty(space) && isEmpty(status)){
      tickets  = topicDAO.getUpdatedTicketsBySpace(space, fromDate,toDate);
    }else{
      tickets  = topicDAO.getUpdatedTicketsBySpaceAndStatus(space, status, fromDate,toDate);
    }

    return convertToDTO(tickets);
  }



  public List<TicketDTO> getCreatedTickets(String space, Calendar fromDate, Calendar toDate) {
     List<TopicEntity> tickets = new ArrayList<>();
     if( !isEmpty(space)){
       tickets  = topicDAO.getCreatedTicketsBySpace(space, fromDate,toDate);
     }else {
       tickets  = topicDAO.getCreatedTickets(fromDate,toDate);
     }
    return convertToDTO(tickets);
  }


  public List<TicketDTO> getNotAssignedTickets(String space) {
     List<TopicEntity> tickets = new ArrayList<>();
     if( !isEmpty(space)){
       tickets  = topicDAO.getNotAssignedTicketsBySpace(space);
     }else {
       tickets  = topicDAO.getNotAssignedTickets();
     }
    return convertToDTO(tickets);
  }

  public void notifyCustomerRate(TicketDTO ticketDTO, String raterUsername, boolean isRatedFromMail) {
    if (raterUsername == null) {
      raterUsername = ticketDTO.getCreatorId(); // From the email
    }
    NotificationContext ctx = NotificationContextImpl.cloneInstance().append(CSRatePlugin.TICKET,ticketDTO).append(CSRatePlugin.IS_RATED_FROM_MAIL,isRatedFromMail).append(CSRatePlugin.RATER_USERNAME,raterUsername);
    ctx.getNotificationExecutor().with(ctx.makeCommand(PluginKey.key(CSRatePlugin.ID))).execute(ctx);
    if (ticketDTO.getCustomerSatisfied() != null && ticketDTO.getCustomerSatisfied() && ticketDTO.getAssignee() != null) {
      gamifyTicketAssignee(ticketDTO.getAssignee(),ticketDTO,raterUsername);
    }
  }

  private void gamifyTicketAssignee(String ticketAssigneeUsername,TicketDTO ticketDTO, String raterUsername) {
    try {
      Map<String, String> gam = new HashMap<>();
      gam.put("ruleTitle", "supportServiceRating");
      gam.put("receiverId", ticketAssigneeUsername);
      gam.put("senderId", raterUsername);
      gam.put("object", CommonsUtils.getCurrentDomain() + ticketDTO.getLink());
      listenerService.broadcast("exo.gamification.generic.action", gam, "");
      LOG.info("Support Service Gamification event broadcast ");
    } catch (Exception e) {
      LOG.error("Cannot launch Support Service Gamification event" + e.getMessage(), e);
    }
  }


  public List<TopicEntity> findTicketsForReminder(Calendar toDate) {
    return topicDAO.findTicketsForReminder(toDate);
  }


  public List<TopicEntity> findAllTickets() {
    return topicDAO.getAllTickets();
  }


  public Long getTicketsCount(SpaceEntity space, IssueType type) {
     return topicDAO.countTickets(space,type);
  }

  public List<LogEntity> getUpdatedTickets(Calendar from , Calendar to){
    return logDAO.getupdates(from,to);
  }

  public TicketDTO findById(String ticketId) {
    TopicEntity ticket = topicDAO.find(ticketId);
    return convertToDTO(ticket);
  }

  public TicketDTO findByTicketId(String ticketId) {
    return convertToDTO(topicDAO.findByTicketId(ticketId));
  }

  private boolean isEmpty (String string){
   return string == null || string.isEmpty();
  }


  public List<Object[]>  countCustomerTicketsGroupdBySpace(boolean internal) {

    return topicDAO.countCustomerTicketsGroupdBySpace(internal);

  }

  public List<Object[]>  countCustomerTicketsGroupdByOwner(boolean internal) {

    return topicDAO.countCustomerTicketsGroupdByOwner(internal);

  }
  public List<Object[]>  countCustomerTicketsGroupdByStatus(boolean internal) {

    return topicDAO.countCustomerTicketsGroupdByStatus(internal);

  }


    public Long getCustomerOpenTicketsCount(boolean internal) {
        return topicDAO.countOpenCustomerTickets(internal);
    }

    public Long getCustomerClosedTicketsCount(boolean internal) {
        return topicDAO.countClosedCustomerTickets(internal);
    }

    public Long getCustomerOpenTicketsCountByDates(boolean internal, Calendar from, Calendar to) {
        return topicDAO.countOpenCustomerTicketsByDates(internal, from,to);
    }

    public Long getCustomerClosedTicketsCountByDates(boolean internal, Calendar from, Calendar to) {
        return topicDAO.countClosedCustomerTicketsByDates(internal, from,to);
    }

    public List<Object[]>  countCustomerTicketsGroupdByStatusAndDates(boolean internal, Calendar from , Calendar to) {

        return topicDAO.countCustomerTicketsGroupdByStatusAndDates(internal, from,to);

    }

  public Long countSatisfiedTicketNumber(boolean internal) {
    return topicDAO.countSatisfiedTicketNumber(internal);
  }
  public Long countNotSatisfiedTicketNumber(boolean internal) {
    return topicDAO.countNotSatisfiedTicketNumber(internal);
  }


  public Double getTimeToFirstResponseAvg(boolean internal) {
    return topicDAO.getTimeToFirstResponseAvg(internal);
  }
  public Double getTimeToResolutionAvg(boolean internal) {
    return topicDAO.getTimeToResolutionAvg(internal);
  }



  public List<Object[]>  countSpaceTicketsGroupdByStatus(String space) {

    return topicDAO.countSpaceTicketsGroupdByStatus(space);

  }

  public List<Object[]>  countSpaceTicketsGroupdByOwner(String space) {

    return topicDAO.countSpaceTicketsGroupdByOwner(space);

  }

  public List<Object[]>  countSpaceTicketsGroupdByAssignee(String space) {

    return topicDAO.countSpaceTicketsGroupdByAssignee(space);

  }

  public List<Object[]>  countCustomerTicketsGroupdByAssigneeAndDates(boolean internal, Calendar from, Calendar to) {

    return topicDAO.countCustomerTicketsGroupdByAssigneeAndDates(internal, from, to);

  }

  public List<Object[]>  countCustomerTicketsGroupdByAssigne(boolean internal) {

    return topicDAO.countCustomerTicketsGroupdByAssignee(internal);

  }

  public List<Object[]>  countCustomerTicketsGroupdBySeverityAndDates(boolean internal, Calendar from, Calendar to) {

    return topicDAO.countCustomerTicketsGroupdBySeverityAndDates(internal, from, to);

  }

  public List<Object[]>  countCustomerTicketsGroupdBySeverity(boolean internal) {

    return topicDAO.countCustomerTicketsGroupdBySeverity(internal);

  }

  public Long countSpaceSatisfiedTicketNumber(String space) {
    return topicDAO.countSpaceSatisfiedTicketNumber(space);
  }
  public Long countSpaceNotSatisfiedTicketNumber(String space) {
    return topicDAO.countSpaceNotSatisfiedTicketNumber(space);
  }

  public Double getSpaceTimeToFirstResponseAvg(String space) {
    return topicDAO.getSpaceTimeToFirstResponseAvg(space);
  }
  public Double getSpaceTimeToResolutionAvg(String space) {
    return topicDAO.getSpaceTimeToResolutionAvg(space);
  }

}
