package org.exoplatform.cs.service;

import java.math.BigInteger;
import java.text.*;
import java.util.*;
import java.util.stream.Stream;

import javax.annotation.security.RolesAllowed;
import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.*;
import javax.ws.rs.core.*;
import javax.ws.rs.ext.RuntimeDelegate;

import org.gatein.api.ApiException;
import org.gatein.api.navigation.*;
import org.gatein.api.security.Group;
import org.gatein.api.site.SiteId;
import org.json.*;

import org.exoplatform.container.*;
import org.exoplatform.cs.dao.LogDAO;
import org.exoplatform.cs.dao.TopicDAO;
import org.exoplatform.cs.dto.*;
import org.exoplatform.cs.entity.LogEntity;
import org.exoplatform.cs.entity.TopicEntity;
import org.exoplatform.cs.service.tickets.*;
import org.exoplatform.cs.service.util.*;
import org.exoplatform.forum.service.*;
import org.exoplatform.portal.config.UserPortalConfigService;
import org.exoplatform.portal.config.model.*;
import org.exoplatform.portal.mop.SiteKey;
import org.exoplatform.portal.mop.description.DescriptionService;
import org.exoplatform.portal.mop.navigation.NavigationContext;
import org.exoplatform.portal.mop.navigation.NavigationService;
import org.exoplatform.portal.mop.page.PageKey;
import org.exoplatform.services.log.ExoLogger;
import org.exoplatform.services.log.Log;
import org.exoplatform.services.organization.OrganizationService;
import org.exoplatform.services.organization.User;
import org.exoplatform.services.resources.ResourceBundleManager;
import org.exoplatform.services.rest.impl.RuntimeDelegateImpl;
import org.exoplatform.services.rest.resource.ResourceContainer;
import org.exoplatform.services.security.ConversationState;
import org.exoplatform.social.core.space.model.Space;
import org.exoplatform.social.core.space.spi.SpaceService;

/**
 * Created by eXo Platform SAS.
 *
 * @author Ali Hamdi
 * @since 03/04/17
 */
@Path("customerspace")
public class TicketUpdatesService implements ResourceContainer {

  private static final Log LOG = ExoLogger.getLogger(TicketUpdatesService.class);
  private static final CacheControl cc;
  private static final String PATTERN = "dd-MM-yyyy HH:mm:ss Z";
  private static final SimpleDateFormat sdf = new SimpleDateFormat(PATTERN);


  static {
    RuntimeDelegate.setInstance(new RuntimeDelegateImpl());
    cc = new CacheControl();
    cc.setNoCache(true);
    cc.setNoStore(true);
  }

  /**
   * This endpoint will provide the lately updated tickets
   * @param uriInfo URI info
   * @param from Date to get updates from
   * @param to Date to get updates to
   * @return Json array of updated tickets
   */
  @GET
  @RolesAllowed("administrators")
  @Path("cstickets/updates.json")
  @Produces(MediaType.APPLICATION_JSON)
  public Response getUpdates(@Context UriInfo uriInfo,
                             @QueryParam("from") String from,
                             @QueryParam("to") String to) {
    TicketService ticketService = getService(TicketService.class, null);
    LogDAO logDAO = getService(LogDAO.class, null);
    String DATE_FORMAT = "dd-MM-yyyy";
    DateFormat df = new SimpleDateFormat(DATE_FORMAT);
    Calendar fromDate = Calendar.getInstance();
    Calendar toDate = Calendar.getInstance();
    try {
      if(from != null && !from.isEmpty()) {
        fromDate.setTime(df.parse(from));
      } else {
        fromDate.add(Calendar.MONTH, -1); // default value for from is the value of To Date minus one month
      }
      if(to != null && !to.isEmpty()) {
        toDate.setTime(df.parse(to));
      }
    } catch (ParseException e) {
      LOG.error("Start and End Date format should be {}", DATE_FORMAT);
      return Response.status(Response.Status.INTERNAL_SERVER_ERROR).build();
    }
    List<LogEntity> updates = logDAO.getupdates(fromDate,toDate);
    String response = buildJsonArrayOfUpdates(updates);
    return Response.ok(response, MediaType.APPLICATION_JSON).cacheControl(cc).build();
  }


  @GET
  @RolesAllowed("administrators")
  @Path("cstickets/getstats")
  @Produces(MediaType.APPLICATION_JSON)
  public Response getStats(@Context UriInfo uriInfo,
                            @QueryParam("internal") Boolean internal) {
    LogDAO logDAO = getService(LogDAO.class, null);
    if(internal==null) internal = false;
    try {

      JSONArray TicketsNumber = new JSONArray();
      List<Object[]> results =logDAO.getLogsGroupdByNewValue(internal);
      for (int i = 0; i < results.size(); i++) {
        Object[] arr = results.get(i);
        JSONObject data = new JSONObject();
        data.put("satatus",((String) arr[0])!=null ? ((String) arr[0]).toString()  : "_");
        data.put("logId",((BigInteger)arr[1]).longValue());
        data.put("ticketId",((BigInteger)arr[1]).longValue());
        TicketsNumber.put(data);
      }
      String response = TicketsNumber.toString();
      return Response.ok(response, MediaType.APPLICATION_JSON).cacheControl(cc).build();
    } catch (Exception e) {
      return Response.status(Response.Status.INTERNAL_SERVER_ERROR).build();
    }
  }

  @GET
  @RolesAllowed("administrators")
  @Path("cstickets/getlogs")
  @Produces(MediaType.APPLICATION_JSON)
  public Response getLogs(@Context UriInfo uriInfo,
                          @QueryParam("newvalue") String newValue,
                            @QueryParam("type") String type) {
    LogDAO logDAO = getService(LogDAO.class, null);
    try {
      List<LogEntity> updates = null;
      if(newValue!= null){
        updates = logDAO.getLogsByNewValue(newValue);
      }else if(type!= null){
        updates = logDAO.getByType(LogType.valueOf(type),0,0);
      }else{
        updates = logDAO.findAll();
      }
      String response = buildJsonArrayOfUpdates(updates);
      return Response.ok(response, MediaType.APPLICATION_JSON).cacheControl(cc).build();
    } catch (Exception e) {
      return Response.status(Response.Status.INTERNAL_SERVER_ERROR).build();
    }
  }

  /**
   * 
   * @param clazz service class
   * @param containerName portal container name
   * @param <T> Type of class
   * @return the selected service
   */
  public static <T> T getService(Class<T> clazz, String containerName) {
    ExoContainer container = ExoContainerContext.getCurrentContainer();
    if (containerName != null) {
      container = RootContainer.getInstance().getPortalContainer(containerName);
    }
    if (container.getComponentInstanceOfType(clazz) == null) {
      containerName = PortalContainer.getCurrentPortalContainerName();
      container = RootContainer.getInstance().getPortalContainer(containerName);
    }
    return clazz.cast(container.getComponentInstanceOfType(clazz));
  }

  @POST
  @Path("cstickets/addcreator")
  @RolesAllowed("administrators")
  @Consumes({MediaType.APPLICATION_JSON})
  public Response addCreator(@Context HttpServletRequest request,
                       @Context UriInfo uriInfo) throws Exception {

    try {
      TicketService ticketService = getService(TicketService.class, null);
      ForumService forumService = getService(ForumService.class, null);
      OrganizationService organizationService = getService(OrganizationService.class, null);
      String currentUser = ConversationState.getCurrent().getIdentity().getUserId();
      User user = organizationService.getUserHandler().findUserByName(currentUser);
      List<TicketDTO> tickets = ticketService.getTickets("",true,"");
      for(TicketDTO ticket : tickets){
        if(ticket.getCreator()==null||ticket.getCreator().equals("")){
          String topicId = ticket.getId();
          Category spaceCategory = forumService.getCategoryIncludedSpace();
          Forum forum = ForumUtils.getSpaceForum(forumService, ticket.getSpaceGroupId());
          if (spaceCategory != null && forum != null) {
            try {
              Topic topic = forumService.getTopic(spaceCategory.getId(), forum.getId(), topicId, null);
              ticketService.updateTicketCreator(ticket,topic.getOwner());
              LOG.info("Ticket "+ ticket.getTitle()+"'s creator set to "+topic.getOwner());
            } catch (Exception e) {
              LOG.error("Can not get data of Topic {}", topicId);
            }
          }
        }
      }



      return Response.ok("Tickets Updates").build();
    } catch (Exception e) {
      LOG.error(e);
      return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity("An internal error has occured").build();
    }
  }



  @POST
  @Path("cstickets/addowner")
  @RolesAllowed("administrators")
  @Consumes({MediaType.APPLICATION_JSON})
  public Response addOwner(@Context HttpServletRequest request,
                       @Context UriInfo uriInfo) throws Exception {

    try {
      TicketService ticketService = getService(TicketService.class, null);
      List<TicketDTO> tickets = ticketService.getTickets("",true,"");
      String owner = "";
      for(TicketDTO ticket : tickets){
        if(ticket.getOwner()==null||ticket.getOwner().equals("")){
            try {
              if(ticket.getStatus().equals("closed")){
                ticketService.updateTicketOwner(ticket,Owner.EMPTY);
                owner = Owner.EMPTY.name();
              }else if (ticket.getStatus().equals("suspended_wfi") || ticket.getStatus().equals("resolved_wfv")){
                ticketService.updateTicketOwner(ticket, Owner.CUSTOMER);
                owner =  Owner.CUSTOMER.name();
              }else if(ticket.getStatus().equals("resolved_maintenance")){
                ticketService.updateTicketOwner(ticket, Owner.MAINTENANCE);
                owner = Owner.MAINTENANCE.name();
              } else if(ticket.getStatus().equals("resolved_improvement")){
                ticketService.updateTicketOwner(ticket, Owner.PM);
                owner = Owner.PM.name();
              } else{
                ticketService.updateTicketOwner(ticket, Owner.SUPPORT);
                owner = Owner.SUPPORT.name();
              }

              LOG.info("Ticket "+ ticket.getTitle()+"'s owner set to "+owner);
            } catch (Exception e) {
              LOG.error("Can not update owner");
            }

        }
      }



      return Response.ok("Tickets Updates").build();
    } catch (Exception e) {
      LOG.error(e);
      return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity("An internal error has occured").build();
    }
  }



  @POST
  @Path("cstickets/updateassignee")
  @RolesAllowed("administrators")
  @Consumes({MediaType.APPLICATION_JSON})
  public Response updateAssignee(@Context HttpServletRequest request,
                           @Context UriInfo uriInfo) throws Exception {

    try {
      TicketService ticketService = getService(TicketService.class, null);
      Stream<User> users = CSUtils.getSupportMembers().stream();
      List<TicketDTO> tickets = ticketService.getTickets("",true,"");
      for(TicketDTO ticket : tickets){
        if(ticket.getAssignee()!=null){
          try {
            if(!users.anyMatch( user -> user.getUserName().equals(ticket.getAssignee()))){
              ticketService.updateTicketAssignee(ticket,null);
            }
            LOG.info("Ticket "+ ticket.getTitle()+"'s assignee changed from "+ticket.getAssignee()+" to null");
          } catch (Exception e) {
            LOG.error("Can not update owner");
          }

        }
      }



      return Response.ok("Tickets Updates").build();
    } catch (Exception e) {
      LOG.error(e);
      return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity("An internal error has occured").build();
    }
  }



  @POST
  @Path("cstickets/updatewarning")
  @RolesAllowed("administrators")
  @Consumes({MediaType.APPLICATION_JSON})
  public Response upadateWarning(@Context HttpServletRequest request,
                       @Context UriInfo uriInfo) throws Exception {

    try {
      TicketService ticketService = getService(TicketService.class, null);
      List<TicketDTO> tickets = ticketService.getTickets("",true,"");
      String owner = "";
      for(TicketDTO ticket : tickets){
        if(!ticket.getStatus().equals("open")||!ticket.getStatus().equals("new")){
            try {
              if(ticket.getFirstWarningDate()!=null || ticket.getLastWarningDate()!=null){
                ticketService.cancelTicketWarning(ticket);
              }
              LOG.info("Ticket "+ ticket.getTitle()+"'s warning updated");
            } catch (Exception e) {
              LOG.error("Can not update warning");
            }
        }
      }
      return Response.ok("Tickets Updates").build();
    } catch (Exception e) {
      LOG.error(e);
      return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity("An internal error has occured").build();
    }
  }

  @POST
  @Path("cstickets/updatlog")
  @RolesAllowed("administrators")
  @Consumes({MediaType.APPLICATION_JSON})
  public Response upadatLogs(@Context HttpServletRequest request,
                       @Context UriInfo uriInfo) throws Exception {

    try {
      LogDAO logDAO = getService(LogDAO.class, null);
      List<LogEntity> logs = logDAO.getByType(LogType.TICKET_SEVERITY_CHANGED,0,0);
      TicketStatusFlowService ticketStatusFlowService = getService(TicketStatusFlowService.class, null);
      Map<String, Flow> flows = ticketStatusFlowService.getFlows();
      List<String> states = new ArrayList<>();
      flows.forEach((k,v)-> {for(FlowState flow : v.getStates()){states.add(flow.getState());}});
      for(LogEntity log : logs){
        if(states.contains(log.getNewValue())){
            try {
              log.setType(LogType.TICKET_STATUS_CHANGED);
              logDAO.update(log);
              LOG.info("log type changed");
            } catch (Exception e) {
              LOG.error("Can not update warning");
            }
        }
      }
      return Response.ok("Tickets Updates").build();
    } catch (Exception e) {
      LOG.error(e);
      return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity("An internal error has occured").build();
    }
  }


  @POST
  @Path("cstickets/addlogspaces")
  @RolesAllowed("administrators")
  @Consumes({MediaType.APPLICATION_JSON})
  public Response addLogSpaces(@Context HttpServletRequest request,
                             @Context UriInfo uriInfo) throws Exception {

    try {
      LogDAO logDAO = getService(LogDAO.class, null);
      List<LogEntity> logs = logDAO.findAll();
      for(LogEntity log : logs){
          try {
            if(log.getSpaceId()==null){
              log.setSpaceId(log.getTopic().getSpace().getGroupId());
              logDAO.update(log);
            }

            LOG.info("log space set");
          } catch (Exception e) {
            LOG.error("Can not update log");
          }

      }
      return Response.ok("logs Updates").build();
    } catch (Exception e) {
      LOG.error(e);
      return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity("An internal error has occured").build();
    }
  }

  @POST
  @Path("csspace/updatenav")
  @RolesAllowed("administrators")
  @Consumes({MediaType.APPLICATION_JSON})
  public Response updatenav(@Context HttpServletRequest request,
                           @Context UriInfo uriInfo) throws Exception {

    try {
      LOG.info("=== Start migration of Spaces Navigation");
      long startTime = System.currentTimeMillis();
      CSSpaceService CsSpaceService = getService(CSSpaceService.class, null);
      SpaceService spaceService = getService(SpaceService.class, null);
      UserPortalConfigService portalConfigService = getService(UserPortalConfigService.class, null);
      NavigationService navigationService = getService(NavigationService.class, null);
      DescriptionService descriptionService = getService(DescriptionService.class, null);
      ResourceBundleManager bundleManager = getService(ResourceBundleManager.class, null);
      int i =1;
      List<SpaceDTO> csspaces = CsSpaceService.getAllSpaces();

      for (SpaceDTO cspace : csspaces){
        if(cspace.getInternal()==true) {
          i++;
          continue;
        }
        Space space = spaceService.getSpaceByGroupId(cspace.getGroupId());
        if(space!=null) {

            LOG.info(i+"/"+ csspaces.size()+"----- Start update nav of Space "+space.getDisplayName());
              try {
                portalConfigService.createUserPortalConfig(PortalConfig.GROUP_TYPE, space.getGroupId(), "CSSpace");
                SiteKey siteKey = SiteKey.group(space.getGroupId());
                Navigation navigation = getNav(space, siteKey, navigationService, descriptionService, bundleManager);
                Node home = navigation.getNode(NodePath.path(space.getUrl()), Nodes.visitAll());
                Node tempHome = navigation.getNode(NodePath.path("cs"), Nodes.visitAll());
                Iterator<Node> nodeIterator = tempHome.iterator();
                while (nodeIterator.hasNext()) {
                  Node childNode = (Node) nodeIterator.next();
                  if(home.hasChild(childNode.getName())){
                      if (childNode.getName().equals("guide")) continue;
                    home.removeChild(childNode.getName());
                  }
                  Node newNode = home.addChild(childNode.getName());
                  newNode.setIconName(childNode.getIconName());
                  newNode.setPageId(childNode.getPageId());
                  newNode.setVisibility(childNode.getVisibility());
                  navigation.saveNode(home);
                  navigation.removeNode(childNode.getNodePath());

                  // Convert Portlet preferences
                  convertChildApplicationPreferences(space, siteKey, newNode, portalConfigService);
                }
                // Set home page
                home.setPageId(tempHome.getPageId());
                navigation.saveNode(home);
                // Convert Portlet preferences

                convertChildApplicationPreferences(space, siteKey, home, portalConfigService);

                // Remove temporary home navigation page
                navigation.removeNode(tempHome.getNodePath());

              } catch (Exception e) {
                LOG.error("NewPortalConfig error: " + e.getMessage(), e);
              }
            }
           LOG.info("----- Space "+space.getDisplayName()+" Nav updated");
        i++;
      }

      long endTime = System.currentTimeMillis();
      LOG.info("=== Migration of Spaces Navigation is done in " + (endTime - startTime) + " ms");
      return Response.ok("Migration of Spaces Navigation is done in " + (endTime - startTime) + " ms").build();
    } catch (Exception e) {
      LOG.error(e);
      return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity("An internal error has occured").build();
    }
  }

private Navigation getNav(Space space, SiteKey siteKey, NavigationService navigationService, DescriptionService descriptionService, ResourceBundleManager bundleManager){
  try {
    NavigationContext ctx = navigationService.loadNavigation(siteKey);
    if (ctx == null) return null;
    Group group = new Group(space.getGroupId());
    return new NavigationImpl(new SiteId(group), navigationService, ctx, descriptionService, bundleManager);
  } catch (Throwable t) {
    throw new ApiException("Failed to load navigation", t);
  }

}

  @GET
  @Path("cstickets/getfirttimelogs")
  @RolesAllowed("administrators")
  public Response getFirtTimeLogs(@Context HttpServletRequest request,
                           @Context UriInfo uriInfo,
                           @QueryParam("startdate") long startDate,
                           @QueryParam("enddate") long endDate) throws Exception {

    try {
    JSONArray ticketsJSON = new JSONArray();
      LogDAO logDAO  = getService(LogDAO.class, null);
      Calendar fromDate=Calendar.getInstance();
      fromDate.setTimeInMillis(startDate);
      Calendar toDate=Calendar.getInstance();
      toDate.setTimeInMillis(endDate);
      List<LogEntity> logs = logDAO.getTimeToFirstResponseBetweenDates(fromDate,toDate,false);
    for (LogEntity log:logs){
    JSONObject update = new JSONObject();
    update.put("id", log.getTopic().getId());
    update.put("topicTitle", log.getTopic().getTitle());
    update.put("updater", log.getUserID());
    update.put("updateType", log.getType().name());
    update.put("oldValue", log.getOriginalValue());
    update.put("newValue", log.getNewValue());
    update.put("when", sdf.format(log.getWhen().getTime()));
    update.put("timeToFirstResponse", log.getTimeToFirstResponse());
    update.put("timeToResolution", log.getTimeToResolution());
    update.put("spaceId", log.getSpaceId());
    ticketsJSON.put(update);
  }

      return Response.ok(ticketsJSON.toString()).build();
} catch (Exception e) {
        LOG.error(e);
        return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity("An internal error has occured").build();
        }
        }




  @GET
  @Path("cstickets/getticketstat/{topic}")
  @RolesAllowed("administrators")
  public Response getTicketStat(@Context HttpServletRequest request,
                                 @Context UriInfo uriInfo,
                                 @PathParam("topic") String topic) throws Exception {
    try {
      JSONArray ticketsJSON = new JSONArray();
      LogDAO logDAO  = getService(LogDAO.class, null);

      List<LogEntity> logs = logDAO.getLogsByTicket(topic);
      for (LogEntity log:logs){
        JSONObject update = new JSONObject();
        update.put("id", log.getTopic().getId());
        update.put("topicTitle", log.getTopic().getTitle());
        update.put("updater", log.getUserID());
        update.put("updateType", log.getType().name());
        update.put("oldValue", log.getOriginalValue());
        update.put("newValue", log.getNewValue());
        update.put("when", sdf.format(log.getWhen().getTime()));
        update.put("timeToFirstResponse", log.getTimeToFirstResponse());
        update.put("timeToResolution", log.getTimeToResolution());
        update.put("spaceId", log.getSpaceId());
        ticketsJSON.put(update);
      }

      return Response.ok(ticketsJSON.toString()).build();
    } catch (Exception e) {
      LOG.error(e);
      return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity("An internal error has occured").build();
    }
  }

  @GET
  @Path("cstickets/getresolutiontimelogs")
  @RolesAllowed("administrators")
  public Response getResTimeLogs(@Context HttpServletRequest request,
                                         @Context UriInfo uriInfo,
                                         @QueryParam("startdate") long startDate,

                                       @QueryParam("enddate") long endDate) throws Exception {
    try {
      JSONArray ticketsJSON = new JSONArray();
    LogDAO logDAO  = getService(LogDAO.class, null);
    Calendar fromDate=Calendar.getInstance();
    fromDate.setTimeInMillis(startDate);
    Calendar toDate=Calendar.getInstance();
    toDate.setTimeInMillis(endDate);
    List<LogEntity> logs = logDAO.getTimeToResolutionBetweenDates(fromDate,toDate,false);
        for (LogEntity log:logs){
        JSONObject update = new JSONObject();
        update.put("id", log.getTopic().getId());
        update.put("topicTitle", log.getTopic().getTitle());
        update.put("updater", log.getUserID());
        update.put("updateType", log.getType().name());
        update.put("oldValue", log.getOriginalValue());
        update.put("newValue", log.getNewValue());
        update.put("when", sdf.format(log.getWhen().getTime()));
        update.put("timeToFirstResponse", log.getTimeToFirstResponse());
        update.put("timeToResolution", log.getTimeToResolution());
        update.put("spaceId", log.getSpaceId());
        ticketsJSON.put(update);
        }

        return Response.ok(ticketsJSON.toString()).build();
        } catch (Exception e) {
        LOG.error(e);
        return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity("An internal error has occured").build();
        }
        }



  @GET
  @Path("cstickets/getTimeStats")
  @RolesAllowed("administrators")
  public Response getTimeLogs(@Context HttpServletRequest request,
                              @Context UriInfo uriInfo) throws Exception {
    try {
      JSONArray ticketsJSON = new JSONArray();
      LogDAO logDAO  = getService(LogDAO.class, null);
      List<LogEntity> logs =  logDAO.getByType(LogType.TIME_TO_RESOLUTION_ADDED,0,0);
      List<LogEntity> logs_ =  logDAO.getByType(LogType.TIME_TO_FIRST_RESPONSE_ADDED,0,0);
      logs.addAll(logs_);
      for (LogEntity log:logs){
        JSONObject update = new JSONObject();
        update.put("id", log.getTopic().getId());
        update.put("topicTitle", log.getTopic().getTitle());
        update.put("updater", log.getUserID());
        update.put("updateType", log.getType().name());
        update.put("oldValue", log.getOriginalValue());
        update.put("newValue", log.getNewValue());
        update.put("when", sdf.format(log.getWhen().getTime()));
        update.put("timeToFirstResponse", log.getTimeToFirstResponse());
        update.put("timeToResolution", log.getTimeToResolution());
        update.put("spaceId", log.getSpaceId());
        ticketsJSON.put(update);
      }

      return Response.ok(ticketsJSON.toString()).build();
    } catch (Exception e) {
      LOG.error(e);
      return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity("An internal error has occured").build();
    }
  }


  @GET
  @Path("cstickets/updatettr")
  @RolesAllowed("administrators")
  public Response updateTTR(@Context HttpServletRequest request,
                              @Context UriInfo uriInfo) throws Exception {
    try {
      LOG.info("=== Start updating Time To First Response");
      long startTime = System.currentTimeMillis();
      LogDAO logDAO  = getService(LogDAO.class, null);
      TopicDAO topicDAO  = getService(TopicDAO.class, null);
      List<LogEntity> logs =  logDAO.getByType(LogType.TIME_TO_RESOLUTION_ADDED,0,0);
      for (LogEntity log:logs){
        try {
          LOG.info("update Topic: "+log.getTopic().getId()+"____"+"Log TTR: "+log.getTimeToResolution()+"____"+"Topic TTR: "+log.getTopic().getTimeToResolution());
          if(log.getTopic().getTimeToResolution()!=null && log.getTimeToResolution()!=null){
            {
              if(log.getTopic().getTimeToFirstResponse()<0){
                TopicEntity top = log.getTopic();
                top.setTimeToResolution(Long.valueOf(0));
                topicDAO.update(top);
              }
              if(log.getTimeToFirstResponse()<0){
                log.setTimeToFirstResponse(Long.valueOf(0));
                logDAO.update(log);
              }
              }

              if(log.getTopic().getTimeToFirstResponse()>log.getTimeToFirstResponse()){
                TopicEntity top = log.getTopic();
                top.setTimeToResolution(log.getTimeToFirstResponse());
                topicDAO.update(top);
                LOG.error("updated Topic: "+log.getTopic().getId());
              }
            }


      } catch (Exception e) {

          LOG.error("Cannot update Topic: "+log.getTopic().getId()+"____"+"Log TTR: "+log.getTimeToResolution()+"____"+"Topic TTR: "+log.getTopic().getTimeToResolution());
      }
      }
      long endTime = System.currentTimeMillis();
      LOG.info("=== updating Time To Resolution done in " + (endTime - startTime) + " ms");
      return Response.ok("updating Time To First Response is done in " + (endTime - startTime) + " ms").build();
    } catch (Exception e) {
      LOG.error(e);
      return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity("An internal error has occured").build();
    }
  }



  @GET
  @Path("cstickets/cleanttr")
  @RolesAllowed("administrators")
  public Response cleanTTR(@Context HttpServletRequest request,
                            @Context UriInfo uriInfo) throws Exception {
    try {
      LOG.info("=== Start cleaning Time To First Response");
      long startTime = System.currentTimeMillis();
      LogDAO logDAO  = getService(LogDAO.class, null);
      List<LogEntity> logs =  logDAO.getByType(LogType.TIME_TO_RESOLUTION_ADDED,0,0);
      for (LogEntity log:logs){
        try {
          if(log.getTopic().getTimeToResolution()!=null && log.getTimeToResolution()!=null &&  log.getTopic().getTimeToResolution()<log.getTimeToResolution()){
           logDAO.delete(log);
          }
        } catch (Exception e) {

          LOG.error("Cannot update Topic: "+log.getTopic().getId()+"____"+"Log TFR: "+log.getTimeToResolution()+"____"+"Topic TFR: "+log.getTopic().getTimeToResolution());
        }
      }
      long endTime = System.currentTimeMillis();
      LOG.info("=== cleaning Time To Resolution done in " + (endTime - startTime) + " ms");
      return Response.ok("cleaning Time To Resolution is done in " + (endTime - startTime) + " ms").build();
    } catch (Exception e) {
      LOG.error(e);
      return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity("An internal error has occured").build();
    }
  }

  @GET
  @Path("cstickets/updatetfr")
  @RolesAllowed("administrators")
  public Response updateTFR(@Context HttpServletRequest request,
                              @Context UriInfo uriInfo) throws Exception {
    try {
      LOG.info("=== Start updating Time To First Response");
      long startTime = System.currentTimeMillis();
      LogDAO logDAO  = getService(LogDAO.class, null);
      TopicDAO topicDAO  = getService(TopicDAO.class, null);
      List<LogEntity> logs =  logDAO.getByType(LogType.TIME_TO_FIRST_RESPONSE_ADDED,0,0);
      for (LogEntity log:logs){
        try {
          LOG.info("update Topic: "+log.getTopic().getId()+"____"+"Log TFR: "+log.getTimeToFirstResponse()+"____"+"Topic TFR: "+log.getTopic().getTimeToFirstResponse());
          if(log.getTopic().getTimeToFirstResponse()!=null && log.getTimeToFirstResponse()!=null){
            {
              if(log.getTopic().getTimeToFirstResponse()<0){
                TopicEntity top = log.getTopic();
                top.setTimeToFirstResponse(Long.valueOf(0));
                topicDAO.update(top);
              }
              if(log.getTimeToFirstResponse()<0){
                log.setTimeToFirstResponse(Long.valueOf(0));
                logDAO.update(log);
              }
              }

              if(log.getTopic().getTimeToFirstResponse()>log.getTimeToFirstResponse()){
                TopicEntity top = log.getTopic();
                top.setTimeToFirstResponse(log.getTimeToFirstResponse());
                topicDAO.update(top);
                LOG.error("updated Topic: "+log.getTopic().getId());
              }
            }


      } catch (Exception e) {

          LOG.error("Cannot update Topic: "+log.getTopic().getId()+"____"+"Log TFR: "+log.getTimeToFirstResponse()+"____"+"Topic TFR: "+log.getTopic().getTimeToFirstResponse());
      }
      }
      long endTime = System.currentTimeMillis();
      LOG.info("=== updating Time To First Response done in " + (endTime - startTime) + " ms");
      return Response.ok("updating Time To First Response is done in " + (endTime - startTime) + " ms").build();
    } catch (Exception e) {
      LOG.error(e);
      return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity("An internal error has occured").build();
    }
  }



  @GET
  @Path("cstickets/cleantfr")
  @RolesAllowed("administrators")
  public Response cleanTFR(@Context HttpServletRequest request,
                            @Context UriInfo uriInfo) throws Exception {
    try {
      LOG.info("=== Start cleaning Time To First Response");
      long startTime = System.currentTimeMillis();
      LogDAO logDAO  = getService(LogDAO.class, null);
      TopicDAO topicDAO  = getService(TopicDAO.class, null);
      List<LogEntity> logs =  logDAO.getByType(LogType.TIME_TO_FIRST_RESPONSE_ADDED,0,0);
      for (LogEntity log:logs){
        try {
          if(log.getTopic().getTimeToFirstResponse()!=null && log.getTimeToFirstResponse()!=null &&  log.getTopic().getTimeToFirstResponse()<log.getTimeToFirstResponse()){
           logDAO.delete(log);
          }
        } catch (Exception e) {

          LOG.error("Cannot update Topic: "+log.getTopic().getId()+"____"+"Log TFR: "+log.getTimeToFirstResponse()+"____"+"Topic TFR: "+log.getTopic().getTimeToFirstResponse());
        }
      }
      long endTime = System.currentTimeMillis();
      LOG.info("=== cleaning Time To First Response done in " + (endTime - startTime) + " ms");
      return Response.ok("cleaning Time To First Response is done in " + (endTime - startTime) + " ms").build();
    } catch (Exception e) {
      LOG.error(e);
      return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity("An internal error has occured").build();
    }
  }

  @GET
  @Path("cstickets/getStatusTransitions")
  @RolesAllowed("administrators")
  public Response getStatusTransitions(@Context HttpServletRequest request,
                              @Context UriInfo uriInfo) throws Exception {
    try {
      JSONArray ticketsJSON = new JSONArray();
      LogDAO logDAO  = getService(LogDAO.class, null);
      List<LogEntity> logs =  logDAO.getByType(LogType.TICKET_STATUS_CHANGED,0,0);
      for (LogEntity log:logs){
        JSONObject update = new JSONObject();
        update.put("topicTitle", log.getTopic().getTitle());
        update.put("transition", log.getOriginalValue()+" --> "+log.getNewValue());
        update.put("updater", log.getUserID());
        update.put("when", sdf.format(log.getWhen().getTime()));
        ticketsJSON.put(update);
      }

      return Response.ok(ticketsJSON.toString()).build();
    } catch (Exception e) {
      LOG.error(e);
      return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity("An internal error has occured").build();
    }
  }


  @POST
  @Path("cstickets/setTimes")
  @RolesAllowed("administrators")
  @Consumes({MediaType.APPLICATION_JSON})
  public Response setTimes(@Context HttpServletRequest request,
                           @Context UriInfo uriInfo) throws Exception {

    try {
      TopicDAO topicDAO = getService(TopicDAO.class, null);
      LogDAO logDAO  = getService(LogDAO.class, null);
      List<TopicEntity> tickets = topicDAO.getAllTickets();
      List<String> status = Arrays.asList("resolved_wfv", "resolved_maintenance");

      List<LogEntity> logs = logDAO.getLogsByNewValue("resolved_wfv");
      for (LogEntity log:logs){
        TopicEntity topic = topicDAO.find(log.getTopic().getId());
        if(topic!=null){
          topic.setTimeToResolution(CSUtils.getDiffBetweenTwoDates(topic.getCreationDate(),log.getWhen()));
          topicDAO.update(topic);
            LogEntity logEntity = new LogEntity(LogType.TIME_TO_RESOLUTION_ADDED, "support", log.getWhen(), "","" , topic,null,topic.getTimeToResolution(),topic.getSpace().getGroupId());
            logDAO.create(logEntity);

          LOG.info("Ticket "+topic.getTitle()+ " TimeToResolution set");
        }
      }

      logs = logDAO.getLogsByNewValue("resolved_maintenance");
      for (LogEntity log:logs){
        TopicEntity topic = topicDAO.find(log.getTopic().getId());
        if(topic!=null && "/spaces/exo_tribe_feedback".equals(topic.getSpace().getGroupId())){
          topic.setTimeToResolution(CSUtils.getDiffBetweenTwoDates(topic.getCreationDate(),log.getWhen()));
          topicDAO.update(topic);
            LogEntity logEntity = new LogEntity(LogType.TIME_TO_RESOLUTION_ADDED, "support", log.getWhen(), "","" , topic,null,topic.getTimeToResolution(),topic.getSpace().getGroupId());
            logDAO.create(logEntity);

          LOG.info("Ticket "+topic.getTitle()+ " TimeToResolution set");
        }
      }


      logs = logDAO.getLogsByNewValue("considered");
      for (LogEntity log:logs){
        TopicEntity topic = topicDAO.find(log.getTopic().getId());
        if(topic!=null && topic.getSpace().getInternal()==false){
          topic.setTimeToFirstResponse(CSUtils.getDiffBetweenTwoDates(topic.getCreationDate(),log.getWhen()));
          topicDAO.update(topic);
          LogEntity logEntity = new LogEntity(LogType.TIME_TO_FIRST_RESPONSE_ADDED, "support", log.getWhen(), "","" , topic,topic.getTimeToFirstResponse(),null,topic.getSpace().getGroupId());
          logDAO.create(logEntity);
          LOG.info("Ticket "+topic.getTitle()+ " TimeToFirstResponse set");
        }
      }



/*      for(TopicEntity ticket : tickets) {
        boolean changed=false;
        if (!"/spaces/exo_tribe_feedback".equals(ticket.getSpace().getGroupId())) {
          List<LogEntity> logs =logDAO.getLogsByTicketAndStatus(ticket.getId(), status);
          if (logs.size()>0){
            ticket.setTimeToResolution(CSUtils.getDiffBetweenTwoDates(ticket.getCreationDate(),logs.get(0).getWhen()));
            changed=true;
          }


          List<String> cStatus = Arrays.asList("considered");
          logs =logDAO.getLogsByTicketAndStatus(ticket.getId(), cStatus);
          if (logs.size()>0){
            ticket.setTimeToFirstResponse(CSUtils.getDiffBetweenTwoDates(ticket.getCreationDate(),logs.get(0).getWhen()));
            changed=true;
          }
          if(changed){
            topicDAO.update(ticket);
          }
          LOG.info("Ticket ");
        }
      }*/
      return Response.ok("Tickets Updated").build();
    } catch (Exception e) {
      LOG.error(e);
      return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity("An internal error has occured").build();
    }
  }

  @POST
  @Path("cstickets/addopenlog")
  @RolesAllowed("administrators")
  @Consumes({MediaType.APPLICATION_JSON})
  public Response addOpenLog(@Context HttpServletRequest request,
                           @Context UriInfo uriInfo) throws Exception {

    try {
      LogDAO logDAO  = getService(LogDAO.class, null);

      List<LogEntity> logs = logDAO.getByType(LogType.TICKET_CREATED,0,0);

      for (LogEntity log:logs){

          LogEntity logEntity = new LogEntity(LogType.TICKET_STATUS_CHANGED, log.getUserID(), log.getWhen(), "", "open", log.getTopic(),null,null,log.getTopic().getSpace().getGroupId());
          logDAO.create(logEntity);


      }

      LOG.info("End add open status logs");
      return Response.ok("Tickets Updated").build();
    } catch (Exception e) {
      LOG.error(e);
      return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity("An internal error has occured").build();
    }
  }


  @POST
  @Path("cstickets/updatelogstimes")
  @RolesAllowed("administrators")
  @Consumes({MediaType.APPLICATION_JSON})
  public Response updateLogsTimes(@Context HttpServletRequest request,
                           @Context UriInfo uriInfo) throws Exception {

    try {
      TopicDAO topicDAO = getService(TopicDAO.class, null);
      LogDAO logDAO  = getService(LogDAO.class, null);

      List<LogEntity> logs = logDAO.getByType(LogType.TIME_TO_RESOLUTION_ADDED,0,0);
      for (LogEntity log:logs) {
        if (log.getTimeToResolution() == null) {
          TopicEntity topic = topicDAO.find(log.getTopic().getId());
          if (topic != null) {
            log.setTimeToResolution(topic.getTimeToResolution());
            logDAO.update(log);

            LOG.info("log for " + topic.getTitle() + " TimeToResolution set");
          }
        }
      }

      logs = logDAO.getByType(LogType.TIME_TO_FIRST_RESPONSE_ADDED,0,0);
      for (LogEntity log:logs){
        if(log.getTimeToFirstResponse()==null) {
          TopicEntity topic = topicDAO.find(log.getTopic().getId());
          if (topic != null) {
            log.setTimeToFirstResponse(topic.getTimeToFirstResponse());
            logDAO.update(log);

            LOG.info("log for " + topic.getTitle() + " TimeToFirstResponse set");
          }
        }
      }

      return Response.ok("Logs Updated").build();
    } catch (Exception e) {
      LOG.error(e);
      return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity("An internal error has occured").build();
    }
  }


  private String buildJsonArrayOfUpdates(List<LogEntity> updatesList) {
    JSONArray ticketsJSON = new JSONArray();
    Map<TopicEntity,List<LogEntity>> updatesMap = new HashMap<>();
    for(LogEntity logEntity : updatesList) {
      updatesMap.computeIfAbsent(logEntity.getTopic(), k -> new ArrayList<>()).add(logEntity);
    }
    updatesMap.forEach((k,v)-> {
              JSONObject ticketJSON = new JSONObject();
              try {
                ticketJSON.put("ticket", buildJsonObject(k));
                for (LogEntity entity : v) {
                  JSONObject update = new JSONObject();
                  update.put("id", entity.getTopic().getId());
                  update.put("topicTitle", entity.getTopic().getTitle());
                  update.put("updater", entity.getUserID());
                  update.put("updateType", entity.getType().name());
                  update.put("oldValue", entity.getOriginalValue());
                  update.put("newValue", entity.getNewValue());
                  update.put("when", sdf.format(entity.getWhen().getTime()));
                  update.put("timeToFirstResponse", entity.getTimeToFirstResponse());
                  update.put("timeToResolution", entity.getTimeToResolution());
                  update.put("spaceId", entity.getSpaceId());
                  ticketJSON.append("updates",update);
                }
                ticketsJSON.put(ticketJSON);
              }catch(Exception e) {
                LOG.error("A problem when building JSON for LogEntity {}", k.getId(), e);
              }
            }
    );
    return ticketsJSON.toString();
  }


  private JSONObject buildJsonObject(TopicEntity topicEntity) {
    JSONObject ticket = new JSONObject();
    try {
      ticket.put("id", topicEntity.getId());
      ticket.put("assignee", topicEntity.getAssignee());
      ticket.put("environmentName", topicEntity.getEnvironment() != null ? topicEntity.getEnvironment().getName():"");
      ticket.put("infoType", topicEntity.getInfoType() != null ? topicEntity.getInfoType().name() : "");
      ticket.put("link", topicEntity.getLink());
      ticket.put("severity", topicEntity.getSeverity() != null ? topicEntity.getSeverity().name() : "");
      ticket.put("spaceGroupId", topicEntity.getSpace().getGroupId());
      ticket.put("title", topicEntity.getTitle());
      ticket.put("type", topicEntity.getType() != null ? topicEntity.getType().name() : "");
      ticket.put("status", topicEntity.getStatus());
      ticket.put("updateDate", sdf.format(topicEntity.getUpdateDate().getTime()));
    } catch (JSONException jsonException) {
      LOG.error("A problem when building JSON for ticket {}", topicEntity.getId());
    }
    return ticket;
  }

  private String buildJsonArray(List<TopicEntity> ticketDTOS) {
    JSONArray array = new JSONArray();
    for (TopicEntity ticketDTO : ticketDTOS) {
      JSONObject ticket = buildJsonObject(ticketDTO);
      array.put(ticket);
    }
    return array.toString();
  }

  private void convertChildApplicationPreferences(Space space, SiteKey siteKey, Node newNode, UserPortalConfigService portalConfigService) throws Exception {
    PageKey pageKey = new PageKey(siteKey, newNode.getPageId().getPageName());
    Page newPage = CSUtils.getPage(portalConfigService.getDataStorage(), portalConfigService.getPageService(), pageKey);
    ArrayList<ModelObject> modelObjects = newPage.getChildren();
    SpacePageUtils.convert(modelObjects, space.getGroupId());
  }


}
