content: package io.aiven.klaw.clusterapi.services;
  
  import com.fasterxml.jackson.databind.ObjectMapper;
  import io.aiven.klaw.clusterapi.models.AivenAclResponse;
  import io.aiven.klaw.clusterapi.models.AivenAclStruct;
  import io.aiven.klaw.clusterapi.models.ApiResponse;
  import io.aiven.klaw.clusterapi.models.ClusterAclRequest;
  import io.aiven.klaw.clusterapi.models.ServiceAccountDetails;
  import io.aiven.klaw.clusterapi.models.enums.AclAttributes;
  import io.aiven.klaw.clusterapi.models.enums.ApiResultStatus;
  import java.util.ArrayList;
  import java.util.Arrays;
  import java.util.HashMap;
  import java.util.HashSet;
  import java.util.List;
  import java.util.Map;
  import java.util.Objects;
  import java.util.Optional;
  import java.util.Set;
  import lombok.extern.slf4j.Slf4j;
  import org.apache.commons.lang3.StringUtils;
  import org.springframework.beans.factory.annotation.Value;
  import org.springframework.core.ParameterizedTypeReference;
  import org.springframework.http.HttpEntity;
  import org.springframework.http.HttpHeaders;
  import org.springframework.http.HttpMethod;
  import org.springframework.http.HttpStatus;
  import org.springframework.http.ResponseEntity;
  import org.springframework.stereotype.Service;
  import org.springframework.web.client.HttpClientErrorException;
  import org.springframework.web.client.RestClientException;
  import org.springframework.web.client.RestTemplate;
  
  @Service
  @Slf4j
  public class AivenApiService {
  
    public static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
    public static final String PROJECT_NAME = "projectName";
    public static final String SERVICE_NAME = "serviceName";
    public static final String USERNAME = "username";
    public static final String PRINCIPLE = "principle";
    public static final String RESOURCE_TYPE = "resourceType";
    public static final String USER_NAME = "userName";
  
    private RestTemplate restTemplate;
  
    private HttpHeaders httpHeaders;
  
    @Value("${klaw.clusters.accesstoken:accesstoken}")
    private String clusterAccessToken;
  
    @Value("${klaw.clusters.aiven.listacls.api:api}")
    private String listAclsApiEndpoint;
  
    @Value("${klaw.clusters.aiven.addacls.api:api}")
    private String addAclsApiEndpoint;
  
    @Value("${klaw.clusters.aiven.deleteacls.api:api}")
    private String deleteAclsApiEndpoint;
  
    @Value("${klaw.clusters.aiven.addserviceaccount.api:api}")
    private String addServiceAccountApiEndpoint;
  
    @Value("${klaw.clusters.aiven.getserviceaccount.api:api}")
    private String serviceAccountApiEndpoint;
  
    @Value("${klaw.clusters.aiven.servicedetails.api:api}")
    private String serviceDetailsApiEndpoint;
  
    public Map<String, String> createAcls(ClusterAclRequest clusterAclRequest) {
      Map<String, String> resultMap = new HashMap<>();
      RestTemplate restTemplate = getRestTemplate();
      String projectName = clusterAclRequest.getProjectName();
      String serviceName = clusterAclRequest.getServiceName();
  
      Map<String, String> permissionsMap = new HashMap<>();
      permissionsMap.put(AclAttributes.TOPIC.value, clusterAclRequest.getTopicName());
      permissionsMap.put(AclAttributes.PERMISSION.value, clusterAclRequest.getPermission());
      permissionsMap.put(AclAttributes.USERNAME.value, clusterAclRequest.getUsername());
  
      String uri =
          addAclsApiEndpoint.replace(PROJECT_NAME, projectName).replace(SERVICE_NAME, serviceName);
  
      HttpHeaders headers = getHttpHeaders();
      HttpEntity<Map<String, String>> request = new HttpEntity<>(permissionsMap, headers);
  
      try {
  
        ResponseEntity<AivenAclResponse> aclResponse =
            restTemplate.postForEntity(uri, request, AivenAclResponse.class);
        Optional<AivenAclStruct> aivenAclStructOptional =
            extractAcl(clusterAclRequest, aclResponse.getBody());
        if (aivenAclStructOptional.isEmpty()) {
          aclResponse = restTemplate.postForEntity(uri, request, AivenAclResponse.class);
          aivenAclStructOptional = extractAcl(clusterAclRequest, aclResponse.getBody());
        }
        aivenAclStructOptional.ifPresent(
            aivenAclStruct -> resultMap.put("aivenaclid", aivenAclStruct.getId()));
  
        handleAclCreationResponse(
            clusterAclRequest, resultMap, projectName, serviceName, aclResponse);
  
        return resultMap;
      } catch (Exception e) {
        log.error("Exception:", e);
        resultMap.put("result", "Failure in adding acls" + e.getMessage());
        return resultMap;
      }
    }
  
    private static Optional<AivenAclStruct> extractAcl(
        ClusterAclRequest clusterAclRequest, AivenAclResponse aivenAclResponse) {
      if (aivenAclResponse == null) {
        return Optional.empty();
      }
      return Arrays.stream(aivenAclResponse.getAcl())
          .filter(
              acl ->
                  acl.getUsername().equals(clusterAclRequest.getUsername())
                      && acl.getTopic().equals(clusterAclRequest.getTopicName())
                      && acl.getPermission().equals(clusterAclRequest.getPermission()))
          .findFirst();
    }
  
    private void handleAclCreationResponse(
        ClusterAclRequest clusterAclRequest,
        Map<String, String> resultMap,
        String projectName,
        String serviceName,
        ResponseEntity<AivenAclResponse> response) {
      if (response.getStatusCode().equals(HttpStatus.OK)) {
        log.info(
            "Acl created. Project :{} Service : {} Topic : {}",
            projectName,
            serviceName,
            clusterAclRequest.getTopicName());
        if (!getServiceAccountDetails(projectName, serviceName, clusterAclRequest.getUsername())
            .isAccountFound()) {
          createServiceAccount(clusterAclRequest, resultMap);
        } else {
          resultMap.put("result", ApiResultStatus.SUCCESS.value);
        }
      } else {
        log.error(
            "Acl creation failure Project :{} Service : {} Topic : {}",
            projectName,
            serviceName,
            clusterAclRequest.getTopicName());
        AivenAclResponse resultMsg = response.getBody();
        String failureMsg =
            (resultMsg != null && StringUtils.isEmpty(resultMsg.getMessage()))
                ? resultMsg.getMessage()
                : "indeterminable exception";
        resultMap.put("result", "Failure in adding acls " + failureMsg);
      }
    }
  
    private void createServiceAccount(
        ClusterAclRequest clusterAclRequest, Map<String, String> resultMap) {
      log.debug("Creating service account clusterAclRequest :{}", clusterAclRequest);
      String projectName = clusterAclRequest.getProjectName();
      String serviceName = clusterAclRequest.getServiceName();
      HttpHeaders headers = getHttpHeaders();
      String uri =
          addServiceAccountApiEndpoint
              .replace(PROJECT_NAME, projectName)
              .replace(SERVICE_NAME, serviceName);
      Map<String, String> requestMap = new HashMap<>();
      requestMap.put(AclAttributes.USERNAME.value, clusterAclRequest.getUsername());
      HttpEntity<Map<String, String>> request = new HttpEntity<>(requestMap, headers);
      try {
        ResponseEntity<String> response = getRestTemplate().postForEntity(uri, request, String.class);
        if (response.getStatusCode().equals(HttpStatus.OK)) {
          log.info("Service account created successfully {}", clusterAclRequest);
          resultMap.put("result", ApiResultStatus.SUCCESS.value);
        } else {
          log.info("Service account creation failure {}", clusterAclRequest);
          resultMap.put("result", "Failure in adding service account " + response.getBody());
        }
      } catch (Exception e) {
        log.error("Exception:", e);
        resultMap.put("result", "Failure in adding acls" + e.getMessage());
      }
    }
  
    private String deleteServiceAccountUser(
        String projectName, String serviceName, String serviceAccountUser) {
      log.debug(
          "Deleting service account user : projectName serviceName serviceAccountUser :{} {} {}",
          projectName,
          serviceName,
          serviceAccountUser);
      String uri =
          serviceAccountApiEndpoint
              .replace(PROJECT_NAME, projectName)
              .replace(SERVICE_NAME, serviceName)
              .replace(USER_NAME, serviceAccountUser);
      try {
        HttpHeaders headers = getHttpHeaders();
        HttpEntity<?> request = new HttpEntity<>(headers);
        restTemplate.exchange(uri, HttpMethod.DELETE, request, Object.class);
      } catch (Exception e) {
        log.error("Exception:", e);
        return ApiResultStatus.FAILURE.value;
      }
      return ApiResultStatus.SUCCESS.value;
    }
  
    // Get Aiven service account details
    public ServiceAccountDetails getServiceAccountDetails(
        String projectName, String serviceName, String userName) {
      log.debug(
          "Service account for project :{} service : {} user : {}",
          projectName,
          serviceName,
          userName);
      HttpHeaders headers = getHttpHeaders();
      String uri =
          serviceAccountApiEndpoint
              .replace(PROJECT_NAME, projectName)
              .replace(SERVICE_NAME, serviceName)
              .replace(USER_NAME, userName);
      HttpEntity<Map<String, String>> request = new HttpEntity<>(headers);
      ServiceAccountDetails serviceAccountDetails = new ServiceAccountDetails();
      serviceAccountDetails.setAccountFound(false);
      try {
        ResponseEntity<Map<String, Map<String, String>>> response =
            getRestTemplate()
                .exchange(uri, HttpMethod.GET, request, new ParameterizedTypeReference<>() {});
        if (response.getStatusCode().equals(HttpStatus.OK)) {
          Map<String, Map<String, String>> responseMap = response.getBody();
          if (responseMap != null
              && responseMap.containsKey("user")
              && responseMap.get("user").containsKey(USERNAME)) {
            // Not sending the full service account details.
            // Response enriched only with username and password. Certificates are removed from the
            // response.
  
            Map<String, String> resultMap = responseMap.get("user");
            serviceAccountDetails.setPassword(resultMap.get("password"));
            serviceAccountDetails.setUsername(resultMap.get(USERNAME));
            serviceAccountDetails.setAccountFound(true);
            return serviceAccountDetails;
          }
        }
      } catch (Exception e) {
        log.error("Exception:", e);
      }
      return serviceAccountDetails;
    }
  
    // Get Aiven service accounts
    public Set<String> getServiceAccountUsers(String projectName, String serviceName) {
      log.debug("Services account for project :{} service : {}", projectName, serviceName);
      Set<String> serviceAccountsSet = new HashSet<>();
      HttpHeaders headers = getHttpHeaders();
      String uri =
          serviceDetailsApiEndpoint
              .replace(PROJECT_NAME, projectName)
              .replace(SERVICE_NAME, serviceName);
      HttpEntity<Map<String, String>> request = new HttpEntity<>(headers);
      try {
        ResponseEntity<Map<String, Map<String, Object>>> response =
            getRestTemplate()
                .exchange(uri, HttpMethod.GET, request, new ParameterizedTypeReference<>() {});
        if (response.getStatusCode().equals(HttpStatus.OK)) {
          Map<String, Map<String, Object>> responseMap = response.getBody();
          if (responseMap != null && responseMap.containsKey("service")) {
            Map<String, Object> serviceDetailsMap = responseMap.get("service");
            if (serviceDetailsMap.containsKey("users")) {
              ArrayList<HashMap<String, Object>> userList =
                  (ArrayList) serviceDetailsMap.get("users");
              userList.forEach(a -> serviceAccountsSet.add((String) a.get("username")));
              return serviceAccountsSet;
            }
          }
        }
      } catch (Exception e) {
        log.error("Exception:", e);
      }
      return new HashSet<>();
    }
  
    public ApiResponse deleteAcls(ClusterAclRequest clusterAclRequest) throws Exception {
      RestTemplate restTemplate = getRestTemplate();
      boolean serviceUserDeleted = false;
      try {
        String projectName = clusterAclRequest.getProjectName();
        String serviceName = clusterAclRequest.getServiceName();
        String aclId = clusterAclRequest.getAivenAclKey();
  
        String uri =
            deleteAclsApiEndpoint
                .replace(PROJECT_NAME, projectName)
                .replace(SERVICE_NAME, serviceName)
                .replace("aclId", aclId);
  
        HttpHeaders headers = getHttpHeaders();
        HttpEntity<?> request = new HttpEntity<>(headers);
        restTemplate.exchange(uri, HttpMethod.DELETE, request, Object.class);
  
        // Check if service user can be deleted
        serviceUserDeleted = deleteServiceAccountUser(clusterAclRequest);
      } catch (Exception e) {
        log.error("Exception:", e);
        if (e instanceof HttpClientErrorException) {
          if (((HttpClientErrorException) e).getStatusCode() == HttpStatus.NOT_FOUND) {
            return ApiResponse.builder()
                .success(true)
                .message(ApiResultStatus.SUCCESS.value)
                .data(false)
                .build();
          }
        }
        throw new Exception("Error in deleting acls " + e.getMessage());
      }
      return ApiResponse.builder()
          .success(true)
          .message(ApiResultStatus.SUCCESS.value)
          .data(serviceUserDeleted)
          .build();
    }
  
    private boolean deleteServiceAccountUser(ClusterAclRequest clusterAclRequest) throws Exception {
      Set<Map<String, String>> aclsList =
          listAcls(clusterAclRequest.getProjectName(), clusterAclRequest.getServiceName());
      long aclsListFiltered =
          aclsList.stream()
              .filter(
                  aclMap ->
                      aclMap.containsKey(PRINCIPLE)
                          && aclMap.containsKey(RESOURCE_TYPE)
                          && aclMap.get(PRINCIPLE).equals(clusterAclRequest.getUsername())
                          && aclMap.get(RESOURCE_TYPE).equals("TOPIC"))
              .count();
      // Check if there are any other topics using the same principle
      if (aclsListFiltered == 0) {
        deleteServiceAccountUser(
            clusterAclRequest.getProjectName(),
            clusterAclRequest.getServiceName(),
            clusterAclRequest.getUsername());
        return true;
      }
      return false;
    }
  
    public Set<Map<String, String>> listAcls(String projectName, String serviceName)
        throws Exception {
      RestTemplate restTemplate = getRestTemplate();
      log.info("listAcls {} {}", projectName, serviceName);
  
      String uri =
          listAclsApiEndpoint.replace(PROJECT_NAME, projectName).replace(SERVICE_NAME, serviceName);
  
      HttpHeaders headers = getHttpHeaders();
      HttpEntity<Map<String, String>> request = new HttpEntity<>(headers);
  
      try {
        ResponseEntity<Map<String, List<Map<String, String>>>> responseEntity =
            restTemplate.exchange(
                uri, HttpMethod.GET, request, new ParameterizedTypeReference<>() {});
  
        List<Map<String, String>> aclsList =
            Objects.requireNonNull(responseEntity.getBody()).get("acl");
        List<Map<String, String>> aclsListUpdated = new ArrayList<>();
        for (Map<String, String> aclsMap : aclsList) {
          Map<String, String> aclsMapUpdated = new HashMap<>();
          for (String keyAcls : aclsMap.keySet()) {
            switch (keyAcls) {
              case "id" -> aclsMapUpdated.put("aivenaclid", aclsMap.get(keyAcls));
              case "permission" -> {
                aclsMapUpdated.put("operation", aclsMap.get(keyAcls).toUpperCase());
                aclsMapUpdated.put(RESOURCE_TYPE, "TOPIC");
              }
              case "topic" -> aclsMapUpdated.put("resourceName", aclsMap.get(keyAcls));
              case USERNAME -> aclsMapUpdated.put(PRINCIPLE, aclsMap.get(keyAcls));
            }
          }
          aclsMapUpdated.put("host", "*");
          aclsMapUpdated.put("permissionType", "ALLOW");
          if ("READ".equals(aclsMapUpdated.get("operation"))) {
            Map<String, String> newRGroupMap = new HashMap<>(aclsMapUpdated);
            newRGroupMap.put(RESOURCE_TYPE, "GROUP");
            newRGroupMap.put("resourceName", "-na-");
            aclsListUpdated.add(newRGroupMap);
          }
          if (!"ADMIN".equals(aclsMapUpdated.get("operation"))
              && !"READWRITE".equals(aclsMapUpdated.get("operation"))) {
            aclsListUpdated.add(aclsMapUpdated);
          }
        }
  
        return new HashSet<>(aclsListUpdated);
      } catch (RestClientException e) {
        log.error("Exception:", e);
        throw new Exception("Error in listing acls : " + e.getMessage());
      }
    }
  
    private HttpHeaders getHttpHeaders() {
      if (this.httpHeaders == null) {
        this.httpHeaders = new HttpHeaders();
        this.httpHeaders.set("Authorization", "Bearer " + clusterAccessToken);
        return this.httpHeaders;
      }
      return this.httpHeaders;
    }
  
    private RestTemplate getRestTemplate() {
      if (this.restTemplate == null) {
        this.restTemplate = new RestTemplate();
      }
      return this.restTemplate;
    }
  }
  