/**
 * This file is part of the Meeds project (https://meeds.io/).
 *
 * Copyright (C) 2020 - 2025 Meeds Association contact@meeds.io
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 */
package org.exoplatform.task.rest;

import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.TimeZone;

import javax.annotation.security.RolesAllowed;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;

import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.exoplatform.task.model.TaskSearchFilter;
import org.json.JSONArray;
import org.json.JSONObject;

import org.exoplatform.commons.utils.ListAccess;
import org.exoplatform.services.log.ExoLogger;
import org.exoplatform.services.log.Log;
import org.exoplatform.services.rest.resource.ResourceContainer;
import org.exoplatform.services.security.ConversationState;
import org.exoplatform.services.security.Identity;
import org.exoplatform.social.core.space.model.Space;
import org.exoplatform.social.core.space.spi.SpaceService;
import org.exoplatform.task.dao.TaskQuery;
import org.exoplatform.task.dto.ChangeLogEntry;
import org.exoplatform.task.dto.CommentDto;
import org.exoplatform.task.dto.LabelDto;
import org.exoplatform.task.dto.ProjectDto;
import org.exoplatform.task.dto.StatusDto;
import org.exoplatform.task.dto.TaskDto;
import org.exoplatform.task.dto.TasksList;
import org.exoplatform.task.model.GroupKey;
import org.exoplatform.task.model.User;
import org.exoplatform.task.rest.model.CommentEntity;
import org.exoplatform.task.rest.model.FiltreTaskList;
import org.exoplatform.task.rest.model.PaginatedTaskList;
import org.exoplatform.task.rest.model.SpaceEntity;
import org.exoplatform.task.rest.model.TaskEntity;
import org.exoplatform.task.rest.model.ViewState;
import org.exoplatform.task.service.CommentService;
import org.exoplatform.task.service.LabelService;
import org.exoplatform.task.service.ProjectService;
import org.exoplatform.task.service.StatusService;
import org.exoplatform.task.service.TaskService;
import org.exoplatform.task.service.UserService;
import org.exoplatform.task.util.CommentUtil;
import org.exoplatform.task.util.TaskUtil;
import org.exoplatform.task.util.UserUtil;

import io.meeds.social.html.model.HtmlTransformerContext;
import io.meeds.social.html.utils.HtmlUtils;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.parameters.RequestBody;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.tags.Tag;

@Path("/tasks")
@Tag(name = "/tasks", description = "Managing tasks")
@RolesAllowed("users")
public class TaskRestService implements ResourceContainer {

  private static final int DEFAULT_LIMIT = 20;

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

  private TaskService      taskService;

  private CommentService   commentService;

  private ProjectService   projectService;

  private StatusService    statusService;

  private UserService      userService;

  private SpaceService     spaceService;

  private LabelService     labelService;

  private static final String PERCENT_ENCODED_REGEX = "%(?![0-9a-fA-F]{2})";

  public TaskRestService(TaskService taskService,
                         CommentService commentService,
                         ProjectService projectService,
                         StatusService statusService,
                         UserService userService,
                         SpaceService spaceService,
                         LabelService labelService) {
    this.taskService = taskService;
    this.commentService = commentService;
    this.projectService = projectService;
    this.statusService = statusService;
    this.userService = userService;
    this.spaceService = spaceService;
    this.labelService = labelService;
  }


  private enum TaskType {
    ALL, INCOMING, OVERDUE, WATCHED, COLLABORATED, ASSIGNED
  }



  @GET
  @Path("{id}")
  @Produces(MediaType.APPLICATION_JSON)
  @RolesAllowed("users")
  @Operation(summary = "Get task by id", method = "GET", description = "This get the task if the authenticated user has permissions to view the objects linked to this task.")
  @ApiResponses(value = {
          @ApiResponse(responseCode = "200", description = "Request fulfilled"),
          @ApiResponse(responseCode = "400", description = "Invalid query input"),
          @ApiResponse(responseCode = "403", description = "Unauthorized operation"),
          @ApiResponse(responseCode = "404", description = "Resource not found") })
  public Response getTaskById(@Parameter(description = "Task id", required = true) @PathParam("id") long id) {
    try {
    TaskDto task = taskService.getTask(id);
    if (task == null) {
      return Response.status(Response.Status.NOT_FOUND).build();
    }
    if (!TaskUtil.hasEditPermission(taskService,task)) {
      return Response.status(Response.Status.FORBIDDEN).build();
    }
    transformHtml(task, ConversationState.getCurrent().getIdentity());
    return Response.ok(task).build();
    } catch (Exception e) {
      LOG.error("Can't get Task By Id {}", id, e);
      return Response.serverError().entity(e.getMessage()).build();
    }
  }


  @GET
  @RolesAllowed("users")
  @Produces(MediaType.APPLICATION_JSON)
  @Operation(summary = "Gets uncompleted tasks of the authenticated user", method = "GET", description = "This returns uncompleted tasks of the authenticated user in the following cases: <br/><ul><li>The authenticated is the creator of the task</li><li>The authenticated is the assignee of the task</li><li>The authenticated is a coworker of the task</li></ul>")
  @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "Request fulfilled") })
  public Response getTasks(@Parameter(description = "Type of task to get (all, incoming, overdue)") @QueryParam("status") String status,
                           @Parameter(description = "Search term") @QueryParam("q") String query,
                           @Parameter(description = "Space id") @QueryParam("spaceId") List<Long> spaceIds,
                           @Parameter(description = "Field to sort by", required = false) @QueryParam("sortField") String sortField,
                           @Parameter(description = "Sort order (asc or desc)") @QueryParam("sortDirection") String sortDirection,
                           @Parameter(description = "Offset") @Schema(defaultValue = "0") @QueryParam("offset") int offset,
                           @Parameter(description = "Limit") @Schema(defaultValue = "20") @QueryParam("limit") int limit,
                           @Parameter(description = "Returning the number of tasks or not") @Schema(defaultValue = "false") @QueryParam("returnSize") boolean returnSize,
                           @Parameter(description = "Returning All Details")
                           @Schema(defaultValue = "false")
                           @QueryParam("returnDetails")
                           boolean returnDetails) {

    try {
      Identity currentId = ConversationState.getCurrent().getIdentity();
      String currentUser = currentId.getUserId();
      List<String> memberships = new LinkedList<>();
      memberships.addAll(UserUtil.getMemberships(currentId));

      long tasksSize;
      List<TaskDto> tasks = null;
      if (StringUtils.isBlank(query)) {
        TaskType taskType;
        try {
          taskType = TaskType.valueOf(status.toUpperCase());
        } catch (Exception e) {
          taskType = TaskType.ALL;
        }
        if (limit <= 0) {
          limit = DEFAULT_LIMIT;
        }
        switch (taskType) {
        case INCOMING: {
          tasks = taskService.getIncomingTasks(currentUser, offset, limit);
          tasksSize = taskService.countIncomingTasks(currentUser);
          break;
        }
        case OVERDUE: {
          tasks = taskService.getOverdueTasks(currentUser, limit);
          tasksSize = taskService.countOverdueTasks(currentUser);
          break;
        }
        case WATCHED: {
          tasks = taskService.getWatchedTasks(currentUser, limit);
          tasksSize = taskService.countWatchedTasks(currentUser);
          break;
        }
        case COLLABORATED: {
          tasks = taskService.getCollaboratedTasks(currentUser, limit);
          tasksSize = taskService.countCollaboratedTasks(currentUser);
          break;
        }
        case ASSIGNED: {
          tasks = taskService.getAssignedTasks(currentUser, limit);
          tasksSize = taskService.countAssignedTasks(currentUser);
          break;
        }
        default: {
          tasks = taskService.getUncompletedTasks(currentUser, limit);
          tasksSize = taskService.countUncompletedTasks(currentUser);
        }
        }
      } else {
        memberships = filterMembershipsBySpaceIds(spaceIds, memberships);
        tasks = taskService.findTasks(new TaskSearchFilter(currentUser, query, memberships, sortField, sortDirection, offset, limit));
        tasksSize = taskService.countTasks(currentUser, query, memberships);
      }
      tasks.forEach(t -> transformHtml(t, currentId));
      return Response.ok(new PaginatedTaskList(tasks.stream()
                                                    .map(task -> getTaskDetails(task, currentId))
                                                    .toList(),
                                               tasksSize))
                     .build();
    } catch (Exception e) {
      LOG.error("Can't Gets uncompleted tasks of the authenticated user", e);
      return Response.serverError().entity(e.getMessage()).build();
    }
  }


  @GET
  @RolesAllowed("users")
  @Path("filter")
  @Produces(MediaType.APPLICATION_JSON)
  @Operation(summary = "Gets  a specific task", method = "GET", description = "This returns  a specific task")
  @ApiResponses(value = {
          @ApiResponse(responseCode = "200", description = "Request ful"),
          @ApiResponse(responseCode = "401", description = "Unauthorized operation"),
          @ApiResponse(responseCode = "404", description = "Resource not found") })
  public Response filterTasks(@Parameter(description = "Type of task to get (all, incoming, overdue)") @QueryParam("statusDto") String status,
                              @Parameter(description = "projectId term") @Schema(defaultValue = "-2") @QueryParam("projectId") long projectId,
                              @Parameter(description = "Search term") @QueryParam("query") String query,
                              @Parameter(description = "dueCategory term") @QueryParam("dueCategory") String dueCategory,
                              @Parameter(description = "priority term") @QueryParam("priority") String priority,
                              @Parameter(description = "assignee term") @QueryParam("assignee") String assignee,
                              @Parameter(description = "coworker term") @QueryParam("coworker") String coworker,
                              @Parameter(description = "watchers term") @QueryParam("watcher") String watcher,
                              @Parameter(description = "showCompletedTasks term") @Schema(defaultValue = "false") @QueryParam("showCompletedTasks") boolean showCompletedTasks,
                              @Parameter(description = "statusId term") @QueryParam("statusId") String statusId,
                              @Parameter(description = "space_group_id term") @QueryParam("space_group_id") String spaceGroupId,
                              @Parameter(description = "groupBy term") @QueryParam("groupBy") String groupBy,
                              @Parameter(description = "orderBy term") @QueryParam("orderBy") String orderBy,
                              @Parameter(description = "dueDate term") @QueryParam("dueDate") String dueDate,
                              @Parameter(description = "labelId term") @QueryParam("labelId") Long labelId,
                              @Parameter(description = "filterLabelIds term") @QueryParam("filterLabelIds") String filterLabelIds,
                              @Parameter(description = "Offset") @Schema(defaultValue = "0") @QueryParam("offset") int offset,
                              @Parameter(description = "Limit") @Schema(defaultValue = "20") @QueryParam("limit") int limit,
                              @Parameter(description = "Returning the number of tasks or not") @Schema(defaultValue = "false") @QueryParam("returnSize") boolean returnSize,
                              @Parameter(description = "Returning All Details")
                              @Schema(defaultValue = "false")
                              @QueryParam("returnDetails")
                              boolean returnDetails) {

    try {
      String listId = ViewState.buildId(projectId, labelId, dueCategory);

      Identity currIdentity = ConversationState.getCurrent().getIdentity();

      if (assignee != null && assignee.equals("ME")) {
        assignee = currIdentity.getUserId();
      }

      if (coworker != null && coworker.equals("ME")) {
        coworker = currIdentity.getUserId();
      }

      if (watcher != null && watcher.equals("ME")) {
        watcher = currIdentity.getUserId();
      }

      Long statusIdLong = null;
      if (org.apache.commons.lang3.StringUtils.isNotBlank(statusId)) {
        StatusDto statusDto = statusService.getStatus(Long.parseLong(statusId));
        if (statusDto == null || !statusDto.getProject().canView(currIdentity)) {
          return Response.status(Response.Status.NOT_FOUND).build();
        }
        statusIdLong = statusDto.getId();
      }
      ViewState.Filter filter = new ViewState.Filter(listId);
      filter.updateFilterData(filterLabelIds,
                              statusId,
                              dueDate,
                              priority,
                              assignee,
                              coworker,
                              watcher,
                              showCompletedTasks,
                              query);

      ProjectDto project = null;
      boolean noProjPermission = false;
      boolean advanceSearch = true;
      if (projectId > 0) {
        project = projectService.getProject(projectId);
        if (!project.canView(currIdentity)) {
          if (advanceSearch) {
            noProjPermission = true;
          } else {
            return Response.status(Response.Status.NOT_FOUND).build();
          }
        }
      }
      boolean noLblPermission = false;
      if (labelId != null && labelId > 0) {
        LabelDto label = labelService.getLabel(labelId);
        if (!label.getUsername().equals(currIdentity.getUserId())) {
          if (advanceSearch) {
            noLblPermission = true;
          } else {
            return Response.status(Response.Status.NOT_FOUND).build();
          }
        }
      }
      String currentUser = currIdentity.getUserId();
      TimeZone userTimezone = userService.getUserTimezone(currentUser);

      if (currentUser == null || currentUser.isEmpty()) {
        return Response.status(Response.Status.NOT_FOUND).build();
      }

      TasksList tasks = taskService.filterTasks(query,
                                                projectId,
                                                filter.getKeyword(),
                                                filter.getLabel(),
                                                filter.getDue(),
                                                filter.getPriority(),
                                                filter.getAssignees(),
                                                filter.getCoworkers(),
                                                filter.getWatchers(),
                                                labelId,
                                                statusIdLong,
                                                currIdentity,
                                                dueCategory,
                                                spaceGroupId,
                                                userTimezone,
                                                filter.isShowCompleted(),
                                                advanceSearch,
                                                noProjPermission,
                                                noLblPermission,
                                                orderBy,
                                                groupBy,
                                                offset,
                                                limit);
      List<TaskDto> taskList = tasks.getListTasks();

      if (StringUtils.isNotBlank(groupBy)
          && !TaskUtil.DUEDATE.equalsIgnoreCase(groupBy)
          && !TaskUtil.NONE.equalsIgnoreCase(groupBy)) {
        Map<GroupKey, List<TaskEntity>> groupTasks = TaskUtil.groupTasks(taskList.stream()
                                                                                 .map(task -> getTaskDetails(task, currIdentity))
                                                                                 .toList(),
                                                                         groupBy,
                                                                         currIdentity,
                                                                         userTimezone,
                                                                         labelService,
                                                                         userService);
        return Response.ok(new FiltreTaskList(groupTasks)).build();
      }
      return Response.ok(new PaginatedTaskList(taskList.stream().map(task -> getTaskDetails(task, currIdentity)).toList(),
                                               tasks.getTasksSize()))
                     .build();
    } catch (Exception e) {
      LOG.error("Can't filter Tasks", e);
      return Response.serverError().entity(e.getMessage()).build();
    }
  }

  @GET
  @Path("project/{id}")
  @RolesAllowed("users")
  @Produces(MediaType.APPLICATION_JSON)
  @Operation(summary = "Gets tasks by projectIdr", method = "GET", description = "This returns list of tasks by project")
  @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "Request fulfilled") })
  public Response getTasksByProjectId(@Parameter(description = "Id", required = true) @Schema(defaultValue = "0") @PathParam("id") Long id,
                                      @Parameter(description = "Offset") @Schema(defaultValue = "0") @QueryParam("offset") int offset,
                                      @Parameter(description = "Limit") @Schema(defaultValue = "0") @QueryParam("limit") int limit,
                                      @Parameter(description = "Returning the Completed tasks") @Schema(defaultValue = "false") @QueryParam("completed") boolean completed,
                                      @Parameter(description = "Returning the number of tasks or not") @Schema(defaultValue = "false") @QueryParam("returnSize") boolean returnSize,
                                      @Parameter(description = "Returning All Details")
                                      @Schema(defaultValue = "false")
                                      @QueryParam("returnDetails")
                                      boolean returnDetails) {
    try {
      Identity currentUser = ConversationState.getCurrent().getIdentity();
      ProjectDto project = projectService.getProject(id);
      if (project == null || !project.canView(ConversationState.getCurrent().getIdentity())) {
        return Response.status(Response.Status.UNAUTHORIZED).build();
      }
      TaskQuery taskQuery = new TaskQuery();
      List<Long> allProjectIds = new ArrayList<>();
      allProjectIds.add(id);
      if (limit == 0) {
        limit = -1;
      }
      taskQuery.setProjectIds(allProjectIds);
      taskQuery.setCompleted(completed);
      List<TaskDto> tasks = taskService.findTasks(taskQuery, limit, offset);
      if (returnSize) {
        tasks.forEach(t -> transformHtml(t, currentUser));
        JSONObject tasksSizeJsonObject = new JSONObject();
        if (returnDetails) {
          tasksSizeJsonObject.put("tasks",
                                  tasks.stream()
                                       .map(task -> getTaskDetails(task, currentUser))
                                       .toList());
        } else {
          tasksSizeJsonObject.put("tasks", tasks);
        }
        return Response.ok(tasksSizeJsonObject).build();
      } else if (returnDetails) {
        return Response.ok(tasks.stream().map(task -> getTaskDetails(task, currentUser)).toList())
                       .build();
      } else {
        tasks.forEach(t -> transformHtml(t, currentUser));
        return Response.ok(tasks).build();
      }
    } catch (Exception e) {
      LOG.error("Can't get Tasks By ProjectId {}", id, e);
      return Response.serverError().entity(e.getMessage()).build();
    }
  }

  @POST
  @Produces(MediaType.APPLICATION_JSON)
  @RolesAllowed("users")
  @Operation(summary = "Add a new task", method = "POST", description = "This adds a new task.")
  @ApiResponses(value = {
          @ApiResponse(responseCode = "200", description = "Request fulfilled"),
          @ApiResponse(responseCode = "400", description = "Invalid query input"),
          @ApiResponse(responseCode = "403", description = "Unauthorized operation"),
          @ApiResponse(responseCode = "404", description = "Resource not found") })
  public Response addTask(@RequestBody(description = "task object to be updated", required = true) TaskDto task) {

    if (task == null) {
      return Response.status(Response.Status.BAD_REQUEST).build();
    }

    try {
      Identity identity = ConversationState.getCurrent().getIdentity();
      task.setCreatedBy(identity.getUserId());
      task.setCreatedTime(new Date());

      Long projectId = null;

      if (task.getStatus() != null && task.getStatus().getProject() != null) {
        projectId = task.getStatus().getProject().getId();
      }

      if (projectId != null) {
        ProjectDto projectDto = projectService.getProject(projectId);
        if (projectDto == null) {
          LOG.warn("Task's project not found");
          return Response.status(Response.Status.BAD_REQUEST).build();
        }

        if (!projectDto.canView(identity)) {
          LOG.warn(
                  "User {} attempts to create a task under a non authorized project {}",
                  identity.getUserId(),
                  projectDto.getName()
          );
          return Response.status(Response.Status.UNAUTHORIZED).build();
        }
      }

      if (projectId == null) {
        if (task.getAssignee() == null) {
          task.setAssignee(identity.getUserId());
        }
        task.setStatus(null);
      }

      if (projectId != null && task.getStatus() != null
              && (task.getStatus().getId() == null || task.getStatus().getId() == 0)) {
        task.setStatus(statusService.getDefaultStatus(projectId));
      }

      task = taskService.createTask(task);
      transformHtml(task, ConversationState.getCurrent().getIdentity());
      return Response.ok(task).build();
    } catch (Exception e) {
      LOG.error("Can't add Task", e);
      return Response.serverError().entity(e.getMessage()).build();
    }
  }

  @POST
  @Path("clone/{taskId}")
  @Produces(MediaType.APPLICATION_JSON)
  @RolesAllowed("users")
  @Operation(summary = "Clones a specific task by id", method = "POST", description = "This clones the task if the authenticated user has permissions to edit the objects linked to this task.")
  @ApiResponses(value = {
          @ApiResponse(responseCode = "200", description = "Request fulfilled"),
          @ApiResponse(responseCode = "400", description = "Invalid query input"), 
          @ApiResponse(responseCode = "403", description = "Unauthorized operation"),
          @ApiResponse(responseCode = "404", description = "Resource not found")})
  public Response cloneTask(@Parameter(description = "Task id", required = true) @PathParam("taskId") long taskId) {
    try {
    TaskDto task = taskService.getTask(taskId);
    if (task == null) {
      return Response.status(Response.Status.BAD_REQUEST).build();
    }
    if (!TaskUtil.hasEditPermission(taskService,task)) {
      return Response.status(Response.Status.FORBIDDEN).build();
    }
    TaskDto newTask = taskService.cloneTask(taskId);
    if (newTask == null) {
      return Response.status(Response.Status.NOT_FOUND).build();
    }
    transformHtml(newTask, ConversationState.getCurrent().getIdentity());
    return Response.ok(newTask).build();
        } catch (Exception e) {
        LOG.error("Can't clone Task {}", taskId, e);
        return Response.serverError().entity(e.getMessage()).build();
        }
  }

  @PUT
  @Path("{id}")
  @Produces(MediaType.APPLICATION_JSON)
  @RolesAllowed("users")
  @Operation(summary = "Updates a specific task by id", method = "PUT", description = "This updates the task if the authenticated user has permissions to view the objects linked to this task.")
  @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "Request fulfilled"),
          @ApiResponse(responseCode = "400", description = "Invalid query input"), 
          @ApiResponse(responseCode = "403", description = "Unauthorized operation"),
          @ApiResponse(responseCode = "404", description = "Resource not found") })
  public Response updateTaskById(@Parameter(description = "Task id", required = true) @PathParam("id") long id,
                                 @RequestBody(description = "task object to be updated", required = true) TaskDto updatedTask) {
    if (updatedTask == null) {
      return Response.status(Response.Status.BAD_REQUEST).build();
    }
    try {
    TaskDto task = taskService.getTask(id);
    if (task == null) {
      return Response.status(Response.Status.NOT_FOUND).build();
    }
    if (task.getStatus() == null) {
      task.setStatus(updatedTask.getStatus());
    }
    if (!TaskUtil.hasEditPermission(taskService, task)) {
      return Response.status(Response.Status.FORBIDDEN).build();
    }
    task = taskService.updateTask(updatedTask);
    transformHtml(task, ConversationState.getCurrent().getIdentity());
    return Response.ok(task).build();
        } catch (Exception e) {
        LOG.error("Can't update Task {}", id, e);
        return Response.serverError().entity(e.getMessage()).build();
        }
  }


  @DELETE
  @Path("{id}")
  @Produces(MediaType.APPLICATION_JSON)
  @RolesAllowed("users")
  @Operation(summary = "Updates a specific task by id", method = "PUT", description = "This updates the task if the authenticated user has permissions to view the objects linked to this task.")
  @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "Request fulfilled"),
          @ApiResponse(responseCode = "400", description = "Invalid query input"), 
          @ApiResponse(responseCode = "403", description = "Unauthorized operation"),
          @ApiResponse(responseCode = "404", description = "Resource not found") })
  public Response deleteTaskById(@Parameter(description = "Task id", required = true) @PathParam("id") long id) {
    try {
    TaskDto task = taskService.getTask(id);
    if (task == null) {
      return Response.status(Response.Status.NOT_FOUND).build();
    }
    if (!TaskUtil.hasEditPermission(taskService,task)) {
      return Response.status(Response.Status.FORBIDDEN).build();
    }
    taskService.removeTask(id);
    return Response.ok().build();
        } catch (Exception e) {
        LOG.error("Can't delete Task {}", id, e);
        return Response.serverError().entity(e.getMessage()).build();
        }
  }


  @GET
  @Path("labels")
  @RolesAllowed("users")
  @Produces(MediaType.APPLICATION_JSON)
  @Operation(summary = "Gets labels of the authenticated user", method = "GET", description = "This returns labels of the authenticated user")
  @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "Request fulfilled") })
  public Response getLabels() {
    try {
    String currentUser = ConversationState.getCurrent().getIdentity().getUserId();
    return Response.ok(labelService.findLabelsByUser(currentUser, 0, -1)).build();
        } catch (Exception e) {
        LOG.error("Can't get Labels", e);
        return Response.serverError().entity(e.getMessage()).build();
        }
  }
  @GET
  @Path("labels/project/{id}")
  @RolesAllowed("users")
  @Produces(MediaType.APPLICATION_JSON)
  @Operation(summary = "Gets labels of the given project", method = "GET", description = "This returns labels of the given project")
  @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "Request fulfilled") })
  public Response getLabelsByProjectId(@Parameter(description = "project id", required = true) @PathParam("id") long id) {
    try {
      Identity currentUser = ConversationState.getCurrent().getIdentity();
      ProjectDto project = projectService.getProject(id);
      if (project == null) {
        return Response.status(Response.Status.NOT_FOUND).build();
      }
      if (!project.canView(currentUser)) {
        return Response.status(Response.Status.FORBIDDEN).build();
      }
    return Response.ok(labelService.findLabelsByProject(id, currentUser,0, -1)).build();
        } catch (Exception e) {
        LOG.error("Can't get Labels", e);
        return Response.serverError().entity(e.getMessage()).build();
        }
  }

  @GET
  @Path("labels/{id}")
  @RolesAllowed("users")
  @Produces(MediaType.APPLICATION_JSON)
  @Operation(summary = "Gets labels of a specific task by id", method = "GET", description = "This returns labels of a specific task by id")
  @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "Request fulfilled"),
      @ApiResponse(responseCode = "500", description = "Internal server error") })
  public Response getLabelsByTaskId(@Parameter(description = "Task id", required = true) @PathParam("id") long id) {
    try {
    Identity currentUser = ConversationState.getCurrent().getIdentity();
      TaskDto task = taskService.getTask(id);
      if (task == null) {
        return Response.status(Response.Status.NOT_FOUND).build();
      }
    List<LabelDto> labels = new ArrayList<>();
    if(task.getStatus()!=null && task.getStatus().getProject()!=null){
      labels = labelService.findLabelsByTask(task, task.getStatus().getProject().getId(), currentUser,0, -1);
    }
    return Response.ok(labels).build();
        } catch (Exception e) {
        LOG.error("Can't get Labels By TaskId {}", id, e);
        return Response.serverError().entity(e.getMessage()).build();
        }
  }

  @POST
  @Path("labels/{id}")
  @RolesAllowed("users")
  @Operation(summary = "Adds a specific task by id to a label", method = "POST", description = "This adds a specific task by id to a label")
  @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "Request fulfilled"),
          @ApiResponse(responseCode = "400", description = "Invalid query input"), 
          @ApiResponse(responseCode = "403", description = "Unauthorized operation"),
          @ApiResponse(responseCode = "404", description = "Resource not found") })
  public Response addTaskToLabel(@RequestBody(description = "label", required = true) LabelDto addedLabel,
                                 @Parameter(description = "Task id", required = true) @PathParam("id") long id) {
    try {
    Identity currentUser = ConversationState.getCurrent().getIdentity();
    if (addedLabel == null) {
      return Response.status(Response.Status.BAD_REQUEST).build();
    }
    TaskDto task = taskService.getTask(id);
    if (task == null) {
      return Response.status(Response.Status.NOT_FOUND).build();
    }
    if (!TaskUtil.hasEditPermission(taskService,task)) {
      return Response.status(Response.Status.FORBIDDEN).build();
    }
      try {
        ProjectDto project = task.getStatus() == null || task.getStatus().getProject() == null ? null : projectService.getProject(task.getStatus().getProject().getId());
        if (project == null) {
          return Response.status(Response.Status.NOT_FOUND).build();
        }
        if (!project.canView(currentUser)) {
          return Response.status(Response.Status.FORBIDDEN).build();
        }
        addedLabel.setProject(project);
      }catch (Exception e) {
        return Response.status(Response.Status.BAD_REQUEST).build();
      }
    if (addedLabel.getId() == 0) {// Create a new label and add a task to it

      addedLabel.setUsername(currentUser.getUserId());
      LabelDto label = labelService.createLabel(addedLabel);
      labelService.addTaskToLabel(task, label.getId());
    } else {// Add a task to an existing label
      labelService.addTaskToLabel(task, addedLabel.getId());
    }
    return Response.ok(addedLabel).build();
        } catch (Exception e) {
        LOG.error("Can't add Task {} To Label", id, e);
        return Response.serverError().entity(e.getMessage()).build();
        }
  }

  @DELETE
  @Path("labels/{id}/{labelId}")
  @RolesAllowed("users")
  @Produces(MediaType.APPLICATION_JSON)
  @Operation(summary = "Deletes a specific task association to a specific label", method = "DELETE", description = "This deletes a specific task association to a specific label")
  @ApiResponses(value = { 
          @ApiResponse(responseCode = "200", description = "Request fulfilled"),
          @ApiResponse(responseCode = "403", description = "Unauthorized operation"), 
          @ApiResponse(responseCode = "404", description = "Resource not found") })
  public Response removeTaskFromLabel(@Parameter(description = "label id", required = true) @PathParam("labelId") long labelId,
                                      @Parameter(description = "Task id", required = true) @PathParam("id") long id) {
    try {
    TaskDto task = taskService.getTask(id);
    if (task == null) {
      return Response.status(Response.Status.NOT_FOUND).build();
    }
    LabelDto label = labelService.getLabel(labelId);
    if (label == null) {
      return Response.status(Response.Status.NOT_FOUND).build();
    }
    if (!TaskUtil.hasEditPermission(taskService,task)) {
      return Response.status(Response.Status.FORBIDDEN).build();
    }
    labelService.removeTaskFromLabel(task, labelId);
    return Response.ok().build();
        } catch (Exception e) {
        LOG.error("Can't remove Task {} From Label {}", id, labelId, e);
        return Response.serverError().entity(e.getMessage()).build();
        }
  }

  @POST
  @Path("labels")
  @RolesAllowed("users")
  @Operation(summary = "Adds a specific task by id to a label", method = "POST", description = "This adds a specific task by id to a label")
  @ApiResponses(value = { 
          @ApiResponse(responseCode = "200", description = "Request fulfilled"),
          @ApiResponse(responseCode = "400", description = "Invalid query input"), 
          @ApiResponse(responseCode = "403", description = "Unauthorized operation"),
          @ApiResponse(responseCode = "404", description = "Resource not found") })
  public Response addLabel(@RequestBody(description = "label object to create", required = true) LabelDto addedLabel) {
    try {
    Identity currentUser = ConversationState.getCurrent().getIdentity();
    if (addedLabel == null) {
      return Response.status(Response.Status.BAD_REQUEST).build();
    }

      try {
        ProjectDto project = projectService.getProject(addedLabel.getProject().getId());
        if (project == null) {
          return Response.status(Response.Status.NOT_FOUND).build();
        }
        if (!project.canEdit(currentUser)) {
          return Response.status(Response.Status.FORBIDDEN).build();
        }
      }catch (Exception e) {
        return Response.status(Response.Status.BAD_REQUEST).build();
      }
    if (addedLabel.getId() == 0) {// Create a new label and add a task to it
      addedLabel.setUsername(currentUser.getUserId());
      addedLabel = labelService.createLabel(addedLabel);
    }
    return Response.ok(addedLabel).build();
        } catch (Exception e) {
        LOG.error("Can't add  Label", e);
        return Response.serverError().entity(e.getMessage()).build();
        }
  }
  @PUT
  @Path("labels/{labelId}")
  @RolesAllowed("users")
  @Operation(summary = "Adds a specific task by id to a label", method = "POST", description = "This adds a specific task by id to a label")
  @ApiResponses(value = { 
          @ApiResponse(responseCode = "200", description = "Request fulfilled"),
          @ApiResponse(responseCode = "400", description = "Invalid query input"), 
          @ApiResponse(responseCode = "403", description = "Unauthorized operation"),
          @ApiResponse(responseCode = "404", description = "Resource not found") })
  public Response editLabel(@Parameter(description = "label id", required = true) @PathParam("labelId") long labelId,
                            @RequestBody(description = "label object to update", required = true) LabelDto label) {
    try {
    Identity currentUser = ConversationState.getCurrent().getIdentity();
    if (label == null) {
      return Response.status(Response.Status.BAD_REQUEST).build();
    }

      try {
        ProjectDto project = projectService.getProject(label.getProject().getId());
        if (project == null) {
          return Response.status(Response.Status.NOT_FOUND).build();
        }
        if (!project.canEdit(currentUser)) {
          return Response.status(Response.Status.FORBIDDEN).build();
        }
      }catch (Exception e) {
        return Response.status(Response.Status.BAD_REQUEST).build();
      }
    if (labelService.getLabel(labelId)==null) {
      return Response.status(Response.Status.NOT_FOUND).build();
    }
      label = labelService.updateLabel(label);
    return Response.ok(label).build();
        } catch (Exception e) {
        LOG.error("Can't add  Label", e);
        return Response.serverError().entity(e.getMessage()).build();
        }
  }

  @DELETE
  @Path("labels/{labelId}")
  @RolesAllowed("users")
  @Produces(MediaType.APPLICATION_JSON)
  @Operation(summary = "Deletes a specific  label", method = "DELETE", description = "This deletes a specific task association to a specific label")
  @ApiResponses(value = { 
          @ApiResponse(responseCode = "200", description = "Request fulfilled"),
          @ApiResponse(responseCode = "403", description = "Unauthorized operation"), 
          @ApiResponse(responseCode = "404", description = "Resource not found") })
  public Response removeLabel(@Parameter(description = "label id", required = true) @PathParam("labelId") long labelId) {
    try {
      Identity currentUser = ConversationState.getCurrent().getIdentity();
    LabelDto label = labelService.getLabel(labelId);
    if (label == null) {
      return Response.status(Response.Status.NOT_FOUND).build();
    }
      try {
        ProjectDto project = projectService.getProject(label.getProject().getId());
        if (project == null) {
          return Response.status(Response.Status.NOT_FOUND).build();
        }
        if (!project.canEdit(currentUser)) {
          return Response.status(Response.Status.FORBIDDEN).build();
        }
      }catch (Exception e) {
        return Response.status(Response.Status.BAD_REQUEST).build();
      }
    labelService.removeLabel(labelId);
    return Response.ok().build();
        } catch (Exception e) {
        LOG.error("Can't remove Label {}",  labelId, e);
        return Response.serverError().entity(e.getMessage()).build();
        }
  }

  @GET
  @Path("logs/{id}")
  @RolesAllowed("users")
  @Produces(MediaType.APPLICATION_JSON)
  @Operation(summary = "Gets a logs of a specific task", method = "GET", description = "This returns a logs of a specific task")
  @ApiResponses(value = { 
          @ApiResponse(responseCode = "200", description = "Request fulfilled"),
          @ApiResponse(responseCode = "401", description = "Unauthorized operation"), 
          @ApiResponse(responseCode = "404", description = "Resource not found") })
  public Response getTaskLogs(@Parameter(description = "Task id", required = true) @PathParam("id") long id,
                              @Parameter(description = "Offset") @Schema(defaultValue = "0") @QueryParam("offset") int offset,
                              @Parameter(description = "Limit") @Schema(defaultValue = "-1") @QueryParam("limit") int limit) {
    try {
    TaskDto task = taskService.getTask(id);
    if (task == null) {
      return Response.status(Response.Status.NOT_FOUND).build();
    }
    if (!TaskUtil.hasViewPermission(taskService,task)) {
      return Response.status(Response.Status.UNAUTHORIZED).build();
    }
    if (limit == 0) {
      limit = -1;
    }
    List<ChangeLogEntry> arr = taskService.getTaskLogs(id, offset, limit);
    if (arr == null) {
      return Response.ok(Collections.emptyList()).build();
    }
    List<ChangeLogEntry> logs = new LinkedList<>(arr);
    return Response.ok(logs).build();
        } catch (Exception e) {
        LOG.error("Can't get Task {} Logs", id, e);
        return Response.serverError().entity(e.getMessage()).build();
        }
  }

  @GET
  @Path("comments/{id}")
  @RolesAllowed("users")
  @Produces(MediaType.APPLICATION_JSON)
  @Operation(summary = "Gets a comment list of a specific task", method = "GET", description = "This returns a comment list of a specific task")
  @ApiResponses(value = { 
          @ApiResponse(responseCode = "200", description = "Request fulfilled"),
          @ApiResponse(responseCode = "403", description = "Unauthorized operation"), 
          @ApiResponse(responseCode = "404", description = "Resource not found") })
  public Response getTaskComments(@Parameter(description = "Task id", required = true) @PathParam("id") long id,
                                  @Parameter(description = "Offset") @Schema(defaultValue = "0") @QueryParam("offset") int offset,
                                  @Parameter(description = "Limit") @Schema(defaultValue = "-1") @QueryParam("limit") int limit) {
    try {
    TaskDto task = taskService.getTask(id);
    if (task == null) {
      return Response.status(Response.Status.NOT_FOUND).build();
    }
    if (!TaskUtil.hasViewPermission(taskService,task)) {
      return Response.status(Response.Status.UNAUTHORIZED).build();
    }
    if (limit == 0) {
      limit = -1;
    }
    Identity currentUserIdentity = ConversationState.getCurrent().getIdentity();
    String currentUser = currentUserIdentity.getUserId();
    List<CommentDto> comments = commentService.getCommentsWithSubs(id, offset, limit);
    List<CommentEntity> commentModelsList = new ArrayList<>();
    for (CommentDto comment : comments) {
      CommentEntity commentModel = addCommentModel(comment, commentModelsList, currentUserIdentity, TaskUtil.getUserLanguage(currentUser));
      if (comment.getSubComments()!=null&&!comment.getSubComments().isEmpty()) {
        List<CommentEntity> subCommentsModelsList = new ArrayList<>();
        for (CommentDto subComment :comment.getSubComments()) {
          addCommentModel(subComment, subCommentsModelsList, currentUserIdentity, TaskUtil.getUserLanguage(currentUser));
        }
        commentModel.setSubComments(subCommentsModelsList);
      }
    }
    Collections.reverse(commentModelsList);
    return Response.ok(commentModelsList).build();
        } catch (Exception e) {
        LOG.error("Can't get Task {} Comments", id, e);
        return Response.serverError().entity(e.getMessage()).build();
        }
  }

  @POST
  @Path("comments/{id}")
  @RolesAllowed("users")
  @Produces(MediaType.APPLICATION_JSON)
  @Operation(summary = "Adds comment to a specific task by id", method = "POST", description = "This Adds comment to a specific task by id")
  @ApiResponses(value = { 
          @ApiResponse(responseCode = "200", description = "Request fulfilled"),
          @ApiResponse(responseCode = "400", description = "Invalid query input"), 
          @ApiResponse(responseCode = "403", description = "Unauthorized operation"),
          @ApiResponse(responseCode = "404", description = "Resource not found") })
  public Response addTaskComment(@Parameter(description = "Comment text", required = false) String commentText,
                                 @Parameter(description = "Task id", required = true) @PathParam("id") long id) {
    try {
    Identity currentUserIdentity = ConversationState.getCurrent().getIdentity();
    String currentUser = currentUserIdentity.getUserId();
    TaskDto task = taskService.getTask(id);
    if (task == null) {
      return Response.status(Response.Status.NOT_FOUND).build();
    }
    if (!TaskUtil.hasEditPermission(taskService,task)) {
      return Response.status(Response.Status.FORBIDDEN).build();
    }
    commentText = commentText.replaceAll(PERCENT_ENCODED_REGEX, "%25");
    commentText = commentText.replaceAll("\\+", "%2b");
    commentText = URLDecoder.decode(commentText, "UTF-8");
    CommentDto addedComment = commentService.addComment(task, currentUser, commentText);
    if (addedComment != null) {
      addedComment = commentService.getComment(addedComment.getId());
      transformHtml(addedComment, currentUserIdentity);
    }
    CommentEntity commentEntity = new CommentEntity(addedComment, userService.loadUser(currentUser), CommentUtil.formatMention(commentText, TaskUtil.getUserLanguage(currentUser)));
    return Response.ok(commentEntity).build();
        } catch (Exception e) {
        LOG.error("Can't add Comment to Task {}", id, e);
        return Response.serverError().entity(e.getMessage()).build();
        }
  }

  @POST
  @Path("comments/{commentId}/{id}")
  @Produces(MediaType.APPLICATION_JSON)
  @RolesAllowed("users")
  @Operation(summary = "Adds a sub comment to a specific parent comment by id and a specific task by id", method = "POST", description = "This Adds sub comment to a parent comment in specific task by id")
  @ApiResponses(value = { 
          @ApiResponse(responseCode = "200", description = "Request fulfilled"),
          @ApiResponse(responseCode = "400", description = "Invalid query input"),
          @ApiResponse(responseCode = "403", description = "Unauthorized operation"),
          @ApiResponse(responseCode = "404", description = "Resource not found") })
  public Response addTaskSubComment(@Parameter(description = "Comment text", required = false) String commentText,
                                    @Parameter(description = "Comment id", required = true) @PathParam("commentId") long commentId,
                                    @Parameter(description = "Task id", required = true) @PathParam("id") long id) {
    try {
    Identity currentUserIdentity = ConversationState.getCurrent().getIdentity();
    String currentUser = currentUserIdentity.getUserId();
    TaskDto task = taskService.getTask(id);
    if (task == null) {
      return Response.status(Response.Status.NOT_FOUND).build();
    }
    if (!TaskUtil.hasEditPermission(taskService,task)) {
      return Response.status(Response.Status.FORBIDDEN).build();
    }

    commentText = commentText.replaceAll(PERCENT_ENCODED_REGEX, "%25");
    commentText = commentText.replaceAll("\\+", "%2b");
    commentText = URLDecoder.decode(commentText, "UTF-8");
    CommentDto addedComment = commentService.addComment(task, commentId, currentUser, commentText);
    if (addedComment != null) {
      addedComment = commentService.getComment(addedComment.getId());
      transformHtml(addedComment, currentUserIdentity);
    }
    CommentEntity commentEntity = new CommentEntity(addedComment, userService.loadUser(currentUser), CommentUtil.formatMention(commentText, TaskUtil.getUserLanguage(currentUser)));
    return Response.ok(commentEntity).build();
        } catch (Exception e) {
        LOG.error("Can't add SubComment to Task {}", id, e);
        return Response.serverError().entity(e.getMessage()).build();
        }
  }

  @DELETE
  @Path("comments/{commentId}")
  @RolesAllowed("users")
  @Operation(summary = "Deletes a specific task comment by id", method = "DELETE", description = "This deletes a specific task comment by id")
  @ApiResponses(value = {
          @ApiResponse(responseCode = "200", description = "Request fulfilled"),
          @ApiResponse(responseCode = "403", description = "Unauthorized operation"),
          @ApiResponse(responseCode = "404", description = "Resource not found") })
  public Response deleteComment(@Parameter(description = "Comment id", required = true) @PathParam("commentId") long commentId) {
    try {
    CommentDto comment = commentService.getComment(commentId);
    if (comment == null) {
      return Response.status(Response.Status.NOT_FOUND).build();
    }
    Identity currentIdentity = ConversationState.getCurrent().getIdentity();
    if (!TaskUtil.canDeleteComment(currentIdentity, comment)) {
      return Response.status(Response.Status.FORBIDDEN).build();
    }
    commentService.removeComment(commentId);
    transformHtml(comment, currentIdentity);
    return Response.ok(comment).build();
        } catch (Exception e) {
        LOG.error("Can't delete Comment {}", commentId, e);
        return Response.serverError().entity(e.getMessage()).build();
        }
  }

  @GET
  @Path("usersToMention/{query}")
  @RolesAllowed("users")
  @Produces(MediaType.APPLICATION_JSON)
  @Operation(summary = "Gets users to mention in comment", method = "GET", description = "This returns users to mention in comment")
  @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "Request fulfilled") })
  public Response findUsersToMention(@Parameter(description = "Query", required = true) @PathParam("query") String query) {
    try {
    ListAccess<User> list = userService.findUserByName(query, true);
    JSONArray usersJsonArray = new JSONArray();
    for (User user : list.load(0, UserUtil.SEARCH_LIMIT)) {
      JSONObject userJson = new JSONObject();
      String fullName = user.getDisplayName();
      if(taskService.isExternal(user.getUsername())){
        fullName += " " + "(" + TaskUtil.getResourceBundleLabel(new Locale(TaskUtil.getUserLanguage(user.getUsername())), "external.label.tag") + ")";
      }
      userJson.put("id", "@" + user.getUsername());
      userJson.put("name", fullName);
      userJson.put("avatar", user.getAvatar());
      userJson.put("type", "contact");
      usersJsonArray.put(userJson);
    }
    return Response.ok(usersJsonArray.toString()).build();
        } catch (Exception e) {
        LOG.error("Can't findUsersToMention", e);
        return Response.serverError().entity(e.getMessage()).build();
        }
  }

  @PUT
  @Path("updateCompleted/{idTask}")
  @Produces(MediaType.APPLICATION_JSON)
  @RolesAllowed("users")
  @Operation(summary = "Updates a specific task by id", method = "PUT", description = "This updates the task if the authenticated user has permissions to view the objects linked to this task.")
  @ApiResponses(value = {
          @ApiResponse(responseCode = "200", description = "Request fulfilled"),
          @ApiResponse(responseCode = "400", description = "Invalid query input"),
          @ApiResponse(responseCode = "403", description = "Unauthorized operation"),
          @ApiResponse(responseCode = "404", description = "Resource not found") })
  public Response updateCompleted(@Parameter(description = "Task id", required = true) @PathParam("idTask") long idTask,
                                  @Parameter(description = "isCompleted") @Schema(defaultValue = "false") @QueryParam("isCompleted") boolean isCompleted) {
    try {
    TaskDto task = taskService.getTask(idTask);
    if (!TaskUtil.hasEditPermission(taskService,task)) {
      return Response.status(Response.Status.FORBIDDEN).build();
    }
    task.setCompleted(isCompleted);
    taskService.updateTask(task);
    transformHtml(task, ConversationState.getCurrent().getIdentity());
    return Response.ok(task).build();
        } catch (Exception e) {
        LOG.error("Can't set task {} as Completed", idTask, e);
        return Response.serverError().entity(e.getMessage()).build();
        }
  }


  private CommentEntity addCommentModel(CommentDto comment, List<CommentEntity> commentModelsList, Identity currentUserIdentity, String lang) {

    User user = userService.loadUser(comment.getAuthor());
    StatusDto taskStatus = comment.getTask().getStatus();
    if (taskStatus != null) {
      comment.getTask().setStatus(taskStatus.clone());// To be checked
    }
    transformHtml(comment, currentUserIdentity);
    CommentEntity commentEntity = new CommentEntity(comment, user, CommentUtil.formatMention(comment.getComment(), lang));
    if (commentEntity.getSubComments() == null) {
      commentEntity.setSubComments(new ArrayList<>());
    }
    commentModelsList.add(commentEntity);
    return commentEntity;
  }


  private TaskEntity getTaskDetails(TaskDto task, Identity userIdentity) {

    long taskId = task.getId();
    int commentCount;
    try {
      commentCount = commentService.countComments(taskId);
    } catch (Exception e) {
      LOG.warn("Error retrieving task '{}' comments count", taskId, e);
      commentCount = 0;
    }
    TaskEntity taskEntity = new TaskEntity(task, commentCount);
    transformHtml(task, userIdentity);
    if (task.getStatus() != null && task.getStatus().getProject() != null) {
      List<LabelDto> labels = new ArrayList<>();
      try {
        labels = labelService.findLabelsByTask(task, task.getStatus().getProject().getId(), userIdentity,0, -1);
      } catch (Exception e) {
        LOG.warn("Error retrieving task '{}' labels", taskId, e);
      }
      SpaceEntity space = getProjectSpace(task.getStatus().getProject());
      taskEntity.setLabels(labels);
      taskEntity.setSpace(space);
    }
    return taskEntity;
  }


  private SpaceEntity getProjectSpace(ProjectDto project) {
    for (String permission : projectService.getManager(project.getId())) {
      int index = permission.indexOf(':');
      if (index > -1) {
        String groupId = permission.substring(index + 1);
        Space space = spaceService.getSpaceByGroupId(groupId);
        if(space!=null){
          return new SpaceEntity(space.getId(), space.getDisplayName(), space.getGroupId(), space.getUrl(), space.getPrettyName(), space.getAvatarUrl());
        }
      }
    }
    return null;
  }

  private void transformHtml(TaskDto task, Identity userIdentity) {
    if (task != null) {
      task.setDescription(HtmlUtils.transform(task.getDescription(), new HtmlTransformerContext(userIdentity, null)));
    }
  }

  private void transformHtml(CommentDto comment, Identity userIdentity) {
    if (comment != null) {
      comment.setComment(HtmlUtils.transform(comment.getComment(), new HtmlTransformerContext(userIdentity, null)));
    }
  }

  private List<String> filterMembershipsBySpaceIds(List<Long> spaceIds, List<String> memberships) {
    if (CollectionUtils.isNotEmpty(spaceIds)) {
      List<String> spacesGroupId = spaceIds.stream().map(spaceId -> {
        Space space = spaceService.getSpaceById(spaceId);
        return space != null ? space.getGroupId() : null;
      }).filter(Objects::nonNull).toList();

      return memberships.stream().filter(membership -> spacesGroupId.stream().anyMatch(membership::contains)).toList();
    }
    return memberships;
  }

}
