content: package io.aiven.klaw.service;
  
  import static io.aiven.klaw.error.KlawErrorMessages.OP_REQS_ERR_101;
  import static io.aiven.klaw.error.KlawErrorMessages.OP_REQS_ERR_102;
  import static io.aiven.klaw.error.KlawErrorMessages.OP_REQS_ERR_103;
  import static io.aiven.klaw.error.KlawErrorMessages.REQ_ERR_101;
  import static io.aiven.klaw.error.KlawErrorMessages.TOPICS_ERR_101;
  import static io.aiven.klaw.service.MailUtils.MailType.RESET_CONSUMER_OFFSET_DENIED;
  import static org.springframework.beans.BeanUtils.copyProperties;
  
  import io.aiven.klaw.config.ManageDatabase;
  import io.aiven.klaw.dao.Acl;
  import io.aiven.klaw.dao.OperationalRequest;
  import io.aiven.klaw.dao.UserInfo;
  import io.aiven.klaw.error.KlawException;
  import io.aiven.klaw.error.KlawNotAuthorizedException;
  import io.aiven.klaw.helpers.HandleDbRequests;
  import io.aiven.klaw.helpers.Pager;
  import io.aiven.klaw.model.ApiResponse;
  import io.aiven.klaw.model.cluster.consumergroup.OffsetResetType;
  import io.aiven.klaw.model.cluster.consumergroup.OffsetsTiming;
  import io.aiven.klaw.model.cluster.consumergroup.ResetConsumerGroupOffsetsRequest;
  import io.aiven.klaw.model.enums.ApiResultStatus;
  import io.aiven.klaw.model.enums.OperationalRequestType;
  import io.aiven.klaw.model.enums.Order;
  import io.aiven.klaw.model.enums.PermissionType;
  import io.aiven.klaw.model.enums.RequestEntityType;
  import io.aiven.klaw.model.enums.RequestStatus;
  import io.aiven.klaw.model.requests.ConsumerOffsetResetRequestModel;
  import io.aiven.klaw.model.response.EnvIdInfo;
  import io.aiven.klaw.model.response.OperationalRequestsResponseModel;
  import io.aiven.klaw.validation.PermissionAllowed;
  import java.sql.Timestamp;
  import java.time.ZonedDateTime;
  import java.util.ArrayList;
  import java.util.Collections;
  import java.util.Comparator;
  import java.util.List;
  import java.util.Map;
  import java.util.Objects;
  import java.util.Set;
  import java.util.stream.Collectors;
  import lombok.extern.slf4j.Slf4j;
  import org.apache.commons.lang3.math.NumberUtils;
  import org.springframework.stereotype.Service;
  
  @Service
  @Slf4j
  public class OperationalRequestsService {
  
    public static final String OFFSET_RESET_TIMESTAMP_FORMAT = "yyyy-MM-dd'T'HH:mm:ss.SSSZ";
    private final ManageDatabase manageDatabase;
    private final MailUtils mailService;
    private final ClusterApiService clusterApiService;
    private final CommonUtilsService commonUtilsService;
    private final RolesPermissionsControllerService rolesPermissionsControllerService;
  
    public OperationalRequestsService(
        ManageDatabase manageDatabase,
        MailUtils mailService,
        ClusterApiService clusterApiService,
        CommonUtilsService commonUtilsService,
        RolesPermissionsControllerService rolesPermissionsControllerService) {
      this.manageDatabase = manageDatabase;
      this.mailService = mailService;
      this.clusterApiService = clusterApiService;
      this.commonUtilsService = commonUtilsService;
      this.rolesPermissionsControllerService = rolesPermissionsControllerService;
    }
  
    public ApiResponse createConsumerOffsetsResetRequest(
        ConsumerOffsetResetRequestModel consumerOffsetResetRequestModel)
        throws KlawNotAuthorizedException {
      log.info("createConsumerOffsetsResetRequest {}", consumerOffsetResetRequestModel);
      checkIsAuthorized(PermissionType.REQUEST_CREATE_SUBSCRIPTIONS);
      String userName = getUserName();
      consumerOffsetResetRequestModel.setRequestor(userName);
      consumerOffsetResetRequestModel.setRequestingTeamId(commonUtilsService.getTeamId(userName));
  
      // validations
  
      // check for owner of the acl consumer group
      EnvIdInfo envIdInfo =
          validateOffsetRequestDetails(
              consumerOffsetResetRequestModel.getEnvironment(),
              consumerOffsetResetRequestModel.getTopicname(),
              consumerOffsetResetRequestModel.getConsumerGroup());
      if (envIdInfo == null) {
        return ApiResponse.notOk(OP_REQS_ERR_101);
      }
  
      // if reset type is date time, timestamp field is mandatory
      if (consumerOffsetResetRequestModel.getOffsetResetType() == OffsetResetType.TO_DATE_TIME
          && consumerOffsetResetRequestModel.getResetTimeStampStr() == null) {
        return ApiResponse.notOk(OP_REQS_ERR_102);
      }
  
      // check if a request already exists
      HandleDbRequests dbHandle = manageDatabase.getHandleDbRequests();
      List<OperationalRequest> operationalRequests =
          dbHandle.getOperationalRequests(
              userName,
              OperationalRequestType.RESET_CONSUMER_OFFSETS,
              RequestStatus.CREATED.value,
              consumerOffsetResetRequestModel.getEnvironment(),
              consumerOffsetResetRequestModel.getTopicname(),
              consumerOffsetResetRequestModel.getConsumerGroup(),
              null,
              false,
              commonUtilsService.getTenantId(userName));
      if (!operationalRequests.isEmpty()) {
        return ApiResponse.notOk(OP_REQS_ERR_103);
      }
  
      OperationalRequest operationalRequest = new OperationalRequest();
      copyProperties(consumerOffsetResetRequestModel, operationalRequest);
      operationalRequest.setTenantId(commonUtilsService.getTenantId(userName));
      if (operationalRequest.getOffsetResetType() == OffsetResetType.TO_DATE_TIME) {
        operationalRequest.setResetTimeStamp(
            getTimeStamp(consumerOffsetResetRequestModel.getResetTimeStampStr()));
      }
      String result = dbHandle.requestForConsumerOffsetsReset(operationalRequest).get("result");
  
      String requestFormattedStr = "Consumer group : " + operationalRequest.getConsumerGroup();
      mailService.sendMail(
          operationalRequest.getTopicname(),
          requestFormattedStr,
          "",
          operationalRequest.getRequestor(),
          operationalRequest.getApprover(),
          operationalRequest.getRequestingTeamId(),
          dbHandle,
          MailUtils.MailType.RESET_CONSUMER_OFFSET_REQUESTED,
          commonUtilsService.getLoginUrl());
  
      return ApiResultStatus.SUCCESS.value.equals(result)
          ? ApiResponse.ok(result)
          : ApiResponse.notOk(result);
    }
  
    // expected timestamp in format constant OFFSET_RESET_TIMESTAMP_FORMAT
    private Timestamp getTimeStamp(String timeStampStr) {
      ZonedDateTime parsedDate = ZonedDateTime.parse(timeStampStr);
      return Timestamp.from(parsedDate.toInstant());
    }
  
    public List<OperationalRequestsResponseModel> getOperationalRequests(
        String pageNo,
        String currentPage,
        OperationalRequestType operationalRequestType,
        String requestStatus,
        String env,
        String topicName,
        String consumerGroup,
        String wildcardSearch,
        Order order,
        boolean isMyRequest) {
      log.debug(
          "getOperationalRequests page {} operationalRequestType {}", pageNo, operationalRequestType);
      String userName = getUserName();
      HandleDbRequests dbHandle = manageDatabase.getHandleDbRequests();
      List<OperationalRequest> operationalRequests =
          dbHandle.getOperationalRequests(
              userName,
              operationalRequestType,
              requestStatus,
              env,
              topicName,
              consumerGroup,
              wildcardSearch,
              isMyRequest,
              commonUtilsService.getTenantId(userName));
      // tenant filtering
      operationalRequests = filterByTenantAndSort(order, userName, operationalRequests);
  
      final int tenantId = commonUtilsService.getTenantId(userName);
      operationalRequests =
          operationalRequests.isEmpty()
              ? Collections.emptyList()
              : Pager.getItemsList(
                  pageNo,
                  currentPage,
                  10,
                  operationalRequests,
                  (pageContext, operationalRequest) -> {
                    operationalRequest.setAllPageNos(pageContext.getAllPageNos());
                    operationalRequest.setTotalNoPages(pageContext.getTotalPages());
                    operationalRequest.setCurrentPage(pageContext.getPageNo());
                    operationalRequest.setEnvironmentName(
                        commonUtilsService
                            .getEnvDetails(operationalRequest.getEnvironment(), tenantId)
                            .getName());
                    return operationalRequest;
                  });
      return getOperationalRequestModels(operationalRequests);
    }
  
    private List<OperationalRequestsResponseModel> getOperationalRequestModels(
        List<OperationalRequest> operationalRequestList) {
      List<OperationalRequestsResponseModel> operationalRequestModelList = new ArrayList<>();
      OperationalRequestsResponseModel operationalRequestModel;
      String userName = getUserName();
      Integer userTeamId = commonUtilsService.getTeamId(userName);
  
      int tenantId = commonUtilsService.getTenantId(userName);
      Set<String> approverRoles =
          rolesPermissionsControllerService.getApproverRoles("TOPICS", tenantId);
      List<UserInfo> userList = manageDatabase.getUsersPerTeamAndTenant(userTeamId, tenantId);
  
      for (OperationalRequest operationalRequest : operationalRequestList) {
        operationalRequestModel = new OperationalRequestsResponseModel();
        copyProperties(operationalRequest, operationalRequestModel);
        operationalRequestModel.setTeamId(operationalRequest.getRequestingTeamId());
        operationalRequestModel.setRequestStatus(
            RequestStatus.of(operationalRequest.getRequestStatus()));
        operationalRequestModel.setOperationalRequestType(
            operationalRequest.getOperationalRequestType());
        if (operationalRequest.getResetTimeStamp() != null) {
          operationalRequestModel.setResetTimeStampStr(
              operationalRequest.getResetTimeStamp().toString() + " (UTC)");
        }
  
        operationalRequestModel.setTeamname(
            manageDatabase.getTeamNameFromTeamId(tenantId, operationalRequest.getRequestingTeamId()));
  
        // show approving info only before approvals
        if (RequestStatus.APPROVED != operationalRequestModel.getRequestStatus()) {
          operationalRequestModel.setApprovingTeamDetails(
              updateApproverInfo(
                  userList,
                  manageDatabase.getTeamNameFromTeamId(tenantId, userTeamId),
                  approverRoles,
                  operationalRequestModel.getRequestor()));
        }
        operationalRequestModelList.add(setRequestorPermissions(operationalRequestModel, userName));
      }
      return operationalRequestModelList;
    }
  
    public List<OperationalRequestsResponseModel> getOperationalRequestsForApprover(
        String pageNo,
        String currentPage,
        String requestStatus,
        OperationalRequestType operationalRequestType,
        String env,
        String topicName,
        String consumerGroup,
        String wildcardSearch,
        Order order) {
      if (log.isDebugEnabled()) {
        log.debug(
            "getOperationalRequestsForApprover pageNo {} requestStatus {} operationalRequestType {} env {} wildcardSearch {}",
            pageNo,
            requestStatus,
            operationalRequestType,
            env,
            wildcardSearch);
      }
  
      String userName = getUserName();
      int teamId = commonUtilsService.getTeamId(userName);
      List<OperationalRequest> operationalRequestList;
      int tenantId = commonUtilsService.getTenantId(userName);
      // get requests relevant to your teams or all teams
      if (commonUtilsService.isNotAuthorizedUser(
          commonUtilsService.getPrincipal(), PermissionType.APPROVE_ALL_REQUESTS_TEAMS)) {
        operationalRequestList =
            manageDatabase
                .getHandleDbRequests()
                .getCreatedOperationalRequests(
                    userName,
                    requestStatus,
                    false,
                    tenantId,
                    teamId,
                    env,
                    topicName,
                    consumerGroup,
                    operationalRequestType,
                    wildcardSearch);
      } else {
        operationalRequestList =
            manageDatabase
                .getHandleDbRequests()
                .getCreatedOperationalRequests(
                    userName,
                    requestStatus,
                    false,
                    tenantId,
                    teamId,
                    env,
                    topicName,
                    consumerGroup,
                    operationalRequestType,
                    wildcardSearch);
      }
  
      operationalRequestList = filterByTenantAndSort(order, userName, operationalRequestList);
      operationalRequestList =
          Pager.getItemsList(
              pageNo,
              currentPage,
              10,
              operationalRequestList,
              (pageContext, activityLog) -> {
                activityLog.setAllPageNos(pageContext.getAllPageNos());
                activityLog.setTotalNoPages(pageContext.getTotalPages());
                activityLog.setCurrentPage(pageContext.getPageNo());
                activityLog.setEnvironmentName(
                    commonUtilsService
                        .getEnvDetails(activityLog.getEnvironment(), tenantId)
                        .getName());
                return activityLog;
              });
  
      return updateCreateOperationalReqsList(operationalRequestList, tenantId);
    }
  
    private List<OperationalRequestsResponseModel> updateCreateOperationalReqsList(
        List<OperationalRequest> topicsList, int tenantId) {
      List<OperationalRequestsResponseModel> topicRequestModelList =
          getOperationalRequestModels(topicsList);
  
      for (OperationalRequestsResponseModel operationalRequestsResponseModel :
          topicRequestModelList) {
        operationalRequestsResponseModel.setTeamname(
            manageDatabase.getTeamNameFromTeamId(
                tenantId, operationalRequestsResponseModel.getTeamId()));
        operationalRequestsResponseModel.setEnvironmentName(
            commonUtilsService
                .getEnvDetails(operationalRequestsResponseModel.getEnvironment(), tenantId)
                .getName());
      }
  
      return topicRequestModelList;
    }
  
    private OperationalRequestsResponseModel setRequestorPermissions(
        OperationalRequestsResponseModel req, String userName) {
      log.debug(
          " My request Status {} and userName {} and requestor {}",
          req.getRequestStatus(),
          userName,
          req.getRequestor());
      if (RequestStatus.CREATED == req.getRequestStatus()
          && userName != null
          && userName.equals(req.getRequestor())) {
        req.setDeletable(true);
        req.setEditable(true);
      }
  
      return req;
    }
  
    private String updateApproverInfo(
        List<UserInfo> userList, String teamName, Set<String> approverRoles, String requestor) {
      StringBuilder approvingInfo = new StringBuilder("Team : " + teamName + ", Users : ");
  
      for (UserInfo userInfo : userList) {
        if (approverRoles.contains(userInfo.getRole())
            && !Objects.equals(requestor, userInfo.getUsername())) {
          approvingInfo.append(userInfo.getUsername()).append(",");
        }
      }
  
      return approvingInfo.toString();
    }
  
    private List<OperationalRequest> filterByTenantAndSort(
        Order order, String userName, List<OperationalRequest> topicReqs) {
      try {
        final Set<String> allowedEnvIdSet = commonUtilsService.getEnvsFromUserId(userName);
        topicReqs =
            topicReqs.stream()
                .filter(topicRequest -> allowedEnvIdSet.contains(topicRequest.getEnvironment()))
                .sorted(getPreferredOrdering(order))
                .collect(Collectors.toList());
      } catch (Exception e) {
        log.error("No environments/clusters found.", e);
        return new ArrayList<>();
      }
      return topicReqs;
    }
  
    private Comparator<OperationalRequest> getPreferredOrdering(Order order) {
      return switch (order) {
        case ASC_REQUESTED_TIME -> compareByTime();
        case DESC_REQUESTED_TIME -> Collections.reverseOrder(compareByTime());
      };
    }
  
    private static Comparator<OperationalRequest> compareByTime() {
      return Comparator.comparing(OperationalRequest::getRequesttime);
    }
  
    @PermissionAllowed(permissionAllowed = {PermissionType.APPROVE_OPERATIONAL_CHANGES})
    public ApiResponse approveOperationalRequests(String reqId) {
      log.info("approveConsumerOffsetRequests {}", reqId);
      final String userName = getUserName();
      int tenantId = commonUtilsService.getTenantId(userName);
      if (commonUtilsService.isNotAuthorizedUser(
          commonUtilsService.getPrincipal(), PermissionType.APPROVE_OPERATIONAL_CHANGES)) {
        return ApiResponse.NOT_AUTHORIZED;
      }
  
      HandleDbRequests dbHandle = manageDatabase.getHandleDbRequests();
      OperationalRequest operationalRequest =
          dbHandle.getOperationalRequest(Integer.parseInt(reqId), tenantId);
      ResetConsumerGroupOffsetsRequest resetConsumerGroupOffsetsRequest =
          ResetConsumerGroupOffsetsRequest.builder()
              .consumerGroup(operationalRequest.getConsumerGroup())
              .topicName(operationalRequest.getTopicname())
              .offsetResetType(operationalRequest.getOffsetResetType())
              .consumerGroupResetTimestampMilliSecs(
                  operationalRequest.getOffsetResetType() == OffsetResetType.TO_DATE_TIME
                      ? operationalRequest.getResetTimeStamp().getTime()
                      : null)
              .build();
      ApiResponse apiResponse;
      try {
        apiResponse =
            clusterApiService.resetConsumerOffsets(
                resetConsumerGroupOffsetsRequest, operationalRequest.getEnvironment(), tenantId);
      } catch (KlawException e) {
        return ApiResponse.notOk(ApiResultStatus.FAILURE.value);
      }
  
      String updateOffsetReqStatus = "";
      if (apiResponse.isSuccess()) {
        if (apiResponse.getData() instanceof Map) {
          Map<OffsetsTiming, Map<String, Long>> offsetPositionsBeforeAndAfter =
              (Map) apiResponse.getData();
          String beforeReset =
              "\n\nBefore Offset Reset"
                  + offsetPositionsBeforeAndAfter.get(OffsetsTiming.BEFORE_OFFSET_RESET);
          String afterReset =
              "\n\nAfter Offset Reset"
                  + offsetPositionsBeforeAndAfter.get(OffsetsTiming.AFTER_OFFSET_RESET);
          String offsetResetDetails =
              resetConsumerGroupOffsetsRequest.getConsumerGroup() + "\n" + beforeReset + afterReset;
  
          updateOffsetReqStatus =
              dbHandle.updateOperationalChangeRequest(
                  operationalRequest, userName, RequestStatus.APPROVED);
  
          dbHandle.insertIntoActivityLog(
              RequestEntityType.OPERATIONAL.value,
              tenantId,
              OperationalRequestType.RESET_CONSUMER_OFFSETS.value,
              operationalRequest.getRequestingTeamId(),
              "Topic : "
                  + operationalRequest.getTopicname()
                  + " ConsumerGroup : "
                  + operationalRequest.getConsumerGroup()
                  + " ResetType :"
                  + operationalRequest.getOffsetResetType(),
              operationalRequest.getEnvironment(),
              operationalRequest.getRequestor());
  
          mailService.sendMail(
              operationalRequest.getTopicname(),
              offsetResetDetails,
              "",
              operationalRequest.getRequestor(),
              operationalRequest.getApprover(),
              operationalRequest.getRequestingTeamId(),
              dbHandle,
              MailUtils.MailType.RESET_CONSUMER_OFFSET_APPROVED,
              commonUtilsService.getLoginUrl());
        }
      }
  
      return updateOffsetReqStatus.equals(ApiResultStatus.SUCCESS.value)
          ? ApiResponse.SUCCESS
          : ApiResponse.FAILURE;
    }
  
    private void checkIsAuthorized(PermissionType permission) throws KlawNotAuthorizedException {
      if (commonUtilsService.isNotAuthorizedUser(commonUtilsService.getPrincipal(), permission)) {
        throw new KlawNotAuthorizedException(TOPICS_ERR_101);
      }
    }
  
    private String getUserName() {
      return mailService.getCurrentUserName();
    }
  
    public EnvIdInfo validateOffsetRequestDetails(
        String envId, String topicName, String consumerGroup) {
      log.debug("validateOffsetRequestDetails {} {} {}", envId, topicName, consumerGroup);
      String userName = getUserName();
      int tenantId = commonUtilsService.getTenantId(userName);
      int teamId = commonUtilsService.getTeamId(userName);
  
      if (consumerGroup == null || consumerGroup.equals("")) {
        return null;
      }
  
      List<Acl> aclList =
          manageDatabase
              .getHandleDbRequests()
              .getSyncAcls(envId, topicName, teamId, consumerGroup, tenantId);
  
      if (!aclList.isEmpty()) {
        EnvIdInfo envIdInfo = new EnvIdInfo();
        envIdInfo.setId(envId);
        envIdInfo.setName(commonUtilsService.getEnvDetails(envId, tenantId).getName());
        return envIdInfo;
      } else {
        return null;
      }
    }
  
    public ApiResponse deleteOperationalRequest(String operationalRequestId) throws KlawException {
      log.info("deleteOperationalRequest {}", operationalRequestId);
  
      if (commonUtilsService.isNotAuthorizedUser(
          commonUtilsService.getPrincipal(), PermissionType.REQUEST_CREATE_OPERATIONAL_CHANGES)) {
        return ApiResponse.NOT_AUTHORIZED;
      }
      String userName = getUserName();
      try {
        String deleteOperationalReqStatus =
            manageDatabase
                .getHandleDbRequests()
                .deleteOperationalRequest(
                    Integer.parseInt(operationalRequestId),
                    userName,
                    commonUtilsService.getTenantId(getUserName()));
  
        return ApiResultStatus.SUCCESS.value.equals(deleteOperationalReqStatus)
            ? ApiResponse.ok(deleteOperationalReqStatus)
            : ApiResponse.notOk(deleteOperationalReqStatus);
      } catch (Exception e) {
        log.error(e.getMessage());
        throw new KlawException(e.getMessage());
      }
    }
  
    public ApiResponse declineOperationalRequest(String reqId, String reasonForDecline)
        throws KlawException {
      log.debug("declineOperationalRequest {} {}", reqId, reasonForDecline);
      if (commonUtilsService.isNotAuthorizedUser(
          commonUtilsService.getPrincipal(), PermissionType.APPROVE_OPERATIONAL_CHANGES)) {
        return ApiResponse.NOT_AUTHORIZED;
      }
  
      String userName = getUserName();
      HandleDbRequests dbHandle = manageDatabase.getHandleDbRequests();
      OperationalRequest operationalRequest =
          dbHandle.getOperationalRequestsForId(
              Integer.parseInt(reqId), commonUtilsService.getTenantId(userName));
  
      if (!RequestStatus.CREATED.value.equals(operationalRequest.getRequestStatus())) {
        return ApiResponse.notOk(REQ_ERR_101);
      }
  
      // tenant filtering
      final Set<String> allowedEnvIdSet = commonUtilsService.getEnvsFromUserId(userName);
      if (!allowedEnvIdSet.contains(operationalRequest.getEnvironment())) {
        return ApiResponse.NOT_AUTHORIZED;
      }
  
      try {
        String result =
            dbHandle.updateOperationalChangeRequest(
                operationalRequest, userName, RequestStatus.DECLINED);
        mailService.sendMail(
            operationalRequest.getTopicname(),
            null,
            reasonForDecline,
            operationalRequest.getRequestor(),
            operationalRequest.getApprover(),
            NumberUtils.toInt(operationalRequest.getApprovingTeamId(), -1),
            dbHandle,
            RESET_CONSUMER_OFFSET_DENIED,
            commonUtilsService.getLoginUrl());
  
        return ApiResultStatus.SUCCESS.value.equals(result)
            ? ApiResponse.ok(result)
            : ApiResponse.notOk(result);
      } catch (Exception e) {
        throw new KlawException(e.getMessage());
      }
    }
  }
  