package org.exoplatform.cs.service;

import java.lang.reflect.Method;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
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.CacheControl;
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 javax.ws.rs.ext.RuntimeDelegate;
import org.exoplatform.container.ExoContainer;
import org.exoplatform.container.ExoContainerContext;
import org.exoplatform.container.PortalContainer;
import org.exoplatform.container.RootContainer;
import org.exoplatform.container.component.RequestLifeCycle;
import org.exoplatform.container.configuration.ConfigurationManager;
import org.exoplatform.container.xml.InitParams;
import org.exoplatform.cs.dao.LogDAO;
import org.exoplatform.cs.dao.TopicDAO;
import org.exoplatform.cs.dto.LogType;
import org.exoplatform.cs.dto.Owner;
import org.exoplatform.cs.dto.SpaceDTO;
import org.exoplatform.cs.dto.TicketDTO;
import org.exoplatform.cs.entity.LogEntity;
import org.exoplatform.cs.entity.TopicEntity;
import org.exoplatform.cs.service.tickets.Flow;
import org.exoplatform.cs.service.tickets.FlowState;
import org.exoplatform.cs.service.tickets.TicketStatusFlowService;
import org.exoplatform.cs.service.util.CSUtils;
import org.exoplatform.cs.service.util.ForumUtils;
import org.exoplatform.cs.service.util.SpacePageUtils;
import org.exoplatform.forum.service.Category;
import org.exoplatform.forum.service.Forum;
import org.exoplatform.forum.service.ForumService;
import org.exoplatform.forum.service.Topic;
import org.exoplatform.portal.config.*;
import org.exoplatform.portal.config.model.ModelObject;
import org.exoplatform.portal.config.model.Page;
import org.exoplatform.portal.config.model.PortalConfig;
import org.exoplatform.portal.mop.SiteKey;
import org.exoplatform.portal.mop.description.DescriptionService;
import org.exoplatform.portal.mop.management.operations.page.PageUtils;
import org.exoplatform.portal.mop.navigation.NavigationContext;
import org.exoplatform.portal.mop.navigation.NavigationService;
import org.exoplatform.portal.mop.page.PageKey;
import org.exoplatform.portal.mop.page.PageService;
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.SpaceApplicationConfigPlugin;
import org.exoplatform.social.core.space.impl.SpaceServiceImpl;
import org.exoplatform.social.core.space.model.Space;
import org.exoplatform.social.core.space.spi.SpaceApplicationHandler;
import org.exoplatform.social.core.space.spi.SpaceService;
import org.gatein.api.ApiException;
import org.gatein.api.Portal;
import org.gatein.api.PortalRequest;
import org.gatein.api.Util;
import org.gatein.api.navigation.*;
import org.gatein.api.security.Group;
import org.gatein.api.site.Site;
import org.gatein.api.site.SiteId;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.exoplatform.portal.config.UserPortalConfigService;
import org.exoplatform.portal.mop.page.PageService;
import org.exoplatform.portal.config.UserPortalConfigService;

/**
 * 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();
  }

  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()));
            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();
    }

  /**
   * 
   * @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")){
            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("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("/spaces/exo_tribe_feedback".equals(cspace.getGroupId())) {
          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);
  }

}



  @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 && !"/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());
            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());
            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 && !"/spaces/exo_tribe_feedback".equals(topic.getSpace().getGroupId())){
          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);
          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();
    }
  }



  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 = PageUtils.getPage(portalConfigService.getDataStorage(), portalConfigService.getPageService(), pageKey);
    ArrayList<ModelObject> modelObjects = newPage.getChildren();
    SpacePageUtils.convert(modelObjects, space.getGroupId());
  }


}
