package io.aiven.klaw.clusterapi.services;

import static io.aiven.klaw.clusterapi.services.AivenApiService.PROJECT_NAME;
import static io.aiven.klaw.clusterapi.services.AivenApiService.SERVICE_NAME;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import io.aiven.klaw.clusterapi.UtilMethods;
import io.aiven.klaw.clusterapi.models.AivenAclResponse;
import io.aiven.klaw.clusterapi.models.ClusterAclRequest;
import io.aiven.klaw.clusterapi.models.enums.ApiResultStatus;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.assertj.core.api.AbstractThrowableAssert;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.util.ReflectionTestUtils;
import org.springframework.web.client.HttpClientErrorException;
import org.springframework.web.client.RestClientException;
import org.springframework.web.client.RestTemplate;

@ExtendWith(SpringExtension.class)
public class AivenApiServiceTest {

  public static final String TESTUSER = "testuser";
  public static final String SERVICE_ACLS_ENDPOINT =
      "https://api.aiven.io/v1/project/projectName/service/serviceName/user/userName";
  AivenApiService aivenApiService;
  @Mock RestTemplate restTemplate;

  private UtilMethods utilMethods;

  private static final String ACLS_BASE_URL = "https://api.aiven.io/v1/project/";

  @BeforeEach
  public void setUp() {
    aivenApiService = new AivenApiService();
    ReflectionTestUtils.setField(aivenApiService, "restTemplate", restTemplate);
    ReflectionTestUtils.setField(
        aivenApiService,
        "addAclsApiEndpoint",
        "https://api.aiven.io/v1/project/projectName/service/serviceName/acl");
    ReflectionTestUtils.setField(
        aivenApiService,
        "listAclsApiEndpoint",
        "https://api.aiven.io/v1/project/projectName/service/serviceName/acl");
    ReflectionTestUtils.setField(
        aivenApiService,
        "deleteAclsApiEndpoint",
        "https://api.aiven.io/v1/project/projectName/service/serviceName/acl/aclId");
    ReflectionTestUtils.setField(
        aivenApiService, "serviceAccountApiEndpoint", SERVICE_ACLS_ENDPOINT);
    ReflectionTestUtils.setField(
        aivenApiService,
        "addServiceAccountApiEndpoint",
        "https://api.aiven.io/v1/project/projectName/service/serviceName/user");
    ReflectionTestUtils.setField(
        aivenApiService,
        "serviceDetailsApiEndpoint",
        "https://api.aiven.io/v1/project/projectName/service/serviceName");
    ReflectionTestUtils.setField(aivenApiService, "clusterAccessToken", "testtoken");
    ReflectionTestUtils.setField(aivenApiService, "restTemplate", restTemplate);
    utilMethods = new UtilMethods();
  }

  // Create Acls (adds service account)
  @Test
  public void createAclsServiceAccountDoesNotExist() throws Exception {
    ClusterAclRequest clusterAclRequest = utilMethods.getAivenAclRequest("Producer");
    String createAclsUri =
        ACLS_BASE_URL
            + clusterAclRequest.getProjectName()
            + "/service/"
            + clusterAclRequest.getServiceName()
            + "/acl";

    AivenAclResponse aivenAclResponse = utilMethods.getAivenAclResponse();
    // create acl stubs
    ResponseEntity<AivenAclResponse> responseEntity =
        new ResponseEntity<>(aivenAclResponse, HttpStatus.OK);
    when(restTemplate.postForEntity(eq(createAclsUri), any(), eq(AivenAclResponse.class)))
        .thenReturn(responseEntity);

    // get service account stubs
    String getServiceAccountUri =
        ACLS_BASE_URL
            + clusterAclRequest.getProjectName()
            + "/service/"
            + clusterAclRequest.getServiceName()
            + "/user/"
            + clusterAclRequest.getUsername();
    // no record exists for this user, throw error by api
    when(restTemplate.exchange(
            eq(getServiceAccountUri),
            eq(HttpMethod.GET),
            any(),
            (ParameterizedTypeReference<Object>) any()))
        .thenThrow(new RuntimeException("No user found"));

    // create service account stubs
    String createServiceAccountUri =
        ACLS_BASE_URL
            + clusterAclRequest.getProjectName()
            + "/service/"
            + clusterAclRequest.getServiceName()
            + "/user";
    ResponseEntity<String> responseEntityAddAccount =
        new ResponseEntity<>("success", HttpStatus.OK);
    when(restTemplate.postForEntity(eq(createServiceAccountUri), any(), eq(String.class)))
        .thenReturn(responseEntityAddAccount);

    Map<String, String> response = aivenApiService.createAcls(clusterAclRequest);
    assertThat(response.get("result")).isEqualTo(ApiResultStatus.SUCCESS.value);
    assertThat(response.get("aivenaclid")).isEqualTo("testid");
  }

  @Test
  public void createAclsService_NullResponse_FromschemaRegistry() throws Exception {
    ClusterAclRequest clusterAclRequest = utilMethods.getAivenAclRequest("Producer");
    String createAclsUri =
        ACLS_BASE_URL
            + clusterAclRequest.getProjectName()
            + "/service/"
            + clusterAclRequest.getServiceName()
            + "/acl";

    // create acl stubs
    ResponseEntity<AivenAclResponse> responseEntity =
        new ResponseEntity<>(null, HttpStatus.INTERNAL_SERVER_ERROR);
    when(restTemplate.postForEntity(eq(createAclsUri), any(), eq(AivenAclResponse.class)))
        .thenReturn(responseEntity);

    Map<String, String> response = aivenApiService.createAcls(clusterAclRequest);
    assertThat(response.get("result")).isEqualTo("Failure in adding acls indeterminable exception");
  }

  // Create Acls (service account already exists)
  @Test
  public void createAclsServiceAccountExists() throws Exception {
    ClusterAclRequest clusterAclRequest = utilMethods.getAivenAclRequest("Producer");
    String createAclsUri =
        ACLS_BASE_URL
            + clusterAclRequest.getProjectName()
            + "/service/"
            + clusterAclRequest.getServiceName()
            + "/acl";

    AivenAclResponse aivenAclResponse = utilMethods.getAivenAclResponse();

    // create acl stubs
    ResponseEntity<AivenAclResponse> responseEntity =
        new ResponseEntity<>(aivenAclResponse, HttpStatus.OK);
    when(restTemplate.postForEntity(eq(createAclsUri), any(), eq(AivenAclResponse.class)))
        .thenReturn(responseEntity);

    // get service account stubs
    String getServiceAccountUri =
        ACLS_BASE_URL
            + clusterAclRequest.getProjectName()
            + "/service/"
            + clusterAclRequest.getServiceName()
            + "/user/"
            + clusterAclRequest.getUsername();
    Map<String, Map<String, String>> serviceAccountResponse = new HashMap<>();
    Map<String, String> userNameMap = new HashMap<>();
    userNameMap.put("username", TESTUSER);
    serviceAccountResponse.put("user", userNameMap);
    ResponseEntity<Map<String, Map<String, String>>> responseEntityServiceAccount =
        new ResponseEntity<>(serviceAccountResponse, HttpStatus.OK);
    when(restTemplate.exchange(
            eq(getServiceAccountUri),
            eq(HttpMethod.GET),
            any(),
            (ParameterizedTypeReference<Map<String, Map<String, String>>>) any()))
        .thenReturn(responseEntityServiceAccount);

    Map<String, String> response = aivenApiService.createAcls(clusterAclRequest);
    assertThat(response.get("result")).isEqualTo(ApiResultStatus.SUCCESS.value);
    assertThat(response.get("aivenaclid")).isEqualTo(aivenAclResponse.getAcl()[0].getId());
  }

  // Create Acls fails with acl already exists
  @Test
  public void createAclsServiceAccountExistsFailure() {
    ClusterAclRequest clusterAclRequest = utilMethods.getAivenAclRequest("Producer");
    String createAclsUri =
        ACLS_BASE_URL
            + clusterAclRequest.getProjectName()
            + "/service/"
            + clusterAclRequest.getServiceName()
            + "/acl";

    // create acl stubs throw error
    when(restTemplate.postForEntity(eq(createAclsUri), any(), eq(String.class)))
        .thenThrow(new RuntimeException("Acl ID already exists"));

    Map<String, String> response = aivenApiService.createAcls(clusterAclRequest);
    assertThat(response.get("result")).contains("Failure");
  }

  // Get service accounts
  @Test
  public void getServiceAccounts() {
    // get service account stubs
    String getServiceAccountUri = ACLS_BASE_URL + "testproject" + "/service/" + "testservice";

    Map<String, Map<String, Object>> serviceAccountsResponse = new HashMap<>();
    Map<String, Object> userNameMap = new HashMap<>();
    ArrayList<HashMap<String, Object>> userList = new ArrayList<>();
    HashMap<String, Object> userNameMapObj1 = new HashMap<>();
    userNameMapObj1.put("username", "user1");

    HashMap<String, Object> userNameMapObj2 = new HashMap<>();
    userNameMapObj2.put("username", "user2");

    userList.add(userNameMapObj1);
    userList.add(userNameMapObj2);

    userNameMap.put("users", userList);
    serviceAccountsResponse.put("service", userNameMap);
    ResponseEntity responseEntityServiceAccount =
        new ResponseEntity<>(serviceAccountsResponse, HttpStatus.OK);

    when(restTemplate.exchange(
            eq(getServiceAccountUri),
            eq(HttpMethod.GET),
            any(),
            (ParameterizedTypeReference<Object>) any()))
        .thenReturn(responseEntityServiceAccount);

    Set<String> response = aivenApiService.getServiceAccountUsers("testproject", "testservice");
    assertThat(response).contains("user1", "user2");
  }

  // Get service accounts
  @Test
  public void getServiceAccountsDontExist() {
    // get service account stubs
    String getServiceAccountUri = ACLS_BASE_URL + "testproject" + "/service/" + "testservice";

    Map<String, Map<String, Object>> serviceAccountsResponse = new HashMap<>();
    ResponseEntity responseEntityServiceAccount =
        new ResponseEntity<>(serviceAccountsResponse, HttpStatus.OK);

    when(restTemplate.exchange(
            eq(getServiceAccountUri),
            eq(HttpMethod.GET),
            any(),
            (ParameterizedTypeReference<Object>) any()))
        .thenReturn(responseEntityServiceAccount);

    Set<String> response = aivenApiService.getServiceAccountUsers("testproject", "testservice");
    assertThat(response).hasSize(0);
  }

  @Test
  public void listAcls() throws Exception {
    String getAclsUrl = ACLS_BASE_URL + "testproject" + "/service/" + "testservice" + "/acl";

    Map<String, List<Map<String, String>>> aclsResp = getAclListMap("testuser");
    ResponseEntity<Map<String, List<Map<String, String>>>> responseEntityServiceAccount =
        new ResponseEntity<>(aclsResp, HttpStatus.OK);

    when(restTemplate.exchange(
            eq(getAclsUrl),
            eq(HttpMethod.GET),
            any(),
            (ParameterizedTypeReference<Map<String, List<Map<String, String>>>>) any()))
        .thenReturn(responseEntityServiceAccount);

    Set<Map<String, String>> acls = aivenApiService.listAcls("testproject", "testservice");
    assertThat(acls).hasSize(6);
  }

  private static Map<String, List<Map<String, String>>> getAclListMap(String userName) {
    Map<String, String> aclMap1 = Map.of("permission", "read");
    Map<String, String> aclMap2 = Map.of("permission", "write");
    Map<String, String> aclMap3 = Map.of("permission", "ADMIN");
    Map<String, String> aclMap4 = Map.of("permission", "READWRITE");
    Map<String, String> aclMap5 = Map.of("id", "ID");
    Map<String, String> aclMap6 = Map.of("topic", "TOPIC");
    Map<String, String> aclMap7 =
        Map.of("username", userName, "topic", "testtopic", "permission", "write");

    List<Map<String, String>> aclList =
        List.of(aclMap1, aclMap2, aclMap3, aclMap4, aclMap5, aclMap6, aclMap7);

    Map<String, List<Map<String, String>>> aclsResp = Map.of("acl", aclList);
    return aclsResp;
  }

  @Test
  public void listAclsFailure() throws Exception {
    String getAclsUrl = ACLS_BASE_URL + "testproject" + "/service/" + "testservice" + "/acl";

    when(restTemplate.exchange(
            eq(getAclsUrl),
            eq(HttpMethod.GET),
            any(),
            (ParameterizedTypeReference<Map<String, List<Map<String, String>>>>) any()))
        .thenThrow(new RestClientException(""));

    AbstractThrowableAssert<?, ? extends Throwable> exception =
        assertThatThrownBy(() -> aivenApiService.listAcls("testproject", "testservice"));
    exception.isInstanceOf(Exception.class);
    exception.hasMessage("Error in listing acls : ");
  }

  @Test
  public void deleteAclsTestAndServiceUser() throws Exception {
    String projectName = "testproject";
    String serviceName = "testservice";

    ClusterAclRequest clusterAclRequest =
        ClusterAclRequest.builder()
            .aivenAclKey("4322342")
            .projectName(projectName)
            .serviceName(serviceName)
            .username(TESTUSER)
            .build();

    handleListAcls(projectName, serviceName, "testuser1"); // different user association

    String actual = aivenApiService.deleteAcls(clusterAclRequest).getMessage();
    String expected = ApiResultStatus.SUCCESS.value;

    assertThat(actual).isEqualTo(expected);
    String uri =
        SERVICE_ACLS_ENDPOINT
            .replace(PROJECT_NAME, projectName)
            .replace(SERVICE_NAME, serviceName)
            .replace("userName", TESTUSER);

    verify(restTemplate, times(1))
        .exchange(eq(uri), eq(HttpMethod.DELETE), any(HttpEntity.class), eq(Object.class));
  }

  @Test
  public void deleteAclsTestAndNotServiceUser() throws Exception {
    String projectName = "testproject";
    String serviceName = "testservice";

    ClusterAclRequest clusterAclRequest =
        ClusterAclRequest.builder()
            .aivenAclKey("4322342")
            .projectName(projectName)
            .serviceName(serviceName)
            .username(TESTUSER)
            .build();

    handleListAcls(projectName, serviceName, TESTUSER); // same user association

    String actual = aivenApiService.deleteAcls(clusterAclRequest).getMessage();
    String expected = ApiResultStatus.SUCCESS.value;

    assertThat(actual).isEqualTo(expected);
    String uri =
        SERVICE_ACLS_ENDPOINT
            .replace(PROJECT_NAME, projectName)
            .replace(SERVICE_NAME, serviceName)
            .replace("userName", TESTUSER);

    verify(restTemplate, times(0)) // service user is not deleted
        .exchange(eq(uri), eq(HttpMethod.DELETE), any(HttpEntity.class), eq(Object.class));
  }

  private void handleListAcls(String projectName, String serviceName, String userName) {
    Map<String, List<Map<String, String>>> aclsResp = getAclListMap(userName);
    ResponseEntity<Map<String, List<Map<String, String>>>> responseEntityServiceAccount =
        new ResponseEntity<>(aclsResp, HttpStatus.OK);

    String getAclsUrl = ACLS_BASE_URL + projectName + "/service/" + serviceName + "/acl";
    when(restTemplate.exchange(
            eq(getAclsUrl),
            eq(HttpMethod.GET),
            any(),
            (ParameterizedTypeReference<Map<String, List<Map<String, String>>>>) any()))
        .thenReturn(responseEntityServiceAccount);
  }

  @Test
  public void deleteAclsTestFailure() throws Exception {
    ClusterAclRequest clusterAclRequest =
        ClusterAclRequest.builder()
            .aivenAclKey("4322342")
            .projectName("testproject")
            .serviceName("serviceName")
            .build();
    String aclsUrl =
        ACLS_BASE_URL
            + clusterAclRequest.getProjectName()
            + "/service/"
            + clusterAclRequest.getServiceName()
            + "/acl/"
            + clusterAclRequest.getAivenAclKey();
    when(restTemplate.exchange(
            eq(aclsUrl), eq(HttpMethod.DELETE), any(HttpEntity.class), any(Class.class)))
        .thenThrow(new RuntimeException(""));

    AbstractThrowableAssert<?, ? extends Throwable> exception =
        assertThatThrownBy(() -> aivenApiService.deleteAcls(clusterAclRequest));
    exception.isInstanceOf(Exception.class);
    exception.hasMessage("Error in deleting acls ");
  }

  @Test
  public void deleteAclsTestFailure404() throws Exception {
    ClusterAclRequest clusterAclRequest =
        ClusterAclRequest.builder()
            .aivenAclKey("4322342")
            .projectName("testproject")
            .serviceName("serviceName")
            .build();
    String aclsUrl =
        ACLS_BASE_URL
            + clusterAclRequest.getProjectName()
            + "/service/"
            + clusterAclRequest.getServiceName()
            + "/acl/"
            + clusterAclRequest.getAivenAclKey();
    when(restTemplate.exchange(
            eq(aclsUrl), eq(HttpMethod.DELETE), any(HttpEntity.class), any(Class.class)))
        .thenThrow(new HttpClientErrorException(HttpStatus.NOT_FOUND));

    String actual = aivenApiService.deleteAcls(clusterAclRequest).getMessage();
    String expected = ApiResultStatus.SUCCESS.value;

    assertThat(actual).isEqualTo(expected);
  }

  // Create Acl Conflict 409 already exists, create service user
  @Test
  public void createAclAlreadyExists() {
    ClusterAclRequest clusterAclRequest = utilMethods.getAivenAclRequest("Producer");
    String createAclsUri =
        ACLS_BASE_URL
            + clusterAclRequest.getProjectName()
            + "/service/"
            + clusterAclRequest.getServiceName()
            + "/acl";

    // create acl stubs throw error
    when(restTemplate.postForEntity(eq(createAclsUri), any(), eq(String.class)))
        .thenThrow(new RuntimeException("Acl ID already exists"));

    Map<String, String> response = aivenApiService.createAcls(clusterAclRequest);
    assertThat(response.get("result")).contains("Failure");
  }
}
